Redis 热 Key 和大 Key 问题解决方案

525 阅读5分钟

Redis 热 key

Redis 热 Key 问题首先是请求流量过大造成的,但是更深层次原因还是出现了流量倾斜,单个 Redis 实例承担的流量过大造成的。

热点发现

对于热 key 的发现,可以有两种方法:

  • 根据经验,提前评估出可能的热 key。
  • 借助一些外部计数工具实现热点发现。对请求的数据进行计数,根据配置的阈值判断请求是否会命中数据,是否触发热点Key。对于判断为热点的数据,主动的推送到前置缓存中。

采用主动发现的架构后,读服务接受到请求后仍然会默认从前置缓存获取数据,如获取到即直接返回。如未获取到,会穿透去查询后端缓存的数据并直接返回。但穿透获取到的数据并不会写入本地前置缓存。数据是否为热点是否要写入前置缓存均由计数工具来决定。此方案很好地解决了因误判断带来的延迟问题。

解决方案

  • 使用多级缓存:发现热 key 后,把热 key 加载到缓存中,可以缓存到浏览器CDN本地缓存中。

  • 热 key 拆分:把一个热 key 拆分成多个 key,每个 key 后面加一个后缀名如 hotkey#1、hotkey#2,然后把这些 key 分散到多个实例中,客户端请求时按照一定规则计算出一个固定的 key,这样多次请求就会被分散到不同的节点上了。

  • 针对热点数据访问的限流熔断等。

美团解决方案

美团万亿级 KV 存储架构与实践

美团基于 Redis Cluster 的自研 KV 系统 Squirrel 针对热点 Key 进行处理。

如下图所示,普通主、从是一个正常集群中的节点,热点主、从是游离于正常集群之外的节点。

当有请求进来读写普通节点时,节点内会同时做请求 Key 的统计。如果某个 Key 达到了一定的访问量或者带宽的占用量,会自动触发流控限制热点 Key 访问,防止节点被热点请求打满。同时,监控服务会周期性的去所有 Redis 实例上查询统计到的热点 Key。

如果有热点,监控服务会把热点 Key 所在 Slot 上报到迁移服务。迁移服务这时会把热点主从节点加入到热点集群中,然后把热点 Slot 迁移到这个热点主从上。因为热点集群的主从上只有热点 Slot 的请求,所以热点 Key 的处理能力得到了大幅提升。

通过这样的设计,可以做到实时的热点监控,并及时通过流控去止损;通过热点迁移,能做到自动的热点隔离和快速的容量扩充。

Redis大key问题

大 key 问题指的是 key 对应的 value 很大:

  • String 类型的值大于5MB。

  • Hash、List、Set、ZSet 类型的元素的个数超过 10000个。

大 key 带来的问题

大 key 问题会带来以下四种影响:

  • 客户端超时阻塞。Redis 执行命令是单线程处理,在处理大 key 问题会比较耗时,就会阻塞 Redis。

  • 网络阻塞。每次获取大 key 产生的网络流量较大。

  • 阻塞工作线程。如果使用 del 删除大 key 时,会阻塞工作线程。

  • 内存分布不均。 集群模型在 slot 分片均匀情况下,会出现数据和查询倾斜情况。

如何查找大 key?

  • redis-cli --bigkeys

  • 通过使用 Redis 的 SCAN 命令,我们可以逐步遍历数据库中的所有 Key。结合其他命令(如STRLEN、LLEN、SCARD、HLEN 等),我们可以识别出大 Key。SCAN 命令的优势在于它可以在不阻塞Redis实例的情况下进行遍历。

  • 使用第三方工具 RdbTools

如何处理大 key?

  • 分割 big key:将一个大 key 分割为多个小 key。例如,将一个含有上万字段数量的 Hash 按照一定策略(比如二次哈希)拆分为多个 Hash。
  • 压缩数据量:可以对大 key 的 value 数据进行压缩,降低存储空间,从而减少 Redis 的内存占用。例如美团在存储商家信息时,就使用 gzip 等压缩算法对商家信息进行压缩。
  • 将大 key 分散到不同的实例上,加快响应速度。
  • 采用合适的数据结构:例如,文件二进制数据不使用 String 保存、使用 HyperLogLog 统计页面 UV、Bitmap 保存状态信息(0/1)。
  • 开启 lazy-free(惰性删除/延迟释放) :lazy-free 特性是 Redis 4.0 开始引入的,指的是让 Redis 采用异步方式延迟释放 key 使用的内存,将该操作交给单独的子线程处理,避免阻塞主线程。

在删除大 key 时,不能使用 del 命令,由于 redis 是单线程的,会导致阻塞。应该使用 UNLINK 命令来异步删除一个或多个指定的 key。

UNLINKE 命令是异步执行的,它会立即返回,将要删除的 key 标记为删除状态,并在后台线程中删除 key 对应的 value 和物理存储。因此,UNLINK 命令不会阻塞 Redis 服务器的主线程,可以提高 Redis 的性能和吞吐量。