选译:Reids 集群规范
Redis 集群目标
-
高性能:可线性伸缩到1000个节点,无代理通信,使用异步复制,不对值执行merge操作。
-
一致性:可接受程度内的写安全。对于连接到主节点中”大多数“的客户端,系统会最大程度保留他们的写入。
-
可用性:当主节点中”大多数“可达,且每个不可达的主节点至少有一个可达的副本,集群能够在分区中存活。
副本迁移:一个副本都没有的主节点,可以从其他拥有多个副本的主节点接收一个副本。
子集实现
Reids 单机版支持的单键命令,Reids 集群也支持。
对于复杂的多键命令,如果这些键都被哈希到相同的槽,Reids 集群也支持。
Reids 集群支持"hash tag"的概念,用来强制某些键存储到同一个哈希槽。
Reids 集群不支持多个数据库,只有0号数据库。
服务端和客户端在 Reids 集群中扮演的角色
- 集群节点负责承载集群的数据和状态,包括把键映射到正确的节点。
- 集群节点可以相互发现,探测不能工作的节点。当故障发生时,为了能够继续操作集群,集群节点可以把副本提升为主节点。
- 集群节点使用 TCP 总线、二进制协议相互通信,也叫集群总线。
- 为了发现新节点,集群节点之间使用 gossip 协议广播集群信息。使用 ping 包确认其他节点正常工作,使用集群消息通知特定的条件。
- Pub/Sub 消息通过集群总线广播到整个集群。
- 系统管理员执行手动故障转移也是通过集群总线编排实现。
- 集群节点不代理请求,使用重定向错误-MOVE或-ASK可以把客户端重定向到其他节点。理论上,客户端可以向任意集群节点发送请求,但根据需要,客户端可能会被重定向。客户端不必须保存集群的状态,但客户端可以缓存键和节点之间的映射关系,合理地提升性能。
写安全
-
集群节点之间使用异步复制,以最后被选中的主节点数据集为准,覆盖其他副本的数据(如果有差异)。发生分区的期间,会有一个时间窗口,期间的写入可能会丢失。
-
被分区中的大多数所接受、确认的写入,在故障期间,写入丢失的例子:
-
写入已经被主节点接受,客户端收到回复(确认),但写入异步复制到副本失败。等待足够久的时间后,其中一个副本被提升为主节点。
-
主节点因为分区而不可达,被其中一个副本代替,一段时候后恢复可达,然后旧的主节点被转换为副本。客户端因为缓存了路由表(键-节点),在旧的主节点被转换为副本之前,向它写入了数据。
-
-
当一个主节点不能被主节点中的大多数访问,持续时间达到 NODE_TIMEOUT,主节会被其副本替换。如果在 NODE_TIMEOUT 之前分区被解决,没有写入丢失,否则期间的写入会丢失。(因为分区)如果主节点中的少数在持续 NODE_TIMEOUT 时间之后,仍然无法连接上主节点中的大多数,主节点中的少数进入拒接写入的状态。
性能
- redis 集群节点不会把命令转发到负责该命令键的节点,但是会重定向客户端到正确的节点。
- 客户端最终可以得到键和节点之间的正确映射关系,直接向正确的节点发送命令。
- 因为使用了异步复制,客户端不会等待其他节点的写入确认,除非使用WAIT命令。
- 集群性能根据主节点的数量线性伸缩。通常客户端和节点保持持久链接,延迟也不会增加。
- 高性能,高伸缩性,弱数据安全,弱可用性,是 redis 集群的主要目标。
键分布式模型
键空间被划分为16384个槽,集群中的每个主节点负责16384个槽中的一个子集。
HASH_SLOT = CRC16(key) mod 16384
键 hash tag
hash tag 保证多个键被哈希到同一个槽。
def HASH_SLOT(key)
s = key.index "{"
if s
e = key.index "}",s+1
if e && e != s+1
key = key[s+1..e-1]
end
end
crc16(key) % 16384
end
重定向和重分片
MOVED 重定向
-
redis 客户端可以向任意节点发送请求,包括副本节点。如果请求是可接受的(单键请求,或者是多键请求但键都在同一个槽),节点会去查找负责(键所属)哈希槽的节点。如果哈希槽正好由当前节点负责,直接处理请求,否则查找内部映射表(槽->节点),回复 MOVED 错误:
GET x -MOVED 3999 127.0.0.1:6381
3999为键所属槽,127.0.0.1:6381为能处理此请求的实例IP和端口。
-
客户端需要重新发送请求到对应的实例。
-
客户单应该记住3999号槽由127.0.0.1:6381实例负责。下次要发送请求时,计算键所在哈希槽,有更大几率直接选中正确的节点。
-
另一个方案是,当收到MOVED重定向时,使用 CLUSTER NODES 或者 CLUSTER SLOTS 命令查询集群内所有槽->节点的映射关系。
ASK 重定向
- 案例:8号槽由节点A负责,现在要把所有属于8号槽的键(可能有很多),由节点A迁移到节点B,键迁移过程中,8号槽仍然由节点A负责,直至所有键迁移完成后,8号槽开始由节点B负责。
- 键迁移过程中,可能一部分键还在旧节点A上,一部分键已经在新节点B上。
- 客户端向节点A请求8号槽的foo键,此时迁移还在进行中,但foo键已经迁移到新节点B,A节点给客户端返回ASK重定向,重定向至节点B。
- 客户单收到至节点B的ASK重定向后,先向节点B发送ASKING命令,随后重新向节点B发送foo键的请求。
- 如果节点B收到8号槽foo键的请求,但是前面没有ASKING命令,节点B不作处理,给客户端返回MOVED重定向,重定向至节点A。因为迁移还未完成,8号槽仍然由节点A负责。
- 客户端不应该更新本地槽->节点的映射,错误地认为8号槽已经由节点B负责。随后关于8号槽其他键的请求,客户端仍要先发送到节点A,收到明确的ASK重定向后才能重新发送(到节点B)。
- 所有键迁移完成后,8号槽开始由节点B负责,客户端向节点A请求8号槽键,会被MOVED重定向到节点B。
客户端首次连接&重定向的处理
下列情况下,客户端通常需要完整地获取所有槽->节点的映射:
- 启动初始化
- 收到MOVED重定向
参考 CLUSTER SLOTS 命令。
多键操作
当某个槽正处于重新分片的过程中,属于此槽的键是不可用的。
用副本节点伸缩读操作
连接可以开启只读模式,暗示客户端可以接受“过时”的数据,对正在进行中的写入不感兴趣。
参考 READONLY 命令。