Redis 大key热key过期key
过期key删除机制
来了解下redis关于过期key的删除机制。
- 惰性删除:当我们使用惰性删除时,数据到期了也不会自动删除,那么他的删除方式是,在下一次在获取这个
key值时,会做一个判断,判断这个key是否过期,如果过期了在执行删除。也就是说当再次执行get name时 会走一个函数expirelfNeeded()这个函数就是判断此key是否过期的。过期的返回nil,然后从内存在进行删除。 - 定期删除:由
redis的定时任务函数实现,该函数以一定的频率运行,每次运行时,都从键空间中取出一定数量的随机key进行检查,并删除其中的过期键。注意:不是每次定时任务都会检查所有的key,而是随机检查一定数量的key,该机制旨在防止阻塞redis主进程太久而造成业务阻塞,所以会造成已过期的key释放内存速度较慢。定期删除的周期受hz参数限制
redis定期任务与hz参数的关系
为了定期检测资源和服务状态并根据预定策略执行相应的操作,Redis调用一个内部函数来执行多种后台任务,例如:
- 计算LRU信息并清除过期key。
- 关闭超时的客户端连接。
- 整理hash类型的数据。
- 执行RDB或AOF持久化相关操作。
- 更新统计信息。
这些定期任务是Redis服务正常运行的保障,它们的执行频率由hz参数的值指定,默认为10,即每秒执行10次。
redis-cli info Server
redis-cli config get hz
# 永久修改该参数的值,可以在其配置文件中指定如下参数:
hz n # n表示的是一个1~100返回的数值典型应用场景
Redis会通过执行定期任务来主动清除过期key,执行过程如下:
- 从设置了过期时间的key的集合中随机检查20个key。
- 删除检查中发现的所有过期key。
- 如果检查结果中25%以上的key已过期,则开始新一轮任务。
如果过期key数量很多或者增加速度很快,而Redis的主动清除频率较低,过期key将占用大量的内存空间,可能会影响Redis服务的性能。适当调整hz参数的值,提高清除频率,能够很好地解决这个问题。
取值范围及设置建议
hz的取值范围为1~500。增大hz参数的值会提升各项定期任务的执行频率,但也会提高Redis服务的CPU使用率。默认值10在一般情况下已经可以满足需求,如果业务场景对于某些定期任务的执行频率有很高的要求,您可以尝试在100以内调整参数值。将hz的值增加到100以上对CPU使用率有相对较大的影响,请谨慎操作。
大key热key
大key可以分为两种情况:
- key的Value较大,例如一个String类型的key大小达到10MB,或者一个集合类型(Hash,List,Set等)的元素总大小达到了100MB。一般单个String类型的key大小达到10kB,或者集合类型的key总大小达到50MB,则定义其为大key。
- key的元素较多,例如一个Hash类型的key,其元素数量达到了10000。一般定义集合类型的key中元素超过5000个,则认为其为大key。
热key通常以一个key被操作的频率和占用的资源来判定其是否为热key,例如:
- 某个集群实例一个分片每秒处理10000次请求,其中有3000次都是操作同一个key。
- 某个集群实例一个分片的总带宽使用(入带宽+出带宽)为100Mbits/s,其中80Mbits是由于对某个Hash类型的key执行HGETALL所占用。
存在大Key/热Key,有什么影响?
大key
造成规格变更失败。
Redis集群变更规格过程中会进行数据rebalance(节点间迁移数据),单个Key过大的时候会触发Redis内核对于单Key的迁移限制,造成数据迁移超时失败,Key越大失败的概率越高,大于512MB的Key可能会触发该问题。
造成数据迁移失败。
数据迁移过程中,如果一个大Key的元素过多,则会阻塞后续Key的迁移,后续Key的数据会放到迁移机的内存Buffer中,如果阻塞时间太久,则会导致迁移失败。
容易造成集群分片不均的情况。
- 各分片内存使用不均。例如某个分片占用内存较高甚至首先使用满,导致该分片Key被逐出,同时也会造成其他分片的资源浪费。
- 各分片的带宽使用不均。例如某个分片被频繁流控,其他分片则没有这种情况。
客户端执行命令的时延变大。
对大Key进行的慢操作会导致后续的命令被阻塞,从而导致一系列慢查询。
导致实例流控。
对大Key高频率的读会使得实例出方向带宽被打满,导致流控,产生大量命令超时或者慢查询,业务受损。
导致主备倒换。
对大Key执行危险的DEL操作可能会导致主节点长时间阻塞,从而导致主备倒换。
热key
容易造成集群分片不均的情况。
造成热Key所在的分片有大量业务访问而同时其他的分片压力较低。这样不仅会容易产生单分片性能瓶颈,还会浪费其他分片的计算资源。
使得CPU冲高。
对热Key的大量操作可能会使得CPU冲高,如果表现在集群单分片中就可以明显地看到热Key所在的分片CPU使用率较高。这样会导致其他请求受到影响,产生慢查询,同时影响整体性能。业务量突增场景下甚至会导致主备切换。
易造成缓存击穿。
热Key的请求压力过大,超出Redis的承受能力易造成缓存击穿,即大量请求将被直接指向后端的数据库,导致数据库访问量激增甚至宕机,从而影响其他业务。
为了减少大Key和热Key过大,有什么使用建议?
- string类型控制在10KB以内,hash、list、set、zset元素尽量不超过5000。
- Key的命名前缀为业务缩写,禁止包含特殊字符(比如空格、换行、单双引号以及其他转义字符)。
- Redis事务功能较弱,不建议过多使用。
- 短连接性能差,推荐使用带有连接池的客户端。
- 如果只是用于数据缓存,容忍数据丢失,建议关闭持久化。
关于大key
进行大Key拆分。
分为以下几种场景:
- **该对象为String类型的大Key:**可以尝试将对象分拆成几个Key-Value, 使用MGET或者多个GET组成的pipeline获取值,分拆单次操作的压力,对于集群来说可以将操作压力平摊到多个分片上,降低对单个分片的影响。
- **该对象为集合类型的大Key,并且需要整存整取:**在设计上严格禁止这种场景的出现,因为无法拆分。有效的方法是将该大Key从Redis去除,单独放到其余存储介质上。
- **该对象为集合类型的大Key,每次只需操作部分元素:**将集合类型中的元素分拆。以Hash类型为例,可以在客户端定义一个分拆Key的数量N,每次对HGET和HSET操作的field计算哈希值并取模N,确定该field落在哪个Key上,实现上类似于Redis Cluster的计算slot的算法。
将大Key单独转移到其余存储介质。
无法拆分的大Key建议使用此方法,将不适用Redis能力的数据存至其它存储介质,如SFS或者其余NoSQL数据库,并在Redis中删除该大Key。
注意:禁止使用DEL直接删除大Key,可能会造成Redis阻塞,甚至主备倒换。
合理设置过期时间并对过期数据定期清理。
合理设置过期时间,避免历史数据在Redis中大量堆积。由于Redis的惰性删除策略,过期数据可能并不能及时清理,如果发现Redis过期Key清理较慢。
关于热key
使用读写分离。
如果热Key主要是读流量较大,则可以在客户端配置读写分离,降低对主节点的影响。还可以增加多个副本以满足读需求,但是备机较多也有相应的影响,DCS主备节点之间使用的是星型复制,即所有的备节点都直接和主节点保持同步,这样能保证备节点之间相互独立,且复制延迟较小。缺点是在备节点数量较多的情况下,主节点的CPU和网络负载会较高。
使用客户端缓存/本地缓存。
该方案需要提前了解业务的热点Key有哪些,设计客户端/本地和远端Redis的两级缓存架构,热点数据优先从本地缓存获取,写入时同时更新,这样能够分担热点数据的大部分读压力。缺点是需要修改客户端架构和代码,改造成本较高。
设计熔断/降级机制。
热Key极易造成缓存击穿,高峰期请求都直接透传到后端数据库上,从而导致业务雪崩。因此热Key的优化一定需要设计系统的熔断/降级机制,在发生击穿的场景下进行限流和服务降级,保护系统的可用性。
如何揪出来大key和热key
从Redis4.0开始,我们可以通过redis-cli的bigkeys和hotkeys参数查找大Key和热Key。
相关命令:
redis-cli --bigkeys
redis-cli --memkeys
redis-cli --hotkeys内存策略
[root@cs ~]# redis-cli config get maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"| 策略 | 描述 |
|---|---|
noeviction | 不淘汰键,当内存空间满后再进行写入会返回错误,不影响读操作。 |
allkeys-lru | 最近最少使用方式,当内存空间满后,会淘汰内存中最近最少使用的键,即最长时间未被访问的键,如果没有可删除的key,则退回到noeviction策略。 |
volatile-lru | 最近最少使用类似,不过是当内存空间满后,在设置了过期时间的键中淘汰最近最少使用的键. |
allkeys-lfu | 最近最不常用方式,当内存满后,会淘汰内存中被访问次数最少的键. |
volatile-lru | 和上面类似,当内存满后,在设置了过期时间的键中淘汰访问次数最少的键. |
allkeys-random | 当内存使用满后,随机淘汰内存中的键,它可以是任意的键,不管你是常使用的还是不常使用的. |
volatile-random | 类似,内存使用满后,在设置了过期时间的键中淘汰任意的键. |
volatile-ttl | 在设置了过期时间的键中,淘汰即将过期的键. |
