consistent hashing 一致性哈希

hash slot 哈希槽

Redis 集群的数据分片

Redis 集群没有使用一致性哈希,而是使用了一种不同的数据分片形式: 每个键在概念上属于所谓的 哈希槽 (hash slot) 的一部分。Redis 集群一共有 16384 个哈希槽,计算一个给定键的哈希槽,采取的方式是:键的 CRC16 对 16384 取模

Redis 集群中的每一个节点都负责所有哈希槽的一个子集. 假如一个集群有三个节点,那么哈希槽的分布可能是这样:

  • A 节点的哈希槽范围 0 - 5500
  • B 节点的哈希槽范围 5501 - 11000
  • C 节点的哈希槽范围 11001 - 16383

这使得很容易在集群中增加和移除节点。例如,如果想增加一个新节点 D, 需要将 A,B,C 中的某些哈希槽移动到 D. 类似的,如果想要移除节点 A, 可以将 A 负责的哈希槽移动到 B 和 C。

由于从一个节点向另一个节点移动哈希槽并不需要停止操作,增加和移除节点或者改变节点所负责的哈希槽的百分比,不需要任何的宕机操作。

Redis 集群支持多个键的操作(多个键的同时操作),只要单个命令的执行(Redis事务,Lua脚本执行)所涉及的这些键,都属于同一个哈希槽。用户可以通过使用一个叫做 哈希标签 (hash tags) 的概念,强制多个键属于不同的哈希槽.

Redis 集群规范中记录了哈希标签,但重点是,如果键中的 {} 括号之间有一个子字符串,则仅对字符串内的内容进行哈希处理,例如 this{foo}keyanother{foo} key 保证在同一个哈希槽中,并且可以在一个命令中一起使用多个键作为参数。

Redis 集群的主复制模式

Redis Cluster master-replica model

为了保证可以用性, 当主节点中的某些节点出现故障或者不能与集群中的大多数节点进行通信时, Redis 集群使用了一种主节点复制模式, 即每个哈希槽都有一个到 N 个副本 (N-1 个额外的副本节点).

在上述 A, B, C 三个节点的集群中, 如果节点 B 故障, 集群不能与之通信, 那么就不再能够提供哈希槽 0-5500 范围的服务. 然而, 如果在创建集群的时候, 为每一个主节点增加一个副本节点, 那么最终集群包含了 A, B, C 以及对应的副本节点 A1, B1, C1. 这样, 系统就能够在 B 节点故障时继续提供服务.

B1 节点复制节点 B, 如果 B 节点故障, 集群将使得 B1 成为新的主节点继续正确地提供服务. 但是如果 B1 和 B 同一时间都故障了, 那么集群就不能继续服务.

Redis 集群地一致性保障

Redis Cluster consistency guarantees

Redis Cluster 无法保证强一致性。实际上,这意味着在某些条件下,Redis Cluster 可能会丢失系统已向客户端确认的写入。

Redis Cluster 会丢失写入的第一个原因是因为它使用异步复制。这意味着在写入期间会发生以下情况:

客户端写入主 B。
主 B 向客户端回复 OK。
主 B 将写入传播到其副本 B1、B2 和 B3。

B 在回复客户端之前不会等待来自 B1、B2、B3 的确认,因为这对 Redis 来说是一个令人望而却步的延迟惩罚,因此如果您的客户端写入某些内容,B 会确认写入,但会在此之前崩溃能够将写入发送到其副本,其中一个副本(未收到写入)可以提升为主,永远失去写入。

这与大多数配置为每秒将数据刷新到磁盘的数据库发生的情况非常相似,因此您已经能够推理出这种情况,因为过去使用不涉及分布式系统的传统数据库系统的经验。同样,您可以通过强制数据库在回复客户端之前将数据刷新到磁盘来提高一致性,但这通常会导致性能低得令人望而却步。在 Redis Cluster 的情况下,这相当于同步复制。

基本上,需要在性能和一致性之间进行权衡。

Redis 集群在绝对需要时支持同步写入,通过 WAIT 命令实现。这使得丢失写入的可能性大大降低。但是请注意,即使使用同步复制,Redis Cluster 也没有实现强一致性:在更复杂的故障场景下,始终有可能将无法接收写入的副本选为 master。

还有一个值得注意的场景是 Redis 集群将丢失写入,这发生在网络分区期间,客户端与少数实例(至少包括一个主实例)隔离。

以我们的 6 个节点集群为例,它由 A、B、C、A1、B1、C1 组成,有 3 个主节点和 3 个副本节点。还有一个客户端,我们将其称为 Z1。

分区发生后,可能在分区的一侧有 A、C、A1、B1、C1,而在另一侧有 B 和 Z1。

Z1 仍然能够写入 B,B 将接受其写入。如果分区在很短的时间内愈合,集群将继续正常运行。但是,如果分区持续足够的时间让 B1 在分区的多数侧提升为 master,则 Z1 同时发送给 B 的写入将丢失。

请注意,Z1 能够发送到 B 的写入量有一个最大窗口:如果分区的多数方已经有足够的时间来选举一个副本作为主节点,那么少数方的每个主节点都将停止接受写入。

这个时间量是Redis Cluster一个非常重要的配置指令,称为节点超时。

节点超时后,主节点被视为出现故障,并且可以由其副本之一替换。类似地,在节点超时过后,主节点无法感知大多数其他主节点,它会进入错误状态并停止接受写入。