线上redis ttl时间未到缺key却被删除排查录

引言:

以下内容直接从我球的docs粘贴:https://xueqiu.com/

欢迎简历投递:[email protected]

问题:

目前悬赏贴悬赏有效期为5天,余额只存储在redis中,集群为status集群,key:st:offer:balance:{statusId},缓存有效期为6天。当进行分配赏金时,会查询该贴的余额。有问题的悬赏贴,相应的redis key被删除。

分析:

首先,大致看了下业务代码和 redis 组件相关的实现,以及 24/25 号的日志,没有发现值得怀疑的点;然后,根据上述的时间范围,去监控面板找异常,还真找到了。。。 24/25号前后 redis 内存占用不正常(具体原因需要业务排查),下图是过去 10 天的一个占用走势

线上redis ttl时间未到缺key却被删除排查录_第1张图片

对比下图的 redis 最大内存配置,会发现 8 个分片的主库内存在 2020.06.24 12:00 - 2020.06.25 03:00 期间都触达了最大值

内存淘汰策略相当于清除掉那些占用内存并且使用不太频繁的数据,淘汰掉这些不活跃数据来清理内存

  我们知道,redis设置配置文件的maxmemory参数,可以控制其最大可用内存大小(字节)。

那么当所需内存,超过maxmemory怎么办?

这个时候就该配置文件中的maxmemory-policy出场了。

其默认值是noeviction。

下面我将列出当可用内存不足时,删除redis键具有的淘汰规则。

就会根据配置的清理策略,redis开始干活了

  • volatile-lru

使用LRU算法,从设置了过期时间的key中选择删除

  • allkeys-lru

使用LRU算法,从所有key中选择删除

  • volatile-random

从设置了过期时间的key中随机删除

  • allkeys-random

从所有的key中随机删除

  • volatile-ttl

从设置了过期时间的key中选择最先过期的删除

  • noeviction

不处理,当有写操作时,直接返回错误

LRU算法,least RecentlyUsed,最近最少使用算法。也就是说默认删除最近最少使用的键。

但是一定要注意一点!redis中并不会准确的删除所有键中最近最少使用的键,而是随机抽取5个键,删除这五个键中最近最少使用的键。

那么5这个数字也是可以设置的,对应位置是配置文件中的maxmeory-samples.

https://redis.io/topics/faq

线上redis ttl时间未到缺key却被删除排查录_第2张图片

redis 统计还提供了另一个数据,叫 evicted_keys,可以看到分片强制淘汰了多少个 key,其他分片大致类似

改进

redis的内存占用本来就是件应当关注的事情

1.增加redis内存监控报警

2.加强使用人员对redis使用的方式了解,结合自身场景选择合适的配置


相关总结

DEL 和 UNLINK区别?同步还是异步?

使用DEL命令会触发「同步删除」,如果Key是一个有很多元素的复杂类型,这个过程可能会堵塞一下Redis服务自身,从而影响用户的访问。

使用UNLINK命令,Redis服务会先计算删除Key的成本,从而更智能地做出「同步删除」或「异步删除」的选择。

成本计算:

对于list,hash,set,zset的对象类型,如果长度大于64(由宏LAZYFREE_THRESHOLD定义),才会采用异步删除的手段,从当前db先释放该key,再由另外一个线程做异步删除。对于长度不大于64的复杂类型,异步删除比同步删除还多了一些函数调用与多线程同步的代价,所以同步删除更好。对于string对象,底层的数据结构sds是一份连续的内存,内存分配器回收这块内存的复杂度是O(1),所以采用同步删除也不会堵塞服务。

总的来说,我们作为用户,都能用UNLINK替代DEL。

驱逐策略

Redis通过参数maxmemory来选择不同的驱逐策略:

  • volatile-random 从已设置过期时间的数据集(server.db[i].expires)中任意选择数据驱逐;volatile-lru 从数据集(server.db[i].dict)中挑选最近最少使用的数据驱逐(2.8默认);

  • volatile-ttl 从已设置过期时间的数据集(server.db[i].expires)中寻找最近即将过期(ttl最小)的key来驱逐;

  • allkeys-random 从数据集(server.db[i].dict)中任意选择数据驱逐;

  • allkeys-lru 从数据集(server.db[i].dict)中挑选最近最少使用的数据驱逐;

  • noeviction 禁止驱逐数据,永远不驱逐,仅对写操作返回一个错误(4.0默认);

在4.0版本后,还增加了以下两种驱逐策略。

  • volatile-lfu在过期集合中使用LFU链来驱逐数据;

  • allkeys-lfu 从数据集(server.db[i].dict)使用LFU算法来驱逐数据;

4.0后,在返回写入失败前,还会先检测lazyfree线程是否还有待删除的Key,没有才会给用户返回写入失败。

在4.0或以上的版本,Key的驱逐会基于参数lazyfree_lazy_eviction,来决定采用unlink还是del。在2.8版本,则只会用del。

 

Key的访问淘汰

对于Slave节点,访问到了已过期的Key,Slave节点会返回该Key不存在,但不会主动删除该Key。删除的动作,还是会从Master上同步过来。

对于Master节点,在4.0或以上的版本,会根据参数lazyfree-lazy-expire,来决定用DEL还是UNLINK。对于2.8版本,则只能用DEL了。这些删除的动作,都会同步到Slave与AOF文件中。

Key的定时淘汰

多久会执行一次定时调度呢?

redis服务的参数hz能控制定时淘汰的频率,hz默认是10,即每秒能调度100次。

定时淘汰一定是在master上发生的吗?

 

有些时候,用户会把Slave节点设置成可写,那么Slave上写的带有过期时间的Key,因为Master是不知道的,就一直不会淘汰掉。所以在版本4.0以后,Redis增加了单独的逻辑,在定时淘汰中删除这些在slave节点上写入的过期Key。

“FAST淘汰”和“SLOW淘汰”?

前者每次淘汰只能花1毫秒,不能花更多了,后者是以hz=10为例,每次调度的总时间是100ms,这里调度不会25%的cpu时间,即25ms。

如果每淘汰1个Key就检测一次,无疑代价太大。从源码上看,定时淘汰会尝试遍历每个db,遍历完了或者时间到了就退出循环。第一层循环是遍历各个db,第二层循环是遍历db里面的一批批key,一批key是20个,如果第三层循环结束后有大于5个key是成功淘汰的(说明这个db很多淘汰key),那么二层就继续循环,如果小于等于5个key,说明这个db没有很多key需要淘汰,则退出二层循环,第三层循环是一批key里面逐个key进行淘汰。即最多320个key进行判断后,就会看看是否已经超过cpu占用时间。

在4.0或以上的版本,会根据参数lazyfree-lazy-expire(默认no)来做DEL还是UNLINK。

 

总结

 

1.驱逐策略的选择,往往与业务特点、使用场景紧密相关。不当的选择,可能会让用户丢失不想丢失的数据,或者导致较差的驱逐效率;

 

2.已过期的Key往往不会立刻被删除,用户在导出快照与建立主从时,会疑惑主从之间的Key数量不一致,我们都需要了解这一点;

 

3.驱逐与淘汰都有可能影响服务,在新版本下,最好都开启unlink代替del。

 

 

你可能感兴趣的:(Redis)