硬核干货!详细解析Redis缓存:过期、淘汰策略、三大问题

缓存

过期删除策略

Why?

对 key 设置过期时间,需要有相应的机制将已过期的键值对删除

How?

  • 过期字典:保存所有 key 的过期时间。

    •  arduino 

      复制代码

      typedef struct redisDb { //数据库键空间,存放着所有的键值对 dict *dict; //键的过期时间 dict *expires; .... } redisDb;

检查该 key 是否存在于过期字典中:

  • 不在,正常读取键值;
  • 在,获取该 key 的过期时间,与当时间比对,判定是否过期。

常见的三种过期删除策略

  • 定时删除:当时间到达时,由事件处理器自动执行 key 的删除操作。

    • 删除非常快
    • 对CPU不友好
  • 惰性删除:使用时发现过期才删除

    • 对CPU友好、内存空间浪费
  • 定期删除:每隔一段时间「随机」取出一定数量的 key 进行检查和删除

    • 限制删除执行的时长和频率,减少CPU占用、内存也不会浪费太多

    • 难以确定删除操作执行的时长和频率。

Redis 过期删除策略

惰性删除+定期删除:合理使用 CPU 时间和避免内存浪费之间取得平衡。

  • 惰性删除:访问或者修改 key 前,调用 expireIfNeeded 函数检查 key 是否过期:

    • 过期:删除该 key,选择异步删除,还是同步删除,根据 lazyfree_lazy_expire 参数配置决定(Redis 4.0版本开始提供参数),然后返回 null 客户端;
    • 没有过期:不做任何处理,返回正常的键值
  • 定期删除:

    • hz 10:10s一次

    • 从过期字典中随机抽取 20 个 key;

    • 检查是否过期并删除已过期的 key;

    • 本轮已过期超过 5 个(25%),继续重复步骤 1;小于 25%,则停止。

    • 定期删除循环流程的时间上限25ms

内存淘汰策略

Why?

Redis 的运行内存超过最大内存后,使用内存淘汰策略删除符合条件的 key,以此来保障 Redis 高效的运行。

  • maxmemory :设置最大内存,默认0无限制

How?

Redis 内存淘汰策略

  • 不淘汰

    • noeviction(Redis3.0默认) :超过最大内存,不淘汰任何数据,有新的数据写入 OOM,查询删除正常。
  • 数据淘汰

    • 设置过期时间的数据中进行淘汰:

      • volatile-random:随机淘汰设置了过期时间的任意键值;
      • volatile-ttl:优先淘汰更早过期的键值。
      • volatile-lru(Redis3.0之前默认):淘汰所有设置了过期时间的键值中,最久未使用的键值;
      • volatile-lfu(Redis 4.0 新增):淘汰所有设置了过期时间的键值中,最少使用的键值;
    • 在所有数据范围内进行淘汰:

      • allkeys-random:随机淘汰任意键值;
      • allkeys-lru:淘汰整个键值中最久未使用的键值;
      • allkeys-lfu(Redis 4.0新增):淘汰整个键值中最少使用的键值。

查看当前 Redis 使用的内存淘汰策略:config get maxmemory-policy

修改内存淘汰策略:

  • config set maxmemory-policy <策略>:立即生效,重启恢复默认
  • 修改配置文件maxmemory-policy <策略>:重启生效

LRU 算法和 LFU 算法有什么区别?

  • LRU(Least Recently Used):最近最少使用,最近最久未使用

    • 传统实现:链表

      • 额外空间开销、移动链表耗时
    • Redis实现:近似 LRU 算法(节约内存、CPU)

      • Redi对象结构体中添加字段,记录此数据的最后一次访问时间。
      • 随机取 5 个(可配置)值,淘汰最久没有使用的那个。
      • 缺点:无法解决缓存污染问题
  • LFU(Least Frequently Used):最近最不常用,根据数据访问次数来淘汰

    • Redis实现:redisObject 记录「数据的访问频次」

      • ldt:访问时间戳
      • logc:访问频次(频率)
    • 先按照上次访问距离当前时长,对 logc 衰减;

    • 再按照一定概率增加 logc 的值

 

arduino

复制代码

typedef struct redisObject { ... // 24 bits //用于记录对象的访问信息 unsigned lru:24; ... } robj;

  • LRU:记录 key 的访问时间戳
  • LFU:高 16bit ldt(Last Decrement Time),低 8bit logc(Logistic Counter)。

  • lfu-decay-time:调整 logc 的衰减速度,分钟为单位,默认值为1,越大越慢;
  • lfu-log-factor :调整 logc 的增长速度,值越大,增长越慢。

缓存雪崩

Why?

Redis大量数据同时过期、Redis故障,导致服务器对数据库发起大量请求,击垮数据库

How?

解决办法

针对大量数据同时过期:

  • 均匀设置过期时间:设置时间时加上随机数

  • 互斥锁:服务数据库前加锁,保证只有一个服务访问数据库

  • 双 key 策略(分级缓存):两倍空间浪费

    • 主 key,设置过期时间。备 key,不会设置过期。 value 值一样。
    • 访问不到主 key,返回备key并更新主备的数据
  • 后台更新缓存:缓存永久有效,后台线程定时更新缓存

    • 当系统内存紧张的时候,有些缓存数据会被“淘汰”,到下次后台定时更新前无法访问

      • 后台线程不仅定时更新缓存,也频繁地(ms级)检测缓存是否有效
    • 发现缓存数据失效后(缓存数据被淘汰),通过消息队列通知后台线程更新缓存

      • 后台线程收到消息后,在更新缓存前可以判断缓存是否存在,存在就不执行更新缓存操作;不存在就读取数据库数据,并将数据加载到缓存。更新会更及时。

针对Redis故障:

  • 服务熔断或请求限流机制;

    • 服务熔断机制:暂停业务应用对缓存服务的访问,直接返回错误,不用继续访问数据库
    • 请求限流机制:只将少部分请求发送到数据库进行处理,再多的请求就在入口直接拒绝服务,等到 Redis 恢复正常并把缓存预热完后,再解除请求限流的机制。
  • 构建 Redis 缓存高可靠集群;

    • 主从节点的方式构建 Redis 缓存高可靠集群

缓存击穿

Why?

热点数据过期,大量的请求访问了该热点数据,直接访问数据库,数据库被高并发的请求冲垮

How?

  • 分布式锁+doublecheck:保证同一时间只有一个业务线程更新缓存,未获取互斥锁的请求,等待锁释放后重新读取缓存或返回空值或者默认值。
  • 不给热点数据设置过期时间+LFU:由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;

缓存穿透

Why?

缓存穿透:大量用户访问的数据,既不在缓存中,也不在数据库中

  • 业务误操作:缓存中的数据和数据库中的数据都被误删除了
  • 黑客恶意攻击:故意大量访问某些读取不存在数据的业务

How?

  • 非法请求的限制;

  • 缓存空值或者默认值;

    • 针对查询的数据,在缓存中设置一个空值或者默认值,后续请求不会继续查询数据库。
  • 使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在;

如何设计一个缓存策略,可以动态缓存热点数据呢?

通过数据最新访问时间来做排名,定期过滤掉不常访问的数据,只留下经常访问的数据。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值