Redis cluster集群模式的数据分片问题

46 阅读5分钟

1. Redis cluster模式下是怎么存储数据的

Redis为了解决这个问题设计了一种解决方案(HashSlot)哈希槽,它是一个「中间层映射规则」。 HashSlot 的核心规则(一句话总结)

  • 集群预先规定:所有数据都属于「16384 个哈希槽」中的一个(编号 0~16383,也就是你说的 16K);
  • 每个 Redis 实例(主节点)负责「一部分哈希槽」(比如 M1 负责 0-5460,M2 负责 5461-10922,M3 负责 10923-16383);
  • 当存储 / 查询数据时,Redis 会按固定算法,把「key」映射到「某个哈希槽」,再找到这个哈希槽对应的实例,就能快速定位数据。 总的来说就是redis会维护一张哈希槽和redis实例的映射表,集群的每一个节点都会维护这张表,这张表的一致性是由Gossip协议在后台自动维护的,无需人为干预。

2. Redis cluster集群模式下拓展一台机器后数据会怎么变化

1. 初始状态(2 个实例)

  • M1 负责:0~8191 槽
  • M2 负责:8192~16383 槽此时 "foo" 映射到槽 12345 → 存在 M2 上。

2. 扩容后(新增 M3,3 个实例)

  • 新的槽分配:M1(0 ~ 5460)、M2(5461 ~ 10922)、M3(10923 ~ 16383)
  • 此时 "foo" 还是映射到槽 12345 → 现在槽 12345 属于 M3,所以数据要从 M2 迁移到 M3。

3. 核心优势:迁移的是 “槽”,不是 “数据”

  • 如果没有 HashSlot:新增实例后,可能需要重新计算所有 key 的存储位置(比如按「实例数量」取模),导致大量数据迁移,还会让旧的映射规则失效(查询时找不到数据);
  • 有了 HashSlot:迁移时只需要「把某个槽的所有数据,从旧实例搬到新实例」,再更新「槽 → 实例」的映射表即可。比如例子中,只需要把 M2 负责的「10923~16383 槽」的数据迁移到 M3,其他槽的数据完全不动。

而且这个迁移过程是「在线的」:

  • 迁移期间,查询某个槽的数据时,Redis 会先去旧实例找,如果数据已经迁移到新实例,会自动转发请求到新实例,用户完全感知不到;
  • 迁移完成后,映射表更新,后续请求直接指向新实例,没有停机时间。

[补充一点] 数据是在集群进行 “重新分片(Resharding)” 操作时,由 Redis 系统自动、主动地从旧实例复制到新实例的。这个过程发生在后台,不依赖于用户的查询。

面试延伸提问

如果面试官进一步问:

  • **槽迁移时的数据一致性如何保证?**答:迁移过程中,原节点会标记槽为「迁移中」,此时写入操作会同时写入原节点和新节点(双写),读取时优先从新节点读取,确保数据不丢失。
  • **为什么是 16384 个槽?**答:16384 是一个经验值,既保证了槽的数量足够分散(避免单节点负责过多槽),又不会因槽数量过多导致节点间的元数据同步开销增大(槽越多,Gossip 协议同步的信息越多)。

3. Redis cluster集群模式下某一台机器出现了热key会怎么处理

作业帮面试问到了

方案:Key 分片(Sharding the Key) 这是一种更彻底、但实施更复杂的解决方案,适用于单个 key 的访问量已经高到单个节点完全无法承受的场景。 核心思想: 将原本的一个热 key,通过在其名称中加入一个随机前缀或后缀,分散成多个 key,存储在不同的哈希槽(从而不同的节点)上。

实现步骤:

  1. 写入数据

    • 假设原始热 key 是 user:profile:1000
    • 我们生成一个随机数(例如 0-9),假设是 5
    • 将数据写入新的 key:user:profile:1000:shard:5
    • 同时,为了能读取到所有分片的数据,你需要一个 “索引” key 来记录这个主 key 被分成了多少片以及分片的 ID。例如,user:profile:1000:shards,其值可以是一个包含所有分片 ID 的集合 {0, 1, 2, ..., 9}
  2. 读取数据

    • 应用程序需要获取 user:profile:1000 的数据时,首先读取索引 key user:profile:1000:shards
    • 然后,并行地去获取所有分片 key 的数据,例如 user:profile:1000:shard:0user:profile:1000:shard:1, ..., user:profile:1000:shard:9
    • 最后,在应用程序内存中将这些分片数据合并成完整的 user:profile:1000 的数据。

优点:

  • 压力完全分散:将一个 key 的压力均匀分散到 N 个节点上(N 是分片数),理论上可以无限扩展。

缺点:

  • 代码侵入性强:读写逻辑都需要大幅修改,变得非常复杂。
  • 数据完整性挑战:如果某个分片 key 丢失或损坏,会导致整体数据不完整。
  • 不适用于所有场景:这种方法更适合于存储大体积、可分割的数据(如一个用户的所有聊天记录),而不是一个简单的计数器或小对象。

热key的其他解决方案

Redis 热 key 的终极解决方案?京东、得物、b 站都是如何解决的?背景 Redis 热 key 问题是指单位时间 - 掘金