30. 键分区与扩展性设计
本教程共 40 篇 · 第 30 篇 · 更新于 2026-08-02
30. 键分区与扩展性设计
当单台 Redis 的内存或算力触顶,下一步不是换更大的机器,而是把数据拆分到多台实例上——这就是分区(Partitioning)。每个实例只保存全部键的一个子集,于是总的可用内存 = 各实例内存之和,总的算力和网络带宽也随之线性扩展。
分区是「扩展性」的根本手段。第 28、29 章的 Redis Cluster 本质上就是一种服务端自动分区方案;本章码上学从更通用的视角,讲清分区的几种实现方式、关键算法,以及它带来的代价与坑。
本节目标
- 理解分区的价值与两种基本策略:范围分区、哈希分区。
- 了解三种实现位置:客户端分区、代理分区、集群(服务端)分区。
- 理解一致性哈希与预分片(presharding)如何解决「扩缩容时大规模重映射」问题。
- 认识分区的代价(多键限制、大 key 不可分片等)与 bigkey 风险。
两种基本分区策略
假设有 4 个 Redis 实例 R0~R3,要把 user:1、user:2……这样的键分散到不同实例。
范围分区(Range Partitioning)
按「键的取值范围」直接映射。例如:
- ID 在 0~10000 的用户 → R0
- ID 在 10001~20000 的用户 → R1
- 以此类推。
优点是人容易理解、区间查询方便;缺点是必须维护一张「范围 → 实例」的映射表,且数据容易分布不均(某些范围特别热)。实际中除非业务本身就有天然区间,否则很少单独用。
哈希分区(Hash Partitioning)
对任意键先用哈希函数转成数字,再取模到实例数:
实例编号 = hash(key) mod N
官方示例用 crc32:crc32("foobar") = 93024922,93024922 mod 4 = 2,于是 foobar 落到 R2。这种方式与键的内容形态无关,分布通常更均匀,是分区的主流选择。
在集群模式下,这个公式被具体化为 CRC16(key) mod 16384 得到「槽」,再由槽映射到节点(详见第 28 章)。你可以用 redis-cli 验证键的归属:
127.0.0.1:7000> CLUSTER KEYSLOT user:1
(integer) 8108
三种实现位置
1. 客户端分区(Client-side Partitioning)
由应用代码自己决定键去哪个实例——直接在客户端计算 hash(key) mod N 后选连接。简单、无中间层,但扩容时要改客户端逻辑,且多语言客户端需各自实现,一致性难保证。
2. 代理分区(Proxy-based Partitioning)
在客户端与 Redis 之间加一层代理(如 Twemproxy、Codis)。客户端只连代理,由代理负责把请求路由到正确实例。对应用透明、集中管控,代价是多一跳延迟和代理本身的高可用。
3. 集群(服务端)分区
即 Redis Cluster:分片、路由、故障转移都由 Redis 自己完成(第 28、29 章)。对应用相对友好(客户端支持集群协议即可),且支持在线扩缩容。缺点是受集群限制约束(多键须同槽、仅 db 0 等)。
经验法则:新项目优先选 Redis Cluster;遗留系统或需要自定义路由逻辑时,再考虑客户端/代理分区。
一致性哈希:减少扩缩容的震荡
普通哈希取模 hash(key) mod N 有个严重问题:一旦实例数 N 变化,几乎所有键的映射都会改变,导致大规模数据迁移。
一致性哈希(Consistent Hashing) 用「哈希环」缓解这一点:把节点和键都哈希到一个 0 ~ 2^32-1 的环上,键顺时针找到最近的节点。当增加或移除一个节点时,只有环上该节点相邻区间的键需要迁移,其余键映射不变——迁移量从「几乎全部」降到「一小段」。
它还常配合虚拟节点(virtual node):每个物理节点在环上映射多个虚拟点,让数据分布更均匀,避免某节点恰好扛住一大片。
预分片(Presharding)
「预分片」是一种工程技巧:一开始就划分远多于当前物理机数量的逻辑分片(例如 16384 个逻辑槽),初期多台逻辑分片共用一台物理机;将来加机器时,只需把若干逻辑分片整体搬到新机器,而不必重新哈希所有键。Redis Cluster 的 16384 槽正是这个思想的体现——你扩容时迁移的是「槽」而非「单个键」。
分区的代价与限制
分区不是免费的,码上学提醒你这些代价(官方文档明确列出):
- 跨实例的多键操作不支持:比如两个集合分别落在 R0、R1,就无法对它们做
SUNION(集群里可用 hash tag 把相关键约束到同槽来规避,见第 28 章)。 - 跨实例的事务不支持:涉及多个实例键的事务无法保证原子性。
- 单键是分片最小粒度:一个几十 GB 的超大键无法被切分,只能整体存在某一实例——这正是 bigkey 风险。
- 运维更复杂:备份要聚合多实例的 RDB/AOF;监控、扩缩容都要面向「一群实例」而非单机。
- 扩缩容复杂度:客户端/代理分区通常不支持在线重平衡,而 Redis Cluster 支持。
bigkey 风险(与分区强相关)
bigkey 指单个键的值过大(如一个包含百万成员的 Hash/Set、一个超长 List、一个几十 MB 的 String)。它与分区的关系:
- 无法被分片打散:bigkey 只能整体落在一个实例,破坏负载均衡,造成「热点实例」。
- 迁移极慢:扩缩容迁移槽时,若槽里含 bigkey,迁移会长时间阻塞,拖慢整体进度。
- 删除阻塞:
DEL一个大 key 是同步的,会长时间阻塞该实例的单线程,导致超时雪崩。 - 网络与序列化开销大:一次读取就占满带宽/连接。
应对:把大集合按业务维度拆成多个小键(如 user:1000:followers:shard0、shard1);删除大 key 用 UNLINK(异步惰性删除,Redis 4+)而非 DEL。bigkey 的更深层优化放在第 31 章。
小结
- 分区把数据拆到多实例,用「内存之和、算力之和」突破单机上限。
- 基本策略:范围分区(易理解但不均)、哈希分区(主流,分布匀)。
- 实现位置:客户端分区、代理分区(Twemproxy/Codis)、集群分区(Redis Cluster,推荐)。
- 一致性哈希 + 虚拟节点把扩缩容的迁移量从「几乎全量」降到「局部」;预分片(逻辑槽)让扩容只搬槽不重哈希键。
- 代价:跨实例多键/事务受限、bigkey 不可分片且风险高。
提示:官方 redis.io 下载页另将 8.8 标注为 “Latest stable”,而 GitHub 上的最新发布 tag 为 8.10.0。分区属于架构设计主题,与具体 8.x 小版本无关,本文以 8.10.0 为基线版本。