集群cluster
通过配置文件中的cluster-enable 开启集群模式,如果为no则开启的服务器只是一个单机模式的服务器。
在刚开始每个节点都是独立的一个集群,可以通过命令cluster meet ip port命令来构建一个多节点的集群。
集群数据结构
集群中的每个节点都会创建一个clusternode结构,保存了当前节点的以前信息,例如节点创建时间,从服务器,主服务器,节点的ip端口等信息。其中有个属性link指向一个clusterlink结构,这个结构保存着连接到其它节点的信息,包括连接建立的时间,套接字描述符,输入缓冲区,输出缓冲区,与这个连接相关连的节点。最后每个节点都保存着一个clusterstate结构,记录着当前集群所处的状态。
meet命令的实现
通过在客户端发送meet的命令请求给节点A,节点A接收到客户端的meet命令请求,为B节点创建一个clusternode结构来记录节点B的结构,并且将其添加到A的clusterstate的nodes字典当中,发送命令meet给B,B一样创建一个clusternode结构记录A的状态,并将其添加到自己的clusterstate的nodes字典当中,顺利的话,B将回复pong给A,A收到pong命令就知道B收 到meet命令,节点A再回复一个ping给B,B知道A收到了命令回复,至此握手完成。之后A将节点B的信息通过Gossip协议传播给集群中的其他节点,让其它节点与B进行握手,最终经过一段时间后,节点B被集群所有节点认识。
槽指派
redis集群通过分片的方式来保存数据库中的键值对,集群的整个数据库被分为16348个槽(slot)。当数据库中的16348个槽都有节点在处理时,集群处于上线状态(up),相反如果有任意一个槽没有节点负责,则集群处于下线状态。
通过命令cluster addslots 1 2 3 number 将指定的槽分给某个节点负责。
记录节点的槽指派信息
clusternode中的slots属性和numslots记录节点负责的槽信息,其中slots是一个2进制的数组长度为2048字节,每个字节8位,总共16384位对应着16384个槽,当索引对应的位为1则表示负责这个槽。numslots属性记录着这个节点负责的槽的数量。
传播槽指派信息
每个节点除了用slots和numslots属性记录槽指派信息,还会将slots数值发送给其它的节点,告诉它们我这个节点负责哪些的槽。当节点收到别的节点发来的slot数组后,会在自身的clusterstate的nodes字典中查找到对应节点的clusternode结构中的slots属性,对其进行更新。在这样的情况下所有的节点都知道哪些槽由哪些节点负责。
记录集群所有的槽指派信息
clusterstate结构中的slots数组长度为16384,每个索引对应一个槽,每个项指向该负责该槽的clusternode节点,槽未分配则指向null。这个数组的作用是:当要知道哪个槽是哪个节点负责时不用遍历clusterstate.nodes字典,时间复杂度未0(1),而遍历nodes字典的话为o(n),n为nodes字典的长度。虽然有clusterstate中的数组slots记录槽的分派信息,但是每个clusternode节点中的slots节点也是有必要的,因为要知道哪个节点负责哪些槽时后者更加的高效。
cluster addslots命令的实现
该命令接受一个或多个槽作为参数,并将所有输入的槽指派给接收该命令的节点负责。
命令原理:先遍历是否所有的槽都未指派,如果有一个槽被指派了,返回错误。如果全部未指派,再次遍历将这些槽分配给该节点,将clusterstate中的slots数组中的对应项指向该节点。clusternode节点的slots数组中对应索引对应的值设置为1。之后在向其它的节点发送slots数组。
在集群中执行命令
计算某个键属于哪个槽
可以使用cluster keyslot key来看给定的键属于哪个槽。
计算这个的函数核心语句是: CRC16(key) & 16384
判断该槽是否是自己负责
通过cluster keyslot key 确定槽后,在clusterstate的slots数组中查找该槽是否是自己负责,如果不是则返回moved错误,指引客户端转向正确的节点。否者进行存储。
moved错误
格式: moved <slot> <ip>:<port> 其中slot为该键对应的槽,而ip和端口为负责该槽的节点。事实上一个客户端会与集群中的多个节点建立连接,所以当收到了moved错误后,客户端会根据错误提示的ip和端口,将连接转到该节点,并继续发送命令。
节点数据库的存储
节点保存键值对的方式与单机数据库保存键值对的方式一样,只不过前者只能使用0号数据库了。并且在clusterstate的slots_to_keys属性保存着一个跳跃表,分值为对应的槽,而memeber为对应的键。
重新分配
redis重新分配操作可以将已经被分配的槽从新分配给别的节点,并且节点中的值也会复制过去。重新分配可以线上进行,集群不需要下线,且可以正常进行命令的请求处理。
重新分配的实现原理
重新分配操作由redis-trib负责执行。步骤如下
1.redis-trib对目标节点发送cluster setslot <slot>importing<sourceid>命令,让目标节点准备好从原节点导入属于槽slot的键值对。
2.redis-tri对源节点发送cluster setslot <slot>migrating<target_id>命令,让源节点准备好将属于slot槽的键值对迁移到target_id
3.redis-tri向源节点发送cluster getkeysinslot <slot> <count>命令,获取最多count个属于槽slot的键值对的键名。
4.对于上一步获取的每一个keyname,redis-trib都向源节点发送migrate<targetip><target_port><key_name><timeout>将被选中的键原子的移向目标节点。
5.重复3和4步直到所有的键值对都被移动到目的节点。
6.redis-trib向集群中的任意一个节点发送cluster setslot <slot>node<target_id>命令,将槽slot指派给目标节点,这一指派消息会发送至整个集群。
Ask错误
ask错误是指在进行重新分片的过程中,一部分键值对在源节点,而部分键值对在目标节点,此时客户端发送一个命令请求,如果键在源节点,则不会报错,但是如果不在,那就检查migrating数组,看该槽是否正在进行移动,如果确实在迁移,则返回一个ask错误,指引客户连接到目标节点,连接到目标节点后,先发送一个asking命令,再发送原本想要执行的命令。
asking命令
唯一作用就是激活redis_asking标识
cluster setslot importing 命令的实现
clusterstate的importing_slots_from属性是一个数组,表示当前节点正在向源节点导入的槽,如果索引对应的项不为空,则表示当前节点正在从源节点导入该槽。并且数组的该项是指向源节点的clusternode结构。当我们发送cluster setslot importing命令时,目标节点会将对应槽的数组项指向源节点的clusternode结构。
cluster setslot migrating 命令的实现
clusterstate的migrating_slot_to数组记录了 当前节点正在迁移至其它节点。
如果数组对应索引的项不为null,则表示当前索引对应的槽正在移动到数组项所指向的clusternode结构。
通过命令cluster setslot slotid migrating target_id 将会使migrating数组的项指向目标节点。
moved错误和ask错误区别
前者是负责权已经完全转移,后者是部分转移。
复制与故障转移
设置从节点通过命令cluster replicate <node_id>,可以让接收命令的节点成为node_id节点的子节点,并开始对主节点进行复制。当执行这个命令时,从节点会在clusterstate的node字典中找到主节点,并且将自身几点的clusternode的属性slaveof指向主节点。以此来记录正在复制的主节点。然后节点会修改clusterstate.myself.flags属性,关闭原来的redis_node_master,打开redis_node_slave标识,表示这个节点已经成为了从节点。复制的过程与单机数据库一致。
一个节点从未从节点并开始复制主节点这个消息会发送给集群中的其他节点,告诉所有的节点,这个从节点是那个主节点的。集群中的所有节点都会在代表主节点的clusternode中的slaves和numslaves属性中记录正在恢复的从节点名单。
故障检查
节点会对在集群中的每一个节点都发送ping命令判断其是否在线,如果在规定的时间内没有得到恢复,则将代表该节点的clusternode结构中的flags属性设置为redis_node_pfail疑似下线。fail代表已经下线。
当一个节点收到某个节点疑似下线的消息后,会在自己的clusterstate.nodes中找到那个节点的clusternode结构,并且将其下线的信息添加到该节点的fail_reports链表里面。如果在负责槽的主节点当中有半数的节点认为其下线,则这个节点就fail下线。将这个节点标记为下线的节点会在集群内广播这个消息,让其它的节点标记这个下线节点。
故障转移
1.复制下线的主节点的从节点当中,会有一个节点被选中。
2.被选中的节点执行slaveof no one命令,成为新的主节点。
3.新的主节点会撤销所有对已下线节点的槽委派,并将所有的槽全部指派给自己。
4.新的主节点向集群内广播一条pong消息,目的是让其它的节点知道它已经成为了主节点,
5.新的主节点开始接收和自己负责处理槽的有关命令,故障转移完成。
选举新的主节点