背景
Redis中间件,我们主要是用来做缓存,缓解数据库的访问压力,我们搭建的是redis集群
在一个风和日丽的下午,突然收到运维的报警信息
运维:小李,你们使用的redis中间件所在的服务器,有大量的流量流出,宽带快要占满了,网卡都冒烟了,严重影响其他服务,快速排查解决下,如果一时半会解决不了,我们只能kill掉redis的进程,避免影响其他服务
小李:我们立刻排查,有需要协助的,请你们帮忙
我们redis使用的是集群,三主三从,怎么才有一台流量这么大?大概率我估计是遇到了大key、热key,大key是存储的数据量大,热key访问频率高,分布还不均匀,才能导致单台机器流量非常大
先介绍下 热key、大key,让大家有个了解
问题的严重性
在使用Redis的过程中,如果未能及时发现并处理大Key与热Key,可能会导致服务性能下降、用户体验变差,甚至引发大面积故障。
大Key和热Key的定义
什么是BigKey
通常以Key的大小和Key中成员的数量来综合判定,例如:
- Key本身的数据量过大:一个String类型的Key,它的值为5 MB。
- Key中的成员数过多:一个ZSET类型的Key,它的成员数量为10,000个。
- Key中成员的数据量过大:一个Hash类型的Key,它的成员数量虽然只有1,000个但这些成员的Value(值)总大小为100 MB。
什么是热key
通常以其接收到的Key被请求频率来判定,例如:
- QPS集中在特定的Key:Redis实例的总QPS(每秒查询率)为10,000,而其中一个Key的每秒访问量达到了7,000。
- 带宽使用率集中在特定的Key:对一个拥有上千个成员且总大小为1 MB的HASH Key每秒发送大量的HGETALL操作请求。
- CPU使用时间占比集中在特定的Key:对一个拥有数万个成员的Key(ZSET类型)每秒发送大量的ZRANGE操作请求。
上述例子中的具体数值仅供参考,在实际业务中,您需要根据Redis的实际业务场景进行综合判断。
大Key和热Key引发的问题
大key
- 客户端执行命令的时长变慢。
- Redis内存达到maxmemory参数定义的上限引发操作阻塞或重要的Key被逐出,甚至引发内存溢出(Out Of Memory)。
- 集群架构下,某个数据分片的内存使用率远超其他数据分片,无法使数据分片的内存资源达到均衡。
- 对大Key执行读请求,会使Redis实例的带宽使用率被占满,导致自身服务变慢,同时易波及相关的服务。
- 对大Key执行删除操作,易造成主库较长时间的阻塞,进而可能引发同步中断或主从切换。
热key
- 占用大量的CPU资源,影响其他请求并导致整体性能降低。
- 集群架构下,产生访问倾斜,即某个数据分片被大量访问,而其他数据分片处于空闲状态,可能引起该数据分片的连接数被耗尽,新的连接建立请求被拒绝等问题。
- 在抢购或秒杀场景下,可能因商品对应库存Key的请求量过大,超出Redis处理能力造成超卖。
- 热Key的请求压力数量超出Redis的承受能力易造成缓存击穿,即大量请求将被直接指向后端的存储层,导致存储访问量激增甚至宕机,从而影响其他业务。
大Key和热Key产生的原因
未正确使用Redis、业务规划不足、无效数据的堆积、访问量突增等都会产生大Key与热Key
大key
- 不适用的场景下使用Redis,易造成Key的value过大,如使用String类型的Key存放大体积二进制文件型数据;
- 业务上线前规划设计不足,没有对Key中的成员进行合理的拆分,造成个别Key中的成员数量过多;
- 未定期清理无效数据,造成如HASH类型Key中的成员持续不断地增加;
- 使用LIST类型Key的业务消费侧发生代码故障,造成对应Key的成员只增不减。
热key
预期外的访问量陡增,如突然出现的爆款商品、访问量暴涨的热点新闻、直播间某主播搞活动带来的大量刷屏点赞、游戏中某区域发生多个工会之间的战斗涉及大量玩家等。
我们的事故案例
通过上面的介绍,应该对大key、热key,有个了解了,那下面介绍下,我们排查问题的过程
找出热key、大key
思路一
redis4.0版本以上提供了分析大key、热key的分析工具,前提是:内存策略修改为LFU
算法
# 分析统计热key
redis-cli --hotkeys
# 分析统计大key
redis-cli --bigkeys
以上两个命令,就可以分析线出线上的热key、大key。也是通过 scan 完成的,可能会对节点造成阻塞,同时bigkeys只能计算每种数据结构的 top1,如果有些数据结构有比较多的 Bigkey是查找不出来的,如下:
此方案暂时放弃,主要怕对节点造成阻塞
思路二
从业务角度出发,我们的系统有链路跟踪,使用的是pinpoint,通过pinpoint查看最近这段时间请求量高、响应慢的接口,找出top靠前的接口,找到接口对应的代码,进行分析,查找这些接口哪里调用了redis,使用的key,然后对key进行查看
通过接口代码分析,发现其中一个接口,for循环里面调用redis,获取值,伪代码如下:
public List getData(){
/**
* 1、通过条件分页查询数据
* 2、对查询出来的数据,for循环去查询redis 的数据,然后对查询出来的数据,进行转换,再组装数据
*
*/
//查询业务数据
List<AccessLogEntity> list = new ArrayList<>();
for (AccessLogEntity accessLog : list) {
String str = redisTemplate.opsForValue().get("key");
//json字符转换为对象
//对list的数据进行匹配 关联
// 组装数据
}
return list;
}
代码分析:
- key是固定的,应该放在循环外面调用一次,遍历的时候直接使用即可,这种人为的造成了热key
- 对key进行分析,这个key到底多大,连接上reids 使用命令 MEMORY usage key 查看大小,结果发现这个key竟然有100Mb,再看这个key值的写入,是整张表的数据直接存在redis中,这是redis当数据库用了,人才啊真是人才,人为制造大key
- 根据业务场景,也不应该使用String,这种场景 使用hash是比较适合的
解决方案
临时解决线上问题
- 把获取调用redis的获取值的方法,放在循环体外,减少请求量
- redis获取的数据,放在本地缓存(比如:使用Map),持续减少请求量
通过以上两个修改,发布线上,持续观察,运维反馈:流量降下来了,服务正常了
后续优化
通过项目代码分析,这并不存在热key,是人为放在分页循环里面导致的,所以不考虑热key的问题,只需要把大key优化掉即可
我们使用hash替换String,并且key拆分为多个hash key,并确保每个Key的成员数量在合理范围
通过这个事故,也让我深究大key、热key的解决方案
优化大Key与热Key
大key优化方案
-
对大Key进行拆分
例如将含有数万成员的一个HASH Key拆分为多个HASH Key,并确保每个Key的成员数量在合理范围。在Redis集群架构中,拆分大Key能对数据分片间的内存平衡起到显著作用。
-
对大Key进行清理
将不适用Redis能力的数据存至其它存储,并在Redis中删除此类数据。
-
对过期数据进行定期清理
堆积大量过期数据会造成大Key的产生,例如在HASH数据类型中以增量的形式不断写入大量数据而忽略了数据的时效性。可以通过定时任务的方式对失效数据进行清理。
-
监控Redis的内存水位
定期对redis内存分析redis内存可视化分析,这里使用的是rdb,redis内存大于5G,建议使用redis-rdb-tools,根据自己的业务实际情况对大key的定义,做二次开发,超过设置的阈值,就报警提醒
热key优化方案
-
在Redis集群架构中对热Key进行复制
在Redis集群架构中,由于热Key的迁移粒度问题,无法将请求分散至其他数据分片,导致单个数据分片的压力无法下降。此时,可以将对应热Key进行复制并迁移至其他数据分片,例如将热Key foo复制出3个内容完全一样的Key并名为foo2、foo3、foo4,将这三个Key迁移到其他数据分片来解决单个数据分片的热Key压力。
-
使用本地二级缓存
当出现热
Key
以后,把热Key
加载到系统的JVM
中。后续针对这些热Key
的请求,会直接从JVM
中获取,而不会走到Redis
层。这些本地缓存的工具很多,比如Ehcache
,或者Google Guava
中Cache
工具,或者直接使用HashMap
作为本地缓存工具都是可以的。
这里有两个缺点:
- 如果对热
Key
进行本地缓存,需要防止本地缓存过大,影响系统性能; - 需要处理本地缓存和
Redis
集群数据的一致性问题。
统一解决方案
京东hotkey
对任意突发性的无法预先感知的热点数据,包括并不限于热点数据(如突发大量请求同一个商品)、热用户(如恶意爬虫刷子)、热接口(突发海量请求同一个接口)等,进行毫秒级精准探测到。然后对这些热数据、热用户等,推送到所有服务端JVM内存中,以大幅减轻对后端数据存储层的冲击,并可以由使用者决定如何分配、使用这些热key(譬如对热商品做本地缓存、对热用户进行拒绝访问、对热接口进行熔断或返回默认值)。这些热数据在整个服务端集群内保持一致性,并且业务隔离
写作不易,刚好你看到,刚好对你有帮助,麻烦点点赞,有疑问的欢迎留言或者私信讨论。