Redis内存淘汰策略

Redis v4.0前提供6种淘汰策略,Redis v4.0后增加两种:

  • volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
  • volatile-lru:从已设置过期时间的数据集中使用LRU算法移除key
  • volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
  • volatile-lfu:从已设置过期时间的数据集中使用LFU算法移除key
  • allkeys-lru:当内存不足以容纳写入数据时,在键空间中使用LRU算法移除key
  • allkeys-random:从数据集中任意选择数据淘汰
  • allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中使用LFU算法移除key
  • no-eviction:禁止驱逐数据,内存不足以容纳新写入数据时,写入操作会报错

Redis为什么选择跳跃表而非红黑树

  1. 范围查找:跳跃表只需要遍历第一层链表,而红黑树需要先找到最小值然后进行中序遍历。
  2. 插入删除:跳跃表只需要修改相邻节点的指针,红黑树可能需要进行子树的逻辑复杂的调整。
  3. 内存占用:跳跃表每个节点包含的指针数目平均为1/(1-p),Redis中p=1/4,平均每个节点包含1.33个指针,具体取决于参数p的大小,而红黑树每个节点包含2个指针。

Redis持久化数据和缓存如何做扩容

  1. Redis被当作缓存使用,使用一致性哈希实现动态扩容缩容;
  2. 如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话,必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做这样。

保证缓存与数据库双写时的数据一致性

两种方案:

  1. 先删除缓存,后更新数据库
  2. 先更新数据库,后删除缓存

对于第一种,延时双删方案如下:

  1. public void write(SString key, Object data) {
  2. Redis.delKey(key);
  3. DB.updateData(data);
  4. Thread.sleep(1000);
  5. Redis.delKey(key);
  6. }
  1. 先淘汰缓存
  2. 再写数据库
  3. 休眠1S,再次淘汰缓存

这样可以将1S内所造成的缓存脏数据再次删除。
确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
自己评估读数据业务逻辑耗时,写数据的休眠时间在读数据业务逻辑的耗时基础上增加几百毫秒即可。

如果使用MySQL读写分离架构,那么主动同步之间也会有时间差。
此时来了两个请求,请求A(更新操作)和请求B(查询操作):

  1. 请求A更新操作,删除Redis数据
  2. 请求主库进行更新,主库和从库进行同步
  3. 请求B查询操作,发现Redis没有数据
  4. 去从库中拿数据
  5. 此时同步数据还未完成,拿到的数据是旧数据

解决办法就是如果是对Redis进行填充数据的查询数据库操作,那么就强制其指向主库进行查询。

对于第二种,方案如下:
这一种情况的问题是,更新数据库成功,但是删除缓存的阶段出错,那么再读取缓存就都是脏数据了。
解决方案就是利用消息队列进行删除补偿,具体业务逻辑如下:

  1. 请求A先对数据库进行更新操作
  2. 在对Redis进行阐述操作的时候发现报错,删除失败
  3. 此时将Redis的key作为消息体发送到消息队列中
  4. 系统接收到消息队列发送的消息再次对Redis进行删除操作

但是这个方案会有一个缺点就是会对业务代码造成大量的侵入,深深的耦合在一起,所以这时会有一个优化的方案,订阅MySQL的binlog,对缓存进行操作。

Redis的单线程以及IO变更多线程原因

(1)Redis v6.0以前单线程的原因
在Redi v6.0以前,Redis的核心网络模型选择使用单线程来实现。

对于一个DB来说,CPU通常不会是瓶颈,因为大多数请求不是CPU密集型,而是I/O密集型。
具体到Redis的话,如果不考虑RDB/AOF等初九话方案,Redis是完全的纯内存操作,执行速度是非常快的,因此这部分操作通常不会是性能瓶颈,Redis真正的性能瓶颈在于网络I/O,也就是客户端和服务端之间的网络传输延迟,因此Redis选择了单线程的I/O多路复用epoll实现核心网络模型。

更加具体的选择单线程的原因如下:

  1. 避免上下文切换开销:单线程避免频繁的线程切换开销,避免用户态和内核态的切换;
  2. 避免同步机制的开销:多线程需要引入同步机制,比如锁,加锁解锁的开销增加程序复杂度还会降低性能;
  3. 简单可维护:多线程模式需要让所有的底层数据结构都必须实现成线程安全的,实现更加复杂。

任何选择,都是多方博弈之后的一种权衡:在保证足够的性能表现之下,使用单线程保持代码的简单和可维护性。

(2)Redis中引入的多线程

  1. Redis v4.0引入多线程处理异步任务
  2. Redis v6.0在网络模型中实现多线程I/O

Redis的网络I/O瓶颈已经越来越明显了。
随着互联网的飞速发展,互联网业务系统所要处理的线上流量越来越大,Redis的单线程模式会导致系统消耗很多CPU时间在网络I/O上从而降低吞吐量,要提升Redis的性能有两个方向:

  • 优化网络I/O模块
  • 提高机器内存读写的速度

后者依赖于硬件的发展,暂时无解。所以只能从前者下手,网络I/O的优化又可以分为两个方向:

  • 零拷贝技术或者DPDK技术
  • 利用多核优势

零拷贝有局限性,无法完全适配Redis这种复杂的网络I/O场景,更多网络I/O对CPU时间的消耗和Linux零拷贝技术。DPDK技术通过旁路网卡I/O绕过内核协议栈的方式又过于复杂以及需要内核甚至是硬件的支持。
总结,Redis支持多线程主要就是两个原因:

  • 可以充分利用服务器CPU资源,目前主线程只能利用一个核
  • 多线程任务可以分摊Redis同步IO读写负荷

Redis如何实现延时队列

使用sortedset,用时间戳作为score,使用zadd key score1 value1命令生产消息,
使用zrangebyscore key min max withsscores limit 0 1消费消息最早的一条消息。

[

](https://blog.csdn.net/u010765526/article/details/89071207)