去中心Redis-Cluster规范(二)
本文翻译自官方文档
Redis-Cluster主要组件概述
键分布模型
键(key)空间被分割为16384个槽(slot),实际上这限制了Redis-Cluster最大主节点数为16384.(然而,建议的集群最大节点数约为1000)
每个主节点处理16384个槽中的一部分.当集群没有在执行任何重配置操作(例如hash slot从一个节点迁移到另一个节点)时被称为稳定的(stable).当集群稳定时,一个独立的hash slot只在一个节点上提供服务(然而一个正在提供服务的节点可以有一个或多个从节点可以在网络分区或失效时进行替换,如果可以接受读到稍旧的值,也可以通过读这些从节点来扩展集群读操作容量)
映射key->hash slot的基本算法如下:
HASH_SLOT = CRC16(key) mod 16384
仅使用CRC16输出的16位中的14位,因此在上述公式中是对16384(2^14^)求模.
键哈希标签(keys hash tag)
计算hash slot时有一个例外,就是hash tag.hash tag可以用来确保多个key被分配在相同的hash slot中,以此来实现在Redis-Cluster中进行多key操作.
为了实现hash tag,hash slot的计算在一定条件下有一点儿不同.如果key中包含”{…}”模式,那么只取”{“和”}”中间的部分进行hash运算得到hash slot.然而,因为”{“和”}”有可能出现多次,所以这个算法有如下要求:
- key中包含一个”{“.
- 有一个”}”在其右侧
- 在首次出现的”{“和首次出现的”}”之间有一个或多个字符.
满足上述条件时,则不使用整个key进行hash计算,仅使用首次出现的”{“和右侧首次出现的”}”之间的部分进行计算.
示例:
- 假设有两个key: “{user1000}.following”和”{user1000}.followers”将被hash计算到相同的hash slot中.因为只有字串”user1000”会被用来做hash计算.
- 对于这个key:”foo{}{bar}”,整个key将被用来计算hash slot,因为首次出现的”{“和右侧跟个的”}”间没有字符.
- “foo{{bar}}”: “{bar”会被用来计算hash tag.因为这是首次出现的”{“和其右侧首次出现的”}”之间的字符串.
- “foo{bar}{zap}”: “bar”会被用来计算hash tag.因为算法只会匹配首次出现的”{…}”模式串.
- 这样的算法意味着如果一个key以”{}”开头,那么一定会使用整个key来进行hash计算得到hash slot.这在使用二进制数据作为key时很有用.
加上hash tags计算的例外情况,HASH_SLOT函数的Ruby语音实现如下:
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
集群节点的属性
在集群中每个节点有个唯一的ID.节点ID在节点首次启动时获得,是一个160位的随机整数的16进制表示(通常使用/dev/urandom获得).节点会将他的ID保存在节点配置文件中,只要系统管理员没有删除掉节点配置文件或者使用CLUSTER RESET
进行硬重置,节点会一直使用相同的ID.
节点ID用来标识集群中的每个节点.节点可以改变IP地址但不需要同时改变节点ID.集群可以检测到ip/port的变化并且使用运作在集群总线上的gossip协议(流言协议)进行重新配置.
节点ID并非只保存在每个节点本身,同时也是集群唯一总是保持全局一致的数据.每个节点也有一些其他关联信息.一部分信息是关于这个特定节点的配置信息的,会跨越整个集群最终保持一致.另外一些信息,像上次被ping的时间,仅保存在节点本地.
每个节点维护如下一些关于集群其他节点的信息:节点ID,IP和PORT,标志(flag)集合,当flag=slave时,节点的master是什么,上次被ping的时间,和上次收到pong的时间,当前的节点配置版本(后面会解释),连接状态以及提供服务的hash slots集合.
在CLUSTER NODES命令的文档中有一份所有节点字段的详尽说明.
可以向集群中的任意节点发送CLUSTER NODES命以得到集群的状态和每个节点的信息.这些信息会按被查询集群节点的本地视图展示.
接下来是一个示例,假设有一个三个节点组成的小集群,将CLUSTER NODES命令发送到其中的一个主节点.
$ redis-cli cluster nodes
d1861060fe6a534d42d8a19aeb36600e18785e04 127.0.0.1:6379 myself - 0 1318428930 1 connected 0-1364
3886e65cc906bfd9b1f7e7bde468726a052d1dae 127.0.0.1:6380 master - 1318428930 1318428931 2 connected 1365-2729
d289c575dcbc4bdd2931585fd4339089e461a27d 127.0.0.1:6381 master - 1318428931 1318428931 3 connected 2730-4095
上面列出的不同字段按顺序为:节点ID,IP:PORT,标志,最后ping发送时间,最后pong接收时间,配置版本,连接状态,提供服务的slots.
集群总线
每个Redis-Cluster节点有一个附加的TCP端口用来接受来自其他集群节点的连接.这个端口由正常的接受来自客户端的TCP端口加上一个固定的偏移量得到.要获取Redis-Cluster端口,在正常的指令端口上加10000即可.例如一个Redis节点在端口6379上监听来自客户端的连接,集群总线端口16379也将被开放.
节点间的通信仅仅使用集群总线和集群总线协议:一个由不同类型和尺寸的帧组成的二进制协议.集群总线协议并没有公开文档化,因为该协议并不是用来令外部软件设备与集群节点通信的.然而有兴趣的读者可以通过阅读Redis-Cluster源文件中的cluster.h和cluster.c来获得更多关于集群总线协议的详细信息.
集群拓扑
Redis-Cluster是一个全网状结构,每个节点都与其他所有节点保持一个TCP链接.在一个由N个节点组成的集群中,每个节点有N-1个扇出TCP链接,和N-1个扇入连接.这些TCP连接不是按需创建的,而是全时保持活跃的.当一个节点期望在集群总线上得到一个响应ping的pong回复时,在等待足够长的时间后要标记节点为不可达之前,会尝试重新建立一个到那个节点的连接.
虽然Redis-Cluster节点形成了一个网状结构,但是用流言协议和配置更新机制避免了在过多的信息在节点间交换,所以被交换的消息数量并非指数增长的.
节点握手
节点总是在集群总线端上接受连接,甚至会回复来自授信的节点的ping.然而,如果发送节点不会认可为集群的一部分,则除ping外其发送的所有的数据包都将被丢弃.
只能通过如下两种方式令一个节点接受另外一个节点为集群的一部分:
- 节点使用一个MEET消息引荐自己.meet消息跟ping消息非常像,但是会强制接收者接受节点成为集群的一部分.一个节点只会在系统管理员使用
CLUSTER MEET ip port
命令时,才会发送MEET消息给其他节点. - 一个已经被信任的节点通过流言协议告知的其他节点也会被注册为集群的一部分.所以如果A认可B,且B认可C,则最终B将发送关于C的流言信息给A.此时A将会注册C为集群网络的一部分,并尝试连接C.
这意味着我们将节点加入多已连接的网络图中的任意位置,最终会自动连接为完全网络图.这意味着只要系统管理员是先强制创建了信任关系,则集群可以自动发现其他节点.
这个机制可以防止不同Redis-Cluster集群间由于偶然的ip地址变更或其他网络相关的事件混合在一起,令集群更加健壮.