深入探讨得物 Java 面试中 Redis 大 Key 的潜在危害、识别方法与优化策略

这篇文章基于一位同学在得物 Java 一面时遇到的 Redis 大 Key 相关问题,提供了全面的面试经验分享。

图片在面试过程中,关于大 Key 的问题是相对常见的,特别是在考察 Redis 性能优化方面的知识时。

通常,提到大 Key 后面还会接着询问热 Key(Hot Key)。即使不准备面试,也建议花时间学习这部分内容,因为在实际开发中同样适用。

大 Key(Big Key)定义

简单来说,当一个 key 对应的 value 占用的内存较大时,就可视作大 Key。具体来说,什么样的大小才算大呢?以下是一个大致的参考标准:

  • 字符串类型(String)的 value 超过 1MB。
  • 复合类型(如 List、Hash、Set、Sorted Set 等)的 value 中的元素数量超过 5000 个(不过对于复合类型,元素数量并不一定直接影响内存占用)。

图片

大 Key 判定标准

大 Key 的成因及其影响

大 Key 的产生通常与以下原因有关:

  • 程序设计不当,例如直接使用字符串类型存储较大文件的二进制数据。
  • 对业务数据规模的考虑不足,比如使用集合类型时未预估数据量的快速增长。
  • 未及时清理无用的数据,例如哈希中包含了大量冗余的键值对。

大 Key 除了会消耗更多的内存与带宽外,还会对性能产生显著影响。

在 Redis 常见阻塞原因总结[1]一文中提到,大 Key 可能导致阻塞问题,主要体现为以下三个方面:

  1. 客户端超时阻塞:Redis 是单线程处理命令,操作大 Key 时耗时较长,可能导致客户端长时间无响应。
  2. 网络阻塞:获取大 Key 时产生的网络流量较大,例如一个 1 MB 的 Key 每秒访问 1000 次,会产生 1000MB 的流量,这会对普通千兆网络的服务器造成影响。
  3. 工作线程阻塞:删除大 Key 时(使用 del 命令),可能会阻塞工作线程,从而导致后续命令无法执行。

大 Key 导致的阻塞问题还会进一步对主从同步和集群扩容产生影响。

综上所述,我们应该尽量避免在 Redis 中存在大 Key,以减少潜在问题的发生。

如何识别大 Key?

1. 使用 Redis 自带的 --bigkeys 参数进行查找。

# redis-cli -p 6379 --bigkeys  

# 扫描整个 keyspace 查找最大 keys 以及每种 key 类型的平均大小。可以通过 -i 0.1 设定每 100 次 SCAN 命令间的休息时间(通常不需要)。
  
[00.00%] 找到的最大字符串 '"ballcat:oauth:refresh_auth:f6cdb384-9a9d-4f2f-af01-dc3f28057c20"' 占用 4437 字节  
[00.00%] 找到的最大列表 '"my-list"' 包含 17 项  
  
-------- 总结 -------  

在 keyspace 中抽样了 5 个 keys!  
总 key 长度为 264 字节(平均长度 52.80)  

最大列表 '"my-list"' 包含 17 项  
最大字符串 '"ballcat:oauth:refresh_auth:f6cdb384-9a9d-4f2f-af01-dc3f28057c20"' 占用 4437 字节  
1 个列表包含 17 项(20.00% 的 keys,平均大小 17.00)  
0 个哈希表包含 0 字段(0.00% 的 keys,平均大小 0.00)  
4 个字符串占用 4831 字节(80.00% 的 keys,平均大小 1207.75)  
0 个流包含 0 条目(0.00% 的 keys,平均大小 0.00)  
0 个集合包含 0 成员(0.00% 的 keys,平均大小 0.00)  
0 个有序集合包含 0 成员(0.00% 的 keys,平均大小 0.00)  

通过上述命令运行结果可以得知,命令会扫描 Redis 中所有的 keys,对于 Redis 性能会有一定影响。此外,此方法只能找出每种数据结构中占用内存最大的一个大 Key,而一个 Key 的元素多并不意味着其内存占用也多,需结合具体业务情况进一步判断。

在生产环境运行该命令时,为降低对 Redis 的影响,可以指定 -i 参数控制扫描频率。redis-cli -p 6379 --bigkeys -i 3 表示每次扫描后休息 3 秒。

2. 利用 Redis 自带的 SCAN 命令

SCAN 命令可以根据特定模式和数量返回匹配的 keys。获取 keys 后,可以利用 STRLENHLENLLEN 等命令返回其长度或成员数量。

数据结构命令复杂度结果(对应 key)
字符串STRLENO(1)字符串值的长度
哈希表HLENO(1)哈希表中字段的数量
列表LLENO(1)列表元素数量
集合SCARDO(1)集合元素数量
有序集合ZCARDO(1)有序集合的元素数量

对于集合类型,还可以使用 MEMORY USAGE 命令(Redis 4.0 及以上版本),该命令返回键值对占用的内存空间。

3. 借助开源工具分析 RDB 文件。

通过分析 RDB 文件寻找大 Key,这种方式的前提是 Redis 使用 RDB 持久化。

网上有现成的工具可供直接使用:

  • redis-rdb-tools[2]:用 Python 编写,用于分析 Redis 的 RDB 快照文件。
  • rdb_bigkeys[3] : 使用 Go 语言编写,更高效的工具用于分析 Redis 的 RDB 快照文件。

4. 借助公有云的 Redis 分析服务。

如果使用公有云的 Redis 服务,务必查看其是否提供键分析功能(一般都会提供)。

以下以阿里云 Redis 为例,该服务支持大 Key 的实时分析与发现,相关文档地址:阿里云文档

图片

阿里云 Key 分析

大 Key 的处理策略

大 Key 的常见处理与优化方案如下(可结合使用):

  • 分割大 Key:将一个大 Key 拆分为多个小 Key。例如,将含有上万个字段的 Hash 按一定策略(如二次哈希)拆分成多个 Hash。
  • 手动清理:Redis 4.0 及以上版本可以使用 UNLINK 命令异步删除一个或多个指定的 Key。低于 4.0 的版本可考虑结合 SCAN 命令与 DEL 命令进行分批删除。
  • 采用适当的数据结构:例如,文件二进制数据不应使用字符串存储,而应使用 HyperLogLog 或 Bitmap 等适合的结构来存储状态信息(0/1)。
  • 开启惰性删除(Lazy-Free)特性:从 Redis 4.0 开始引入的惰性删除特性,允许 Redis 通过异步方式延迟释放 Key 占用的内存,该操作由独立的子线程处理,从而避免主线程的阻塞。

更多关于 Redis 的面试问题,欢迎访问 JavaGuide 在线网站(javaguide.cn)阅读相关文章。

参考资料

redis-rdb-tools: https://github.com/sripathikrishnan/redis-rdb-tools

rdb_bigkeys: https://github.com/weiyanwei412/rdb_bigkeys