您现在的位置是:网站首页> 编程资料编程资料

redis集群规范详解_Redis_

2023-05-27 411人已围观

简介 redis集群规范详解_Redis_

本文档翻译自 http://redis.io/topics/cluster-spec 。

引言

这个文档是正在开发中的 Redis 集群功能的规范(specification)文档, 文档分为两个部分:

第一部分介绍目前已经在 unstable 分支中实现了的那些功能。
第二部分介绍目前仍未实现的那些功能。

文档各个部分的内容可能会随着集群功能的设计修改而发生改变, 其中, 未实现功能发生修改的几率比已实现功能发生修改的几率要高。

这个规范包含了编写客户端库(client library)所需的全部知识, 不过请注意, 这里列出的一部分细节可能会在未来发生变化。

什么是 Redis 集群?

Redis 集群是一个分布式(distributed)、容错(fault-tolerant)的 Redis 实现, 集群可以使用的功能是普通单机 Redis 所能使用的功能的一个子集(subset)。

Redis 集群中不存在中心(central)节点或者代理(proxy)节点, 集群的其中一个主要设计目标是达到线性可扩展性(linear scalability)。

Redis 集群为了保证一致性(consistency)而牺牲了一部分容错性: 系统会在保证对网络断线(net split)和节点失效(node failure)具有有限(limited)抵抗力的前提下, 尽可能地保持数据的一致性。

集群将节点失效视为网络断线的其中一种特殊情况。

集群的容错功能是通过使用主节点(master)和从节点(slave)两种角色(role)的节点(node)来实现的:

主节点和从节点使用完全相同的服务器实现, 它们的功能(functionally)也完全一样, 但从节点通常仅用于替换失效的主节点。
不过, 如果不需要保证“先写入,后读取”操作的一致性(read-after-write consistency), 那么可以使用从节点来执行只读查询。

Redis 集群实现的功能子集

Redis 集群实现了单机 Redis 中, 所有处理单个数据库键的命令。

针对多个数据库键的复杂计算操作, 比如集合的并集操作、合集操作没有被实现, 那些理论上需要使用多个节点的多个数据库键才能完成的命令也没有被实现。

在将来, 用户也许可以通过 MIGRATE COPY 命令, 在集群的计算节点(computation node)中执行针对多个数据库键的只读操作, 但集群本身不会去实现那些需要将多个数据库键在多个节点中移来移去的复杂多键命令。

Redis 集群不像单机 Redis 那样支持多数据库功能, 集群只使用默认的 0 号数据库, 并且不能使用 SELECT 命令。

Redis 集群协议中的客户端和服务器

Redis 集群中的节点有以下责任:

持有键值对数据。
记录集群的状态,包括键到正确节点的映射(mapping keys to right nodes)。
自动发现其他节点,识别工作不正常的节点,并在有需要时,在从节点中选举出新的主节点。

为了执行以上列出的任务, 集群中的每个节点都与其他节点建立起了“集群连接(cluster bus)”, 该连接是一个 TCP 连接, 使用二进制协议进行通讯。

节点之间使用 Gossip 协议 来进行以下工作:

传播(propagate)关于集群的信息,以此来发现新的节点。
向其他节点发送 PING 数据包,以此来检查目标节点是否正常运作。
在特定事件发生时,发送集群信息。

除此之外, 集群连接还用于在集群中发布或订阅信息。

因为集群节点不能代理(proxy)命令请求, 所以客户端应该在节点返回 -MOVED 或者 -ASK 转向(redirection)错误时, 自行将命令请求转发至其他节点。

因为客户端可以自由地向集群中的任何一个节点发送命令请求, 并可以在有需要时, 根据转向错误所提供的信息, 将命令转发至正确的节点, 所以在理论上来说, 客户端是无须保存集群状态信息的。

不过, 如果客户端可以将键和节点之间的映射信息保存起来, 可以有效地减少可能出现的转向次数, 籍此提升命令执行的效率。

键分布模型

Redis 集群的键空间被分割为 16384 个槽(slot), 集群的最大节点数量也是 16384 个。

推荐的最大节点数量为 1000 个左右。

每个主节点都负责处理 16384 个哈希槽的其中一部分。

当我们说一个集群处于“稳定”(stable)状态时, 指的是集群没有在执行重配置(reconfiguration)操作, 每个哈希槽都只由一个节点进行处理。

重配置指的是将某个/某些槽从一个节点移动到另一个节点。

一个主节点可以有任意多个从节点, 这些从节点用于在主节点发生网络断线或者节点失效时, 对主节点进行替换。

以下是负责将键映射到槽的算法:

 HASH_SLOT = CRC16(key) mod 16384

以下是该算法所使用的参数:

算法的名称: XMODEM (又称 ZMODEM 或者 CRC-16/ACORN)
结果的长度: 16 位
多项数(poly): 1021 (也即是 x16 + x12 + x5 + 1)
初始化值: 0000
反射输入字节(Reflect Input byte): False
发射输出 CRC (Reflect Output CRC): False
用于 CRC 输出值的异或常量(Xor constant to output CRC): 0000
该算法对于输入 "123456789" 的输出: 31C3

附录 A 中给出了集群所使用的 CRC16 算法的实现。

CRC16 算法所产生的 16 位输出中的 14 位会被用到。
在我们的测试中, CRC16 算法可以很好地将各种不同类型的键平稳地分布到 16384 个槽里面。

集群节点属性

每个节点在集群中都有一个独一无二的 ID , 该 ID 是一个十六进制表示的 160 位随机数, 在节点第一次启动时由 /dev/urandom 生成。

节点会将它的 ID 保存到配置文件, 只要这个配置文件不被删除, 节点就会一直沿用这个 ID 。

节点 ID 用于标识集群中的每个节点。 一个节点可以改变它的 IP 和端口号, 而不改变节点 ID 。 集群可以自动识别出 IP/端口号的变化, 并将这一信息通过 Gossip 协议广播给其他节点知道。

以下是每个节点都有的关联信息, 并且节点会将这些信息发送给其他节点:

节点所使用的 IP 地址和 TCP 端口号。
节点的标志(flags)。
节点负责处理的哈希槽。
节点最近一次使用集群连接发送 PING 数据包(packet)的时间。
节点最近一次在回复中接收到 PONG 数据包的时间。
集群将该节点标记为下线的时间。
该节点的从节点数量。
如果该节点是从节点的话,那么它会记录主节点的节点 ID 。 如果这是一个主节点的话,那么主节点 ID 这一栏的值为 0000000 。

以上信息的其中一部分可以通过向集群中的任意节点(主节点或者从节点都可以)发送 CLUSTER NODES 命令来获得。

以下是一个向集群中的主节点发送 CLUSTER NODES 命令的例子, 该集群由三个节点组成:

 $ redis-cli cluster nodes d1861060fe6a534d42d8a19aeb36600e18785e04 :0 myself - 0 1318428930 connected 0-1364 3886e65cc906bfd9b1f7e7bde468726a052d1dae 127.0.0.1:6380 master - 1318428930 1318428931 connected 1365-2729 d289c575dcbc4bdd2931585fd4339089e461a27d 127.0.0.1:6381 master - 1318428931 1318428931 connected 2730-4095

在上面列出的三行信息中, 从左到右的各个域分别是: 节点 ID , IP 地址和端口号, 标志(flag), 最后发送 PING 的时间, 最后接收 PONG 的时间, 连接状态, 节点负责处理的槽。

节点握手(已实现)

节点总是应答(accept)来自集群连接端口的连接请求, 并对接收到的 PING 数据包进行回复, 即使这个 PING 数据包来自不可信的节点。

然而, 除了 PING 之外, 节点会拒绝其他所有并非来自集群节点的数据包。

要让一个节点承认另一个节点同属于一个集群, 只有以下两种方法:

一个节点可以通过向另一个节点发送 MEET 信息, 来强制让接收信息的节点承认发送信息的节点为集群中的一份子。 一个节点仅在管理员显式地向它发送 CLUSTER MEET ip port 命令时, 才会向另一个节点发送 MEET 信息。
另外, 如果一个可信节点向另一个节点传播第三者节点的信息, 那么接收信息的那个节点也会将第三者节点识别为集群中的一份子。 也即是说, 如果 A 认识 B , B 认识 C , 并且 B 向 A 传播关于 C 的信息, 那么 A 也会将 C 识别为集群中的一份子, 并尝试连接 C 。

这意味着如果我们将一个/一些新节点添加到一个集群中, 那么这个/这些新节点最终会和集群中已有的其他所有节点连接起来。

这说明只要管理员使用 CLUSTER MEET 命令显式地指定了可信关系, 集群就可以自动发现其他节点。

这种节点识别机制通过防止不同的 Redis 集群因为 IP 地址变更或者其他网络事件的发生而产生意料之外的联合(mix), 从而使得集群更具健壮性。

当节点的网络连接断开时, 它会主动连接其他已知的节点。

MOVED 转向

一个 Redis 客户端可以向集群中的任意节点(包括从节点)发送命令请求。 节点会对命令请求进行分析, 如果该命令是集群可以执行的命令, 那么节点会查找这个命令所要处理的键所在的槽。

如果要查找的哈希槽正好就由接收到命令的节点负责处理, 那么节点就直接执行这个命令。
另一方面, 如果所查找的槽不是由该节点处理的话, 节点将查看自身内部所保存的哈希槽到节点 ID 的映射记录, 并向客户

端回复一个 MOVED 错误。

以下是一个 MOVED 错误的例子:

 GET x -MOVED 3999 127.0.0.1:6381

错误信息包含键 x 所属的哈希槽 3999 , 以及负责处理这个槽的节点的 IP 和端口号 127.0.0.1:6381 。 客户端需要根据这个 IP 和端口号, 向所属的节点重新发送一次 GET 命令请求。

注意, 即使客户端在重新发送 GET 命令之前, 等待了非常久的时间, 以至于集群又再次更改了配置, 使得节点 127.0.0.1:6381 已经不再处理槽 3999 , 那么当客户端向节点 127.0.0.1:6381 发送 GET 命令的时候, 节点将再次向客户端返回 MOVED 错误, 指示现在负责处理槽 3999 的节点。

虽然我们用 ID 来标识集群中的节点, 但是为了让客户端的转向操作尽可能地简单, 节点在 MOVED 错误中直接返回目标节点的 IP 和端口号, 而不是目标节点的 ID 。

虽然不是必须的, 但一个客户端应该记录(memorize)下“槽 3999 由节点 127.0.0.1:6381 负责处理“这一信息, 这样当再次有命令需要对槽 3999 执行时, 客户端就可以加快寻找正确节点的速度。

注意, 当集群处于稳定状态时, 所有客户端最终都会保存有一个哈希槽至节点的映射记录(map of hash slots to nodes), 使得集群非常高效: 客户端可以直接向正确的节点发送命令请求, 无须转向、代理或者其他任何可能发生单点故障(single point failure)的实体(entiy)。

除了 MOVED 转向错误之外, 一个客户端还应该可以处理稍后介绍的 ASK 转向错误。

集群在线重配置(live reconfiguration)

Redis 集群支持在集群运行的过程中添加或者移除节点。

实际上, 节点的添加操作和节点的删除操作可以抽象成同一个操作, 那就是, 将哈希槽从一个节点移动到另一个节点:

添加一个新节点到集群, 等于将其他已存在节点的槽移动到一个空白的新节点里面。

从集群中移除一个节点, 等于将被移除节点的所有槽移动到集群的其他节点上面去。

因此, 实现 Redis 集群在线重配置的核心就是将槽从一个节点移动到另一个节点的能力。 因为一个哈希槽实际上就是一些键的集合, 所以 Redis 集群在重哈希(rehash)时真正要做的, 就是将一些键从一个节点移动到另一个节点。

要理解 Redis 集群如何将槽从一个节点移动到另一个节点, 我们需要对 CLUSTER 命令的各个子命令进行介绍, 这些命理负责管理集群节点的槽转换表(slots translation table)。

以下是 CLUSTER 命令可用的子命令:

 CLUSTER ADDSLOTS slot1 [slot2] ... [slotN] CLUSTER DELSLOTS slot1 [slot2] ... [slotN] CLUSTER SETSLOT slot NODE node CLUSTER SETSLOT slot MIGRATING node CLUSTER SETSLOT slot IMPORTING node

最开头的两条命令 ADDSLOTS 和 DELSLOTS 分别用于向节点指派(assign)或者移除节点, 当槽被指派或者移除之后, 节点会将这一信息通过 Gossip 协议传播到整个集群。 ADDSLOTS 命令通常在新创建集群时, 作为一种快速地将各个槽指派给各个节点的手段来使用。

CLUSTER SETSLOT slot NODE node 子命令可以将指定的槽 slot 指派给节点 node 。

至于 CLUSTER SETSLOT slot MIGRATING node 命令和 CLUSTER SETSLOT slot IMPORTING node 命令, 前者用于将给定节点 node 中的槽 slot 迁移出节点, 而后者用于将给定槽 slot 导入到节点 node :

当一个槽被设置为 MIGRATING 状态时, 原来持有这个槽的节点仍然会继续接受关于这个槽的命令请求, 但只有命令所处理的键仍然存在于节点时, 节点才会处理这个命令请求。

如果命令所使用的键不存在与该节点, 那么节点将向客户端返回一个 -ASK 转向(redirection)错误, 告知客户端, 要将命令请求发送到槽的迁移目标节点。

当一个槽被设置为 IMPORTING 状态时, 节点仅在接收到 ASKING 命令之后, 才会接受关于这个槽的命令请求。

如果客户端没有向节点发送 ASKING 命令, 那么节点会使用 -MOVED 转向错误将命令请求转向至真正负责处理这个槽的节点。

上面关于 MIGRATING 和 IMPORTING 的说明有些难懂, 让我们用一个实际的实例来说明一下。

假设现在, 我们有 A 和 B 两个节点, 并且我们想将槽 8 从节点 A 移动到节点 B , 于是我们:

向节点 B 发送命令 CLUSTER SETSLOT 8 IMPORTING A
向节点 A 发送命令 CLUSTER SETSLOT 8 MIGRATING B

每当客户端向其他节点发送关于哈希槽 8 的命令请求时, 这些节点都会向客户端返回指向节点 A 的转向信息:

如果命令要处理的键已经存在于槽 8 里面, 那么这个命令将由节点 A 处理。
如果命令要处理的键未存在于槽 8 里面(比如说,要向槽添加一个新的键), 那么这个命令由节点 B 处理。

这种机制将使得节点 A 不再创建关于槽 8 的任何新键。

与此同时, 一个特殊的客户端 redis-trib 以及 Redis 集群配置程序(configuration utility)会将节点 A 中槽 8 里面的键移动到节点 B 。

键的移动操作由以下两个命令执行:

 CLUSTER GETKEYSINSLOT slot count

上面的命令会让节点返回 count 个 slot 槽中的键, 对于命令所返回的每个键, redis-trib 都会向节点 A 发送一条 MIGRATE 命令, 该命令会将所指定的键原子地(atomic)从节点 A 移动到节点 B (在移动键期间,两个节点都会处于阻塞状态,以免出现竞争条件)。

以下为 MIGRATE 命令的运作原理:

 MIGRATE target_host target_port key target_database id timeout 

执行 MIGRATE 命令的节点会连接到 target 节点, 并将序列化后的 key 数据发送给 target , 一旦 target 返回 OK , 节点就将自己的 key 从数据库中删除。

从一个外部客户端的视角来看, 在某个时间点上, 键 key 要么存在于节点 A , 要么存在于节点 B , 但不会同时存在于节点 A 和节点 B 。

因为 Redis 集群只使用 0 号数据库, 所以当 MIGRATE 命令被用于执行集群操作时, target_database 的值总是 0 。
target_database 参数的存在是为了让 MIGRATE 命令成为一个通用命令, 从而可以作用于集群以外的其他功能。

我们对 MIGRATE 命令做了优化, 使得它即使在传输包含多个元素的列表键这样的复杂数据时, 也可以保持高效。

不过, 尽管 MIGRATE 非常高效, 对一个键非常多、并且键的数据量非常大的集群来说, 集群重配置还是会占用大量的时间, 可能会导致集群没办法适应那些对于响应时间有严格要求的应用程序。

ASK 转向

在之前介绍 MOVED 转向的时候, 我们说除了 MOVED 转向之外, 还有另一种 ASK 转向。

当节点需要让一个客户端长期地(permanently)将针对某个槽的命令请求发送至另一个节点时, 节点向客户端返回 MOVED 转向。

另一方面, 当节点需要让客户端仅仅在下一个命令请求中转向至另一个节点时, 节点向客户端返回 ASK 转向。

比如说, 在我们上一节列举的槽 8 的例子中, 因为槽 8 所包含的各个键分散在节点 A 和节点 B 中, 所以当客户端在节点 A 中没找到某个键时,

-六神源码网