监控发现Redis Cluster中某个节点的内存使用率远高于其他节点(发生数据倾斜),如何排查并解决?
在Redis Cluster中发现某个节点内存使用率远高于其他节点(即发生数据倾斜或内存倾斜),通常是由以下几个原因引起的:Slot(槽位)分配不均、存在大Key(Big Key)、Hash Tag滥用、或者非数据内存(如客户端缓冲区、内存碎片)占用过大。
以下是标准的排查步骤和对应的解决方案:
第一阶段:排查原因 (Troubleshooting)
1. 检查 Slot (槽位) 分配是否均匀
Redis Cluster将数据分布在16384个Slot中。如果某个节点分配的Slot数量远多于其他节点,自然会导致内存占用更高。
- 排查命令:bash
redis-cli -c -h <ip> -p <port> cluster nodes # 或者 redis-cli --cluster info <ip>:<port> - 观察:看每个Master节点负责的Slot数量是否大致相同(例如3个Master,每个应该在5461个左右)。
2. 检查 Key 的数量是否均匀
如果Slot分配是均匀的,但某个节点的Key数量远多于其他节点。
- 排查命令:
登录到各个Master节点,执行:bashINFO keyspace - 观察:对比各个节点的
keys=后面的数字。如果某个节点Key数量特别多,大概率是使用了 Hash Tag。 - Hash Tag 确认:检查业务代码,看是否大量使用了带有
{}的Key(例如user:{1001}:info,user:{1001}:orders)。Hash Tag 会强制把{}内相同字符串的Key路由到同一个Slot,导致某个Slot(进而导致某个节点)数据爆满。
3. 检查是否存在大 Key (Big Key)
如果Slot数量均匀,Key的总数也大致均匀,但内存倾斜严重,最常见的原因就是存在 Big Key(例如某个Hash、List、Set或Zset中包含了数百万个元素,或者某个String特别大)。
- 排查方法 A:Redis自带工具 (对性能有轻微影响,建议低峰期执行)bash
redis-cli -h <倾斜节点的IP> -p <port> --bigkeys - 排查方法 B:离线分析 RDB 文件 (最安全推荐)
使用redis-rdb-tools或RedisInsight等工具,解析倾斜节点的.rdb备份文件,可以直观地导出内存占用最大的Key排行。bashrdb -c memory dump.rdb > memory.csv
4. 检查非数据内存占用 (缓冲区、内存碎片)
如果以上都没问题,可能是非数据本身占用的内存。
- 客户端输出缓冲区 (Client Output Buffer):是否有消费极慢的客户端(如执行了大量的
MONITOR、KEYS *,或Pub/Sub消费慢),导致缓冲区堆积?bashINFO clients # 关注 client_recent_max_output_buffer 和 client_recent_max_input_buffer CLIENT LIST # 查看具体的 omem (输出缓冲区占用) - 内存碎片 (Fragmentation):节点频繁更新/删除数据,导致内存碎片率过高。bash
INFO memory # 关注 mem_fragmentation_ratio,如果大于 1.5 说明碎片比较严重
第二阶段:解决方案 (Resolution)
根据上述排查出的根因,采取相应的解决措施:
场景 1:Slot 分配不均
解决办法:重新平衡集群的 Slot(Resharding)。
可以使用 Redis 官方工具重新分配 Slot,将倾斜节点上的 Slot 迁移一部分到空闲节点。
bash
# 自动平衡集群的 slot
redis-cli --cluster rebalance <ip>:<port> --cluster-use-empty-masters
# 或者手动指定迁移
redis-cli --cluster reshard <ip>:<port>
场景 2:存在大 Key (Big Key)
解决办法:
- 业务拆分:联系研发,将大Key拆分成多个小Key。例如,一个包含1000万字段的Hash,通过哈希取模拆分成10个或100个Hash (
hash:01,hash:02...),这样它们就会分散到不同的Slot中。 - 安全清理:如果大Key是废弃数据,千万不要直接用
DEL删除(会阻塞Redis),应使用UNLINK异步删除;如果是大Hash/Set,建议通过脚本使用HSCAN/SSCAN逐步HDEL/SREM删除。
场景 3:Hash Tag 滥用
解决办法:
- 评估业务逻辑:研发团队需要评估是否真的需要在一个事务中操作这些Key(只有需要用到事务或Lua脚本保证原子性时,才必须把Key放在同一个Slot)。
- 去除/修改 Hash Tag:如果不需要事务支持,去掉
{},让Key根据自身的完整名字自然分散到各个Slot去。
场景 4:客户端缓冲区堆积
解决办法:
- 杀掉导致堆积的异常客户端连接 (
CLIENT KILL)。 - 修改
redis.conf中的配置,限制输出缓冲区大小,强制断开慢客户端:plaintextclient-output-buffer-limit normal 0 0 0 client-output-buffer-limit replica 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 - 排查业务侧是否有慢查询或拉取全量数据的操作(如滥用
HGETALL等)。
场景 5:内存碎片过高
解决办法:
- 开启自动碎片清理(Redis 4.0+ 支持):bash
CONFIG SET activedefrag yes - 重启节点:如果版本较低或碎片清理太慢,可以通过主从切换(Failover),将倾斜节点变为Slave,然后重启该节点,重启时会重新加载RDB,内存碎片自然消除。
第三阶段:预防建议 (Best Practices)
- 开发规范:建立严格的 Redis 开发规范,禁止在生产环境产生超过 10MB 的 String,或元素超过 10000 个的 Hash/Set/List/Zset;严格审批 Hash Tag 的使用。
- 监控告警:在 Prometheus/Grafana 等监控系统中,配置集群各节点内存使用率的方差告警(当某节点内存使用率偏离平均值超过20%时触发告警)。
- 定期巡检:定期执行离线 RDB 分析,提前发现有增长趋势的潜在大 Key,防患于未然。