Redis hot key 发现以及解决办法
# 热 Key 带来问题
所谓热key问题就是,突然有几十万的请求去访问redis上的某个特定key。那么,这样会造成流量过于集中,达到物理网卡上限,从而导致这台redis的服务器宕机。
由于某个 Key
的数据一定是存储到后端某台服务器的 Redis
单个实例上,如果对这个 Key
突然出现大量的请求操作,这样就会造成流量过于集中,达到 Redis
单个实例处理上限,可能会导致 Redis
实例 CPU
使用率 100%
,或者是网卡流量达到上限等,对系统的稳定性和可用性造成影响,或者更为严重出现服务器宕机,无法对外提供服务;更有甚者在出现 Redis
服务不可用之后,大量的数据请求全部落地数据库查询上,Redis
都已经顶不住了,数据库也是分分钟挂掉的节奏。
- 流量集中,达到服务器处理上限(
CPU
、网络IO
等); - 会影响在同一个
Redis
实例上其他Key
的读写请求操作; - 热
Key
请求落到同一个Redis
实例上,无法通过扩容解决; - 大量
Redis
请求失败,查询操作可能打到数据库,拖垮数据库,导致整个服务不可用。
# 如何发现热 Key
# 凭借业务经验,预估热 Key 出现
根据业务系统上线的一些活动和功能,我们是可以在某些场景下提前预估热 Key
的出现的,比如业务需要进行一场商品秒杀活动,秒杀商品信息和数量一般都会缓存到 Redis
中,这种场景极有可能出现热 Key
问题的。
- 优点:简单,凭经验发现热
Key
,提早发现提早处理; - 缺点:没有办法预测所有热
Key
出现,比如某些热点新闻事件,无法提前预测。
# 客户端进行收集
一般我们在连接 Redis
服务器时都要使用专门的 SDK(比如:Java
的客户端工具 Jedis
、Redisson
),我们可以对客户端工具进行封装,在发送请求前进行收集采集,同时定时把收集到的数据上报到统一的服务进行聚合计算。
- 优点:方案简单
- 缺点:
- 对客户端代码有一定入侵,或者需要对
SDK
工具进行二次开发; - 没法适应多语言架构,每一种语言的
SDK
都需要进行开发,后期开发维护成本较高。
- 对客户端代码有一定入侵,或者需要对
# 在代理层进行收集
如果所有的 Redis
请求都经过 Proxy
(代理)的话,可以考虑改动 Proxy
代码进行收集,思路与客户端基本类似。
hotkeys 参数
Redis
在 4.0.3
版本中添加了 hotkeys (opens new window) 查找特性,可以直接利用 redis-cli --hotkeys
获取当前 keyspace
的热点 key
,实现上是通过 scan + object freq
完成的。
monitor
命令可以实时抓取出 Redis
服务器接收到的命令,通过 redis-cli monitor
抓取数据,同时结合一些现成的分析工具,比如 redis-faina (opens new window),统计出热 Key。
# Redis 节点抓包分析
Redis
客户端使用 TCP
协议与服务端进行交互,通信协议采用的是 RESP
协议。自己写程序监听端口,按照 RESP
协议规则解析数据,进行分析。或者我们可以使用一些抓包工具,比如 tcpdump
工具,抓取一段时间内的流量进行解析。
# 热 Key 问题解决方案
# 增加 Redis 实例复本数量
对于出现热 Key
的 Redis
实例,我们可以通过水平扩容增加副本数量,将读请求的压力分担到不同副本节点上。
# 二级缓存(本地缓存)
当出现热 Key
以后,把热 Key
加载到系统的 JVM
中。后续针对这些热 Key
的请求,会直接从 JVM
中获取,而不会走到 Redis
层。这些本地缓存的工具很多,比如 Ehcache
,或者 Google Guava
中 Cache
工具,或者直接使用 HashMap
作为本地缓存工具都是可以的。
使用本地缓存需要注意两个问题:
- 如果对热
Key
进行本地缓存,需要防止本地缓存过大,影响系统性能; - 需要处理本地缓存和
Redis
集群数据的一致性问题。
# 热 Key 备份
通过前面的分析,我们可以了解到,之所以出现热 Key
,是因为有大量的对同一个 Key
的请求落到同一个 Redis
实例上,如果我们可以有办法将这些请求打散到不同的实例上,防止出现流量倾斜的情况,那么热 Key
问题也就不存在了。
那么如何将对某个热 Key
的请求打散到不同实例上呢?我们就可以通过热 Key
备份的方式,基本的思路就是,我们可以给热 Key
加上前缀或者后缀,把一个热 Key
的数量变成 Redis
实例个数 N
的倍数 M
,从而由访问一个 Redis
Key
变成访问 N * M
个 Redis
Key
。 N * M
个 Redis
Key
经过分片分布到不同的实例上,将访问量均摊到所有实例。
在这段代码中,通过一个大于等于 1
小于 M
的随机数,得到一个 bakHotKey
,程序会优先访问 bakHotKey
,在得不到数据的情况下,再访问原来的 hotkey
,并将 hotkey
的内容写回 bakHotKey
。值得注意的是,bakHotKey
的过期时间是 hotkey
的过期时间加上一个较小的随机正整数,这是通过坡度过期的方式,保证在 hotkey
过期时,所有 bakHotKey
不会同时过期而造成缓存雪崩。
使用京东框架发现热key,https://gitee.com/jd-platform-opensource/hotkey
# 总结
在这一篇文章中我们首先分析了在 Redis
中热 Key
带来的一些问题,同时也介绍了在海量的 Redis
Key
中找到热 Key
的一些方法,最后也提到了在解决热 Key
问题中我们常用的一些办法;总结来说,Redis
热 Key
问题首先是请求流量过大造成的,但是更深层次原因还是出现了流量倾斜,单个 Redis
实例承担的流量过大造成的,了解到了本质原因,解决的思路也就简单了,就是要想尽一切办法将单个实例承担的流量打散,让每个机器均衡承担热 Key
的流量,不要出现流量倾斜,保证系统的稳定性。