深入探讨得物 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 可能导致阻塞问题,主要体现为以下三个方面:
- 客户端超时阻塞:Redis 是单线程处理命令,操作大 Key 时耗时较长,可能导致客户端长时间无响应。
- 网络阻塞:获取大 Key 时产生的网络流量较大,例如一个 1 MB 的 Key 每秒访问 1000 次,会产生 1000MB 的流量,这会对普通千兆网络的服务器造成影响。
- 工作线程阻塞:删除大 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 后,可以利用 STRLEN
、HLEN
、LLEN
等命令返回其长度或成员数量。
数据结构 | 命令 | 复杂度 | 结果(对应 key) |
---|---|---|---|
字符串 | STRLEN | O(1) | 字符串值的长度 |
哈希表 | HLEN | O(1) | 哈希表中字段的数量 |
列表 | LLEN | O(1) | 列表元素数量 |
集合 | SCARD | O(1) | 集合元素数量 |
有序集合 | ZCARD | O(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