redis

基于内存的单线程的可持久化存储的NoSql数据库
Redis为什么快!
  • 1 Redis是C语言编写,但是语言不是影响快慢的核心,客户端通过socket与服务端建立网络通道,然后发送请求命令。

  • 2 相较其他基于磁盘的DB,Redis 的纯内存IO操作有着天然的性能优势

  • 3 它是I/O多路复用,基于 epoll/select/kqueue 等 I/O 多路复用技术,实现高吞吐的网络 I/O。

  • 4 单线程无法利用多核,但是却避免了多线程频繁切换上下文和同步机制如锁带来的开销。

  • 5 请求大多数是IO密集型,不是CPU密集型, 如果不考虑 RDB/AOF 等持久化方案,基于内存的Redis是非常快的,它的性能瓶颈在网络IO,也就是客户端和服务端之间的网络传输延迟,因此 Redis 选择了单线程的 I/O 多路复用来实现它的核心网络模型。

  • 6 多线程调度过程中必然需要在 CPU 之间切换线程上下文 context,上下文的切换又涉及程序计数器、堆栈指针和程序状态字等一系列的寄存器置换、程序堆栈重置甚至是 CPU 高速缓存、TLB 快表的汰换,如果是进程内的多线程切换还好一些,因为单一进程内多线程共享进程地址空间,线程上下文比之进程上下文要小得多,如果是跨进程调度,则需要切换掉整个进程地址空间。如果是单线程则可以规避进程内频繁的线程切换开销,因为程序始终运行在进程中单个线程内,没有多线程切换的场景。

  • 7 如果 Redis 选择多线程模型,又因为 Redis 是一个数据库,涉及到底层数据同步的问题,必然会引入某些同步机制,比如锁,而 Redis 不仅仅提供了简单的 key-value 数据结构,还有string, list、set,zset,hash的数据结构,而不同的数据结构对同步访问的加锁粒度又不尽相同,可能会导致在操作数据过程中带来很多加锁解锁的开销,增加程序复杂度的同时还会降低性能。

windows配置密码
  • 1 在go的redis包中有个参数 : TestOnBorrow 是一个测试链接可用性的方法.
  • 2 win10里配置redis密码,一劳永逸的办法是改配置文件:redis.windows.conf 在requirepass处 后面将foobared改成要修改的密码就行。 启动的时候在CMD终端执行启动服务命令:
    -- 服务端启动:redis-server redis.windows.conf (如果不行,就按tab键启动该行命令)
    -- 客户端启动: redis-cli
    -- 验证密码: config get requirepass
    -- 授权密码 : auth **(123456)
    -- 再次验证密码: config get requirepass (最好用管理员权限操作)
Redis 重启
  • 1 redis-cli -h 127.0.0.1 -p 6379 shutdown
  • 2 执行后如果报权限错误:NOAUTH Authentication required 就执行输入密码: auth 123456(密码)
  • 3 再shutdown
  • 4 再启动服务: redis-server redis.windows.conf

redis对键的删除机制

  • 被动删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key
  • 主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key
  • 当前已用内存超过maxmemory限定时,触发主动清理策略

-- 被动删除是当,这个key被操作时,redis才会被动检查该key是否过期,过期就删除并且返回NIL。这种删除策略对CPU是友好,只在操作时才会被删除,但是有一些key永远不会访问到,服务器也不会主动去删除,对于依赖于内存的Redis服务器来说,无用的垃圾数据会占用大量内存空间.
-- 主动策略,定期会检查自身的资源和状态,以及整理,让服务器保持在一个健康的稳定状态,叫常规操作.这种删除弥补了被动删除上对内存的不友好.
-- 实际上Redis不会准确将整个数据库中最久未被使用的键删除,而是随机删除

redis缓存:穿透,雪崩,击穿,的解决思路
  • 1 缓存穿透是指查询一个不存在的数据。例如:从缓存redis没有命中,需要从mysql数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透

  • 2 如果查询redis没有,然后再去Mysql查,还是没有的话就写一个默认值Null到redis里,然后把过期时间expire设置成30s秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击,第二次到缓冲中获取就有值了,而不会继续访问数据库。设置一个过期时间或者当有值的时候将缓存中的值替换掉即可。接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截。

  • 3 可以给key设置一些格式规则,然后查询之前先过滤掉不符合规则的Key。

  • 4 穿透的解决方案:1装一个布隆过滤器,访问Redis之前,在布隆过滤器里查询这个Key是否存在,存在再去访问Redis,不存在就直接返回出去1 个默认的空对象过去,比如:"null" ,再设置一个过期或当有值的时候将缓存的值替换即可,第二次访问的时候就有值了。

  • 5 雪崩的解决思路:1在原有的失效时间上加上一个随机值,比如1-5分钟随机。这样就避免了因为采用相同的过期时间导致的缓存雪崩。2 使用熔断机制。当流量到达一定的阈值时,就直接返回“系统拥挤”之类的提示,防止过多的请求打在数据库上。至少能保证一部分用户是可以正常使用,其他用户多刷新几次也能得到结果。 3 提高数据库的容灾能力,可以使用分库分表,读写分离的策略。 4 为了防止Redis宕机导致缓存雪崩的问题,可以搭建Redis集群,提高Redis的容灾性。

  • 6 缓存击穿解决思路: 1如果业务允许的话,对于热点的key可以设置永不过期的key 2 使用互斥锁。如果缓存失效的情况,只有拿到锁才可以查询数据库,降低了在同一时刻打在数据库上的请求,防止数据库打死。当然这样会导致系统的性能变差。

可持久化存储的2种方式:
  • RDB持久化方式是默认开启的,在配置文件写Yes,AOF是默认关闭的,需手动开启。

  • 1 RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储

  • 2 AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

  • 3 RDB的优点:1一旦系统出现灾难性故障,我们可以非常容易的进行恢复 2 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上 3只要fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了 4相比于AOF机制,如果数据集很大,RDB的启动效率会更高

  • 4 AOF持久化存储,就算系统宕机了,下次重启前还是能通过redis-check-aof工具来帮助我们解决数据一致性的问题。AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快

Redis在win10下利用AOF做可持久化存储
  • 1 先把redis.windows.conf 配置文件里的appendonly no 改成:appendonly yes
  • 2 配置好后就重启Redis,然后会发现多了个文件:appendonly.aof
  • 3 输入命令,插入数据:


    image.png
  • 4 flushall 最后输入这个清除刚刚插入的数据
  • 5 再keys *就没有数据了
  • 6 打开appendonly.aof文件,可以看到:


    image.png
  • 7 去掉最后面的flushall(也可以按照redis协议增加命令),重启客户端和服务端,看数据是否真的持久化了:


    image.png
  • 8 到这一步就已经证明利用AOF形式进行可持久化存储成功!
Redis的优化
  • 1 随着并发的流量越来越大,单线程IO的瓶颈已经越来越明显,优化的方向有2个
    -- 优化网络IO模块
    -- 提高机器内存读写的速度
    网络 I/O 的优化又可以分为两个方向:
    -- 1 零拷贝技术或者 DPDK 技术
    -- 2 利用多核

  • 2 零拷贝技术有其局限性,无法完全适配 Redis 这一类复杂的网络 I/O 场景。利用多核优势就成了优化网络 I/O 性价比最高的方案。Redis在6.0版本之后引入了多线程,多线程跟单线程模型是一样的,区别在于变动的地方只是把读取客户端请求命令和回写响应数据的逻辑异步化了,交给 I/O 线程去完成。I/O 线程仅仅是读取和解析客户端命令而不会真正去执行命令,客户端命令的执行最终还是要在主线程上完成。

  • 3 Redis 的多线程模型是全程无锁,这是通过原子操作+交错访问来实现的,主线程和 I/O 线程之间共享的变量有三个:io_threads_pending计数器、io_threads_op I/O 标识符和 io_threads_list 线程本地任务队列。3个变量的访问是相互错开的,不会引发数据竞争的问题。io_threads_pending 是原子变量,不需要加锁保护(1:利用原子操作+交错访问实现无锁的多线程模型。 2:以及通过设置 CPU 亲和性,隔离主进程和其他子进程,让多线程网络模型能发挥最大的性能)

如何解决DB和缓存一致性问题?
  • 1 读的时候先从Redis读取,如果Redis中没有,那么再去数据库中读,读完之后放回Redis,然后返回响应。写的时候先删除缓存,然后再去写入数据库,然后等待100毫秒再次删除缓存。这里有几点点需要说明: 为什么是先删除缓存而不是先更新数据? 假设我们先更新数据,再删除缓存,那么存在数据更新成功,但是删除缓存失败的情况。先删除缓存,如果数据库写操作成功,下次读缓存时会从数据库获取新的数据并放入缓存;如果数据库写操作失败,那么再次读到的数据也是旧的数据,也保证了缓存的一致性。另外,有的缓存数据是经过计算的数据,不是单纯的某个字段的值,如果去更新的话会是一个复杂的过程,倒不如下次获取的时候去计算并放入缓存,这也是一个“懒”的思想。 为什么写入数据库后还要再次删除缓存?采用延时双删策略 我们在数据库写操作的时候是不能保证没有读的操作,特别是在高并发场景下,往往数据库写操作还没完成,就已经有读的操作完成并将修改前的数据放入缓存,这也就造成了缓存和数据库中的数据不一致的问题。那么解决这个问题有一下方法

  • 2 数据库写操作完成后再次删除缓存,这样出现不一致的时间就只会在数据库写操作和再次删除缓存这段时间内,如果业务能够容忍短时间的不一致,可以采用这个方法。

  • 3 如果业务对一致性要求较高,那么可以在第一次删除缓存后对后续的操作做串行处理,后续的所有操作都需要等待数据库写操作的完成。具体的代码思路可以是这样子,将请求放入JVM的一个Queue中,将请求积压在队列中,同步等待写操作的完成。这个思路的实现有些复杂,还涉及到如果Queue中积累的请求过多该怎么处理,请求过多的话也就说明这是个热点key。

  • 4 第二种方案:异步更新缓存(基于订阅binlog的同步机制) 技术整体思路: MySQL binlog增量订阅消费+消息队列+增量数据更新到redis 读Redis:热数据基本都在Redis 写MySQL:增删改都是操作MySQL 更新Redis数据:MySQ的数据操作binlog,来更新到Redis Redis更新 1)数据操作主要分为两大块: 一个是全量(将全部数据一次写入到redis) 一个是增量(实时更新) 这里说的是增量,指的是mysql的update、insert、delate变更数据。 2)读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。 这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。 其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。 这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。 当然,这里的消息推送工具你也可以采用别的第三方:kafka、rabbitMQ等来实现推送更新Redis。 以上就是Redis和MySQL数据一致性详解

Redis客户端
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容