Redis集群规范(一)

什么是 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 那样支持多数据库功能, 集群只使用默认的 0 号数据库, 并且不能使用 SELECT 命令

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

  • 持有键值对数据。
  • 记录集群的状态,包括键到正确节点的映射(mapping keys to right nodes)。
  • 自动发现其他节点,识别工作不正常的节点,并在有需要时,在从节点中选举出新的主节点。
    为了执行以上列出的任务, 集群中的每个节点都与其他节点建立起了“集群连接(cluster bus)”, 该连接是一个 TCP 连接, 使用二进制协议进行通讯。

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

  • 传播(propagate)关于集群的信息,以此来发现新的节点。
  • 向其他节点发送 PING 数据包,以此来检查目标节点是否正常运作。
  • 在特定事件发生时,发送集群信息。
    集群节点不能代理(proxy)命令请求, 所以客户端应该在节点返回 -MOVED 或者 -ASK 转向(redirection)错误时, 自行将命令请求转发至其他节点。

键分布模型

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

键空间:redis是一个键值对数据库,数据库中的键值对就由字典保存:每个数据库都有一个与之相对应的字典,这个字典被称为键空间。当用户添加一个键值对到数据库(不论键值对是什么类型),程序就讲该键值对添加到键空间,删除同理。

推荐的最大节点数量为 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

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 命令来获得。

$ 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 1318428932 connected 2730-4095

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

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 x命令之前。集群配置重新发生了变化,首次moved指向的127.0.0.1:6381不再处理哈希槽3999,则再次发送get命令之后仍然返回moved错误。并指示当前真正处理哈希槽的节点。

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

ASK 转向

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

比如说, 在我们上一节列举的槽 8 的例子中, 因为槽 8 所包含的各个键分散在节点 A 和节点 B 中, 所以当客户端在节点 A 中没找到某个键时, 它应该转向到节点 B 中去寻找, 但是这种转向应该仅仅影响一次命令查询, 而不是让客户端每次都直接去查找节点 B : 在节点 A 所持有的属于槽 8 的键没有全部被迁移到节点 B 之前, 客户端应该先访问节点 A , 然后再访问节点 B 。
因为这种转向只针对 16384 个槽中的其中一个槽, 所以转向对集群造成的性能损耗属于可接受的范围。

因为上述原因, 如果我们要在查找节点 A 之后, 继续查找节点 B , 那么客户端在向节点 B 发送命令请求之前, 应该先发送一个ASKING 命令, 否则这个针对带有 IMPORTING 状态的槽的命令请求将被节点 B 拒绝执行。

接收到客户端 ASKING 命令的节点将为客户端设置一个一次性的标志(flag), 使得客户端可以执行一次针对 IMPORTING 状态的槽的命令请求。

从客户端的角度来看, ASK 转向的完整语义(semantics)如下:

  • 如果客户端接收到 ASK 转向, 那么将命令请求的发送对象调整为转向所指定的节点。
  • 先发送一个 ASKING 命令,然后再发送真正的命令请求。
  • 不必更新客户端所记录的槽 8 至节点的映射: 槽 8 应该仍然映射到节点 A , 而不是节点 B 。

一旦节点 A 针对槽 8 的迁移工作完成, 节点 A 在再次收到针对槽 8 的命令请求时, 就会向客户端返回 MOVED 转向, 将关于槽 8的命令请求长期地转向到节点 B 。
注意, 即使客户端出现 Bug , 过早地将槽 8 映射到了节点 B 上面, 但只要这个客户端不发送 ASKING 命令, 客户端发送命令请求的时候就会遇上 MOVED 错误, 并将它转向回节点 A 。

Redis 集群的一致性保证(guarantee)

Redis 集群不保证数据的强一致性(strong consistency): 在特定条件下, Redis 集群可能会丢失已经被执行过的写命令。原因有如下两点:
(1)、使用异步复制(asynchronous replication)考虑以下这个写命令的例子:

  • 客户端向主节点 B 发送一条写命令。
  • 主节点 B 执行写命令,并向客户端返回命令回复。
  • 主节点 B 将刚刚执行的写命令复制给它的从节点 B1 、 B2 和 B3 。
    如你所见, 主节点对命令的复制工作发生在返回命令回复之后先返回客户端回复,再执行从节点数据复制, 因为如果每次处理命令请求都需要等待复制操作完成的话, 那么主节点处理命令请求的速度将极大地降低 —— 我们必须在性能和一致性之间做出权衡。

(2) 、集群出现网络分裂(network partition), 并且一个客户端与至少包括一个主节点在内的少数(minority)实例被孤立。

假设集群包含 A 、 B 、 C 、 A1 、 B1 、 C1 六个节点, 其中 A 、B 、C 为主节点, 而 A1 、B1 、C1 分别为三个主节点的从节点, 另外还有一个客户端 Z1 。

集群中发生网络分裂, 那么集群可能会分裂为两方, 大多数(majority)的一方包含节点 A 、C 、A1 、B1 和 C1 , 而少数(minority)的一方则包含节点 B 和客户端 Z1 。

在网络分裂期间, 主节点 B 仍然会接受 Z1 发送的写命令:

  • 如果网络分裂出现的时间很短, 那么集群会继续正常运行;
  • 如果网络分裂出现的时间足够长,从节点 B1被选举代替原来的主节点 B , 那么 Z1 发送给主节点 B 的写命令将丢失。

在网络分列期间,如果节点B未能在节点超时限制之内从新连接上集群

  • 该主节点将停止处理写命令, 并向客户端报告错误。
  • 集群会将这个主节点视为下线, 并使用从节点来代替这个主节点继续工作。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值