文章目录

切片集群把数据均匀打散到多个节点,理论上能让每个节点的负载差不多。但生产环境经常出现这样的情况:明明用了集群,某个节点还是被打爆了,其他节点却很闲。这就是数据倾斜——数据或访问没有均匀分布。倾斜分两种:数据量倾斜和数据访问倾斜,原因和应对方式都不一样。
数据量倾斜

数据量倾斜指某个或某几个节点存的数据明显多于其他节点。原因主要有三个。
Bigkey 导致的倾斜
某个 key 本身的 value 极大——一个 List 几百万元素、一个 Hash 几百万 field、一个 Set 几亿成员。这种 key 不管路由到哪个节点,都会把那个节点占满。
定位 bigkey:
redis-cli --bigkeys
# 扫描所有 key,输出每种类型最大的几个
或者用 MEMORY USAGE 命令查看具体 key 的内存占用:
MEMORY USAGE user:profile:big_one
应对方式:拆分 bigkey。
原 key: user:followers:1001 = Set(1000万成员)
拆分后: user:followers:1001:0 = Set(100万)
user:followers:1001:1 = Set(100万)
... 拆成 10 份
读取时按规则去多个 key 聚合。代价是逻辑变复杂、跨节点查询多了,但解决了数据倾斜的根本问题。
Hash Tag 滥用
Redis Cluster 支持 Hash Tag——key 中 {} 内的部分用于计算槽位:
user:{1001}:profile → CRC16("1001") → 槽 X
user:{1001}:orders → CRC16("1001") → 槽 X

这能把同一用户的多个 key 聚到同一节点,方便做跨 key 操作。但如果大量 key 用了相同的 Hash Tag,所有这些 key 都会落到同一个槽、同一个节点。
最常见的错误是用业务前缀做 Hash Tag:
order:{shop_1}:1001
order:{shop_1}:1002
order:{shop_1}:1003
... 该店铺所有订单全到一个节点
如果店铺 1 是大客户,几百万订单全压在一个节点上。
应对方式:只在确实需要原子操作的 key 上用 Hash Tag,不要随意加。
槽分配不均
集群初始化或扩容时,如果手工分配槽,可能没分均匀。比如 4 个节点:
节点 A: 0-8000 (8000 个槽)
节点 B: 8001-12000 (4000 个槽)
节点 C: 12001-14000 (2000 个槽)
节点 D: 14001-16383 (2000 个槽)
A 节点承载的槽数是 D 的 4 倍,数据量自然倾斜。
应对方式:用 redis-cli --cluster rebalance 或 cluster reshard 重新均匀分配。
数据访问倾斜
数据量分布均匀,访问量却不均匀。某个 key 被频繁访问,导致它所在的节点 CPU、网络打满,而其他节点很闲。这就是热 key 问题。

热 key 的来源
- 明星动态:某条微博突然爆火。
- 热销商品:双 11 的爆款。
- 热点事件:突发新闻、明星八卦。
- 抢购页面:限时优惠。
热 key 往往是业务侧无法预测的。
定位热 key
# Redis 4.0+ 支持
redis-cli --hotkeys
# 需要 maxmemory-policy 设为 allkeys-lfu 或 allkeys-lru
也可以通过 MONITOR 抽样观察请求分布,但 MONITOR 性能开销大,慎用。
应对方式:本地缓存
热 key 的访问大部分是只读的。在应用层加本地缓存(如 Caffeine、Guava Cache),让大部分请求在应用本地就返回,不走 Redis:
LoadingCache<String, String> localCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.SECONDS)
.build(key -> redis.get(key));
短 TTL(几秒)能保证一致性,长 TTL 能更好挡流量。具体值看业务容忍。
应对方式:热 key 副本
把热 key 复制到多个 key,分散到多个节点:
原 key: hot_news_1001
副本: hot_news_1001:0, hot_news_1001:1, ..., hot_news_1001:N
读取时随机选一个副本:
copy_id = random.randint(0, N)
key = f"hot_news_1001:{copy_id}"
写入时所有副本都更新(牺牲一致性换取读性能)。
这个方案适合读多写少的热 key。如果是写多读少的(比如计数器),副本方案就不行了。
应对方式:客户端缓存(Redis 6.0)
Redis 6.0 引入了 Tracking 机制,服务端在 key 变更时主动通知客户端失效。客户端可以放心做本地缓存,不用担心读到旧数据。
CLIENT TRACKING ON
GET hot_news_1001
# 服务端会记住这个客户端读过这个 key
# key 变更时主动通知失效
这是优雅的方案,但需要客户端库支持。
数据倾斜的预防
事后处理不如事前预防。架构设计阶段就要考虑:
- 避免单一维度的 key 设计:不要让所有 key 都按一个 ID 分布。
- 慎用 Hash Tag:只在跨 key 原子操作时用,不要为了"看起来整齐"加。
- 预估数据规模:业务上线前估算每个 key 的数据量,超过阈值的提前拆。
- 监控告警:每个节点的内存、QPS、CPU 都要监控,倾斜要及时发现。
实践建议

- 定期扫 bigkey:
--bigkeys命令成本不高,可以每周运行一次。 - bigkey 拆分要趁早:等到把节点压垮再拆,要做的工作多得多。
- Hash Tag 只用在必要的地方:默认不要加,要加之前问清楚为什么。
- 热 key 做本地缓存:成本最低收益最大的方案。
- 关键业务做读副本:电商首页的爆款商品、社交平台的热门内容。
- 集群扩容时重新均衡:扩容只是加节点,不会自动迁移数据,必须手动 rebalance。
数据倾斜是切片集群必然会遇到的问题。理解倾斜的两种来源——数据量和访问量——才能对症下药。bigkey 拆分、Hash Tag 谨慎使用、热 key 副本和本地缓存,构成了应对数据倾斜的工具箱。把这些手段用对,集群才能真正发挥"水平扩展"的价值。

307

被折叠的 条评论
为什么被折叠?



