画册设计网站安全的赣州网站建设

当前位置: 首页 > news >正文

画册设计网站,安全的赣州网站建设,万州建设工程信息官网,江西建设工程信息网站Redis集群是Redis提供的分布式数据库方案#xff0c;集群通过分片#xff08;sharding#xff0c;水平切分#xff09;来进行数据共享#xff0c;并提供复制和故障转移功能。 17.1 节点 一个Redis集群通常由多个节点#xff08;node#xff09;组成#xff0c;在刚开…Redis集群是Redis提供的分布式数据库方案集群通过分片sharding水平切分来进行数据共享并提供复制和故障转移功能。 17.1 节点 一个Redis集群通常由多个节点node组成在刚开始的时候每个节点都是相互独立的它们都处于一个只包含自己的集群中要组建一个真正可工作的集群我们必须将各个独立的节点连接起来构成一个包含多个节点的集群。 连接各个节点的工作可使用CLUSTER MEET命令来完成该命令格式如下 当客户端向一个节点node发送CLUSTER MEET命令可以让node节点与ip和port指定的节点进行握手handshake当握手成功时node节点会将ip和port所指定的节点添加到node节点当前所在的集群中。 例如现在有三个独立的节点127.0.0.1:7000、127.0.0.1:7001、127.0.0.1:7002下文省略IP地址直接使用端口号来区分各个节点我们首先用客户端连上节点7000通过发送SLUSTER NODE命令可以看到集群中只包含7000自己一个节点 redis-cli的-c选项可启用集群模式。 通过客户端向节点7000发送以下命令我们可以将节点7001添加到节点7000所在的集群里面 继续向节点7000发送以下命令可以将节点7002也添加到节点7000和节点7001所在的集群里 现在这个集群里包含了7000、7001、7002三个节点图17-1至17-5展示了这三个节点进行握手的过程 17.1.1 启动节点 一个节点就是一个运行在集群模式下的Redis服务器Redis服务器在启动时会根据cluster-enabled配置选项是否为yes来决定是否开启服务器的集群模式 节点运行在集群模式下的Redis服务器会继续使用所有在单机模式中使用的服务器组件比如 1.节点会继续使用文件事件处理器来处理命令请求和返回命令回复。 2.节点会继续使用时间事件处理器来执行serverCron函数而serverCron函数又会调用集群模式特有的clusterCron函数。clusterCron函数负责执行在集群模式下的常规操作如向集群中其他节点发送Gossip消息检查节点是否断线检查是否需要对下线节点进行自动故障转移等。 3.节点会继续使用数据库来保存价值对键值对依然是各种不同类型的对象。 4.节点会继续使用RDB持久化模块和AOF持久化模块来执行持久化工作。 5.节点会继续使用发布于订阅模块来执行PUBLISH、SUBSCRIBE等命令。 6.节点会继续使用复制模块来进行节点的复制工作。 7.节点会继续使用Lua脚本环境来执行客户端输入的Lua脚本。 此外节点会继续使用redisServer结构来保存服务器状态使用redisClient结构来保存客户端状态至于那些只有在集群模式下才会用到的数据节点将它们保存到了cluster.h/clusterNode结构、cluster.h/clusterLink结构、cluster.h/clusterState结构里。 17.1.2 集群数据结构 clusterNode结构保存了一个节点的当前状态比如节点的创建时间、节点名、节点当前的配置纪元、节点IP和端口号等。 每个节点都会使用一个clusterNode结构来记录自己的状态并为集群中其他所有节点包括主节点和从节点都创建一个相应的clusterNode结构以此来记录其他节点的状态 struct clusterNode {// 创建节点的时间mstime_t ctime;// 节点名由40个十六进制字符组成char name[REDIS_CLUSTER_NAMELEN];// 节点标志用来记录节点的角色如主节点或从节点和节点目前所处的状态如在线或下线uint64_t configEpoch;// 节点的IP地址char ip[REDIS_IP_STR_LEN];// 节点的端口号int port;// 保存到该节点的连接的相关信息如套接字描述符、连接创建时间等clusterLink *link;// … };clusterNode结构的link属性是一个clusterLink结构该结构保存了连接节点所需的信息如套接字描述符、输入输出缓冲区 typedef struct clusterLink {// 连接的创建时间mstime_t ctime;// TCP套接字描述符int fd;// 输出缓冲区保存着等待发送给其他节点的消息sds sndbuf;// 输入缓冲区保存着从其他节点收到的消息sds rcvbuf;// 与这个连接相关联的节点如果没有就为NULLstruct clusterNode *node; } clusterLink;redisClient和clusterLink结构都有自己的套接字描述符和输入输出缓冲区两者的区别在于redisClient结构中的套接字和缓冲区是用于连接客户端的而clusterLink的是用于连接节点的。 每个节点都保存着一个clusterState结构这个结构记录了在当前节点的视角下集群目前所处的状态例如集群是否在线集群包含多少个节点集群当前的纪元等 typedef struct clusterState {// 指向当前节点的指针clusterNode *myself;// 集群当前的配置纪元用于实现故障转移uint64_t currentEpoch;// 集群当前的状态在线or下线int state;// 集群中至少处理着一个槽的节点的数量int size;// 集群节点名单包括myself节点// 字典的键为节点名字典的值为节点对应的clusterNode结构dict *nodes;// … } clusterState;以前面介绍的7000、7001、7002三个节点为例图17-7展示了节点7000创建的clusterState结构这个结构从节点7000的角度记录了集群和集群中三个节点的当前状态为了空间考虑图中省略了clusterNode结构的一部分属性 上图中 1.结构的currentEpoch属性值为0表示集群当前的配置纪元为0。 2.结构的size属性值为0表示集群目前没有任何节点在处理槽因此结构的state属性值为REDIS_CLUSTER_FAIL这表示集群目前处于下线状态。 3.结构的nodes字典记录了集群目前包含的三个节点这三个节点分别由三个clusterNode结构表示其中myself指针指向代表节点7000的clusterNode结构而字典中另外两个指针分别代表节点7001和节点7002的clusterNode结构这两个节点是节点7000已知的在集群中的其他节点。 4.三个节点的clusterNode结构的flags属性都是REDIS_NODE_MASTER说明三个节点都是主节点。 节点7001和7002也会创建类似的clusterState结构 1.不过在节点7001创建的clusterState结构中myself指针将指向代表节点7001的clusterNode结构而节点7000和7002则是集群中的其他节点。 2.而在节点7002创建的clusterState结构中myself指针将指向代表节点7002的clusterNode结构而节点7000和7001则是集群中的其他节点。 17.1.3 CLUSTER MEET命令的实现 通过向节点A发送CLUSTER MEET命令客户端可以让接收命令的节点A将另一个节点B添加到节点A所在的集群里 收到命令的节点A将与节点B进行握手handshake以此来确认彼此的存在并为将来的进一步通信做准备 1.节点A会为节点B创建一个clusterNode结构并将该结构添加到自己的clusterState.nodes字典里。 2.之后节点A将根据CLUSTER MEET命令给定的IP和端口向节点B发送一条MEET消息message。 3.如果一切顺利节点B将收到节点A发送的MEET消息节点B会为节点A创建一个clusterNode结构并将该结构添加到自己的clusterState.nodes字典里。 4.之后节点B将向节点A返回一条PONG消息。 5.如果一切顺利节点A将接收到节点B返回的PONG消息通过这条PONG消息节点A可以知道节点B已经成功地接收到了自己发送的MEET消息。 6.之后节点A将向节点B返回一条PING消息。 7.如果一切顺利节点B将接收到节点A返回的PING消息通过这条PING消息节点B可以知道节点A已经成功地接收到了自己返回的PONG消息握手完成。 图17-8展示了以上步骤描述的握手过程 之后节点A会将节点B的信息通过Gossip协议传播给集群中的其他节点让其他节点也与节点B进行握手最终经过一段时间后节点B会被集群中所有节点认识。 17.2 槽指派 Redis集群通过分片的方式来保存数据库中的键值对集群的整个数据库被分为16384个槽slot数据库中的每个键都属于这16384个槽的其中一个集群中的每个节点可以处理0个或最多16384个槽。 当数据库中的16384个槽都有节点在处理时集群处于上线状态ok相反地如果数据库中有任何一个槽没有得到处理那么集群处于下线状态fail。 在上一节我们使用CLUSTER MEET命令将7000、7001、7002三个节点连接到了同一个集群里不过这个集群目前仍处于下线状态因为集群中的三个节点都没有处理任何槽 通过向节点发送CLUSTER ADDSLOTS命令我们可以将一个或多个槽指派assign给节点负责 例如执行以下命令可将槽0至5000指派给节点7000负责 为了让7000、7001、7002三个节点所在的集群进入上线状态我们继续执行以下命令将槽5001至10000指派给节点7001负责 然后将槽10001至16383指派给7002负责 当以上三个CLUSTER ADDSLOTS命令都执行完毕后数据库中的16384个槽都已经被指派给了相应的节点集群进入上线状态 17.2.1 记录节点的槽指派信息 clusterNode结构的slots和numslot属性记录了节点负责处理哪些槽 struct clusterNode {// …unsigned char slots[163848];int numslots;// … };slots属性是一个二进制位数组bit array这个数组的长度为16384/82048字节共包含16384个二进制位。 Redis以0为起始索引16383为终止索引对slots数组中的16384个二进制位进行编号并根据索引i上的二进制位的值来判断节点是否负责处理槽i 1.如果slots数组在索引i上的二进制位的值为1那么表示节点负责处理槽i。 2.如果slots数组在索引i上的二进制位的值为0那么表示节点不负责处理槽i。 图17-9展示了一个slots数组示例这个数组索引0到7上的二进制位都为1其余所有二进制位都为0这表示节点负责槽0至7 图17-10展示了另一个slots数组示例这个数组索引1、3、5、8、9、10上的二进制位的值为1而其余所有二进制位的值为0这表示节点负责处理槽1、3、5、8、9、10 因为取出和设置slots数组中的任意一个二进制位的值的时间复杂度仅为O(1)所以对于一个给定节点的slots数组来说程序检查节点是否负责处理某个槽由或者将某个槽指派给节点负责这两个动作的时间复杂度都是O(1)。 至于numslots属性则记录节点负责处理的槽数量即slots数组中值为1的二进制位的数量。 比如对于图17-9所示的slots数组来说节点处理的槽数量为8而对于图17-10所示的slots数组来说节点处理的槽数量为6。 17.2.2 传播节点的槽指派信息 一个节点除了会将自己负责的槽记录在clusterNode结构的slots和numslots属性外还会将自己的slots数组通过消息发送给集群中的其他节点以此来告知其他节点自己目前负责处理哪些槽。 例如对于前面展示的包含7000、7001、7002三个节点的集群来说 1.节点7000会通过消息向节点7001和7002发送自己的slots数组以此来告知这两个节点自己负责处理槽0至5000如图17-11所示 2.节点7001和节点7002同理 当节点A通过消息从节点B那里接收到节点B的slots数组时节点A会在自己的clusterState.nodes字典中查找节点B对应的clusterNode结构并对结构中的slots数组进行更新。 因为集群中的每个节点都会将自己的slots数组通过消息发送给集群中的其他节点且每个接收到slots数组的节点都会将数组保存到相应节点的clusterNode结构里因此集群中的每个节点都会知道数据库中的16384个槽分别被指派给了集群中的哪些节点。 17.2.3 记录集群所有槽的指派信息 clusterState结构中的slots数组记录了集群中所有16384个槽的指派信息 typedef struct clusterState {// …clusterNode *slots[16384];// … } clusterState;slots数组包含16384个项每个数组项都是一个指向clusterNode结构的指针 1.如果slots[i]指针指向NULL那么表示槽i尚未指派给任何节点。 2.如果slots[i]指针指向一个clusterNode结构那么表示槽i已经指派给了clusterNode结构所代表的节点。 例如对于7000、7001、7002三个节点来说它们的clusterState结构的slots数组将会是图17-14所示的样子 如果只将槽指派信息保存在各个节点的clusterNode.slots数组里会出现一些无法高效地解决的问题而clusterState.slots数组的存在解决了这些问题 1.如果节点只使用clusterNode.slots数组来记录槽的指派信息那么为了知道槽i是否已经被指派或槽i被指派给了哪个节点程序需要遍历clusterState.nodes字典中的所有clusterNode结构检查这些结构的slots数组直到找到负责槽i的节点为止这个过程的时间负责度为O(N)N为clusterState.nodes字典保存的clusterNode结构的数量。 2.而通过将所有槽的指派信息保存在clusterState.slots数组里程序要检查槽i是否已被指派又或者取得负责处理槽i的节点只需访问clusterState.slots[i]的值即可这个操作的时间复杂度仅为O(1)。 例如对于图17-14所示的slots数组来说如果程序需要知道槽10002被指派给了哪个节点那么只要访问数组slots[10002]就可以马上知道槽10002被指派给了节点7002如图17-15所示 要说明的一点是虽然clusterState.slots数组记录了集群中所有槽的指派信息但使用clusterNode结构的slots数组来记录单个节点的槽指派信息仍然是有必要的 1.因为当程序需要将某个节点的槽指派信息通过消息发送给其他节点时程序只需要将相应节点的clusterNode.slots数组整个发送出去就可以了。 2.另一方面如果Redis不使用clusterNode.slots数组而单独使用clusterState.slots数组的话那么每次要将节点A的槽指派信息传播给其他节点时程序必须先遍历整个clusterState.slots数组记录节点A负责处理哪些槽然后才能发送节点A的槽指派信息这比直接发送clusterNode.slots数组要麻烦和低效得多。 17.2.4 CLUSTER ADDSLOTS命令的实现 CLUSTER ADDSLOTS命令接受一个或多个槽作为参数并将所有输入的槽指派给接收该命令的节点负责 CLUSTER ADDSLOTS命令的实现可用以下伪代码表示 def CLUSTER_ADDSLOTS(*all_input_slots):# 遍历所有输入槽for i in all_input_slots:# 如果有一个槽已经被指派给了某个节点# 那么向客户端返回错误并终止命令执行if clusterState.slots[i] ! NULL:reply_error()return# 如果所有输入槽都是未指派槽# 那么再次遍历所有输入槽将这些槽指派给当前节点for i in all_input_slots:# 设置clusterState结构的slots数组# 将slots[i]的指针指向代表当前节点的clusterNode结构clusterState.slots[i] clusterState.myself# 访问代表当前节点的clusterNode结构的slots数组# 将数组索引i上的二进制位设为1setSlotBit(clusterState.myself.slots, i)例如图17-16展示了一个节点的clusterState结构clusterState.slots数组中的所有指针都指向NULL且clusterNode.slots数组中的所有二进制位的值都是0这说明当前节点没有被指派任何槽且集群中所有槽都是未指派的 当客户端对图17-16所示的节点执行命令 将槽1和2指派给节点后节点的clusterState结构将被更新成图17-17所示的样子 上图中 1.clusterState.slots数组在索引1和2上的指针指向了代表当前节点的clusterNode结构。 2.clusterNode.slots数组在索引1和2上的位被设置成了1。 最后在CLUSTER ADDSLOTS命令执行完毕后节点会通过发送消息告知集群中的其他节点自己目前正在负责处理哪些槽。 17.3 在集群中执行命令 在对数据库中的16384个槽都进行了指派后集群会进入上线状态这时客户端就可以向集群中的节点发送数据命令了。 当客户端向节点发送与数据库键有关的命令时接收命令的节点会计算出命令要处理的数据库键属于哪个槽并检查这个槽是否指派给了自己 1.如果键所在的槽正好指派给了当前节点那么节点直接执行这个命令。 2.如果键所在的槽没有指派给当前节点那么节点会向客户端返回一个MOVED错误指引客户端转向redirect至正确的节点并再次发送之前想要执行的命令。 例如我们之前提到的由7000、7001、7002三个节点组成的集群中用客户端连上节点7000并发送以下命令那么命令会直接被节点7000执行 因为键date所在的槽2022正是由节点7000负责处理的。 但如果我们执行以下命令那么客户端会先被转向至节点7001然后再执行命令 因为键msg所在的槽6257是由节点7001负责处理的而不是最初接收命令的节点7000 1.当客户端第一次向节点7000发送SET命令时节点7000会向客户端返回MOVED错误指引客户端转向至节点7001。 2.当客户端转向到节点7001后客户端重新向节点7001发送SET命令这个命令会被节点7001执行。 17.3.1 计算键属于哪个槽 节点使用以下算法计算给定键key属于哪个槽 def slot_number(key):return CRC16(key) 16383其中CRC16(key)语句用于计算键key的CRC-16校验和而 16383用于计算出一个介于0至16383之间的整数作为键key的槽号。 使用CLUSTER KEYSLOT key命令可查看一个给定键属于哪个槽 CLUSTER KEYSLOT命令就是通过调用上面给出的槽分配算法来实现的以下是该命令的伪代码实现 def CLUSTER_KEYSLOT(key):# 计算槽号slot slot_number(key)# 将槽号返回给客户端reply_client(slot)17.3.2 判断槽是否由当前节点负责处理 当节点计算出键所处的槽i后节点会检查自己在clusterState.slots数字中的项i判断键所在的槽是否等于clusterState.myself 1.如果等于说明槽i由当前节点负责。 2.如果不等于说明槽i不由当前节点负责节点会根据clusterState.slots[i]指向的clusterNode结构所记录的节点IP和端口向客户端返回MOVED错误指引客户端转向至正在处理槽i的节点。 例如图17-19为节点7000的clusterState结构 上图中 1.当客户端向节点7000发送命令SET date 2013-12-31的时候节点首先计算出键date属于槽2022然后检查得出clusterState.slots[2022]等于clusterState.myself这说明槽2022正是由节点7000负责于是节点7000直接执行这个SET命令并将结果返回给发送命令的客户端。 2.当客户端向节点7000发送命令SET msg happy new year!的时候节点首先计算出键msg属于槽6257然后检查clusterState.slots[6527]是否等于clusterState.myself结果发现两者并不相等这说明槽6257并非由节点7000负责处理于是节点7000访问clusterState.slots[6257]所指向的clusterNode结构并根据结构中记录的IP和端口向客户端返回错误MOVED 6257 127.0.0.1:7001指引节点转向至正在负责处理槽6257的节点7001。 17.3.3 MOVED错误 当节点发现键所在的槽并非由自己负责处理时节点就会向客户端返回一个MOVED错误指引客户端转向至正在负责槽的节点。 MOVED错误的格式为 其中slot为键所在的槽而ip和port是负责处理槽slot的节点的IP和端口。例如错误 表示槽10086正由IP为127.0.0.1端口为7002的节点负责。 当客户端接收到节点返回的MOVED错误时会根据MOVED错误中提供的IP和端口转向至负责处理槽slot的节点并向该节点重新发送之前想要执行的命令。以客户端从节点7000转向至7001的情况作为例子 图17-20展示了客户端向节点7000发送SET命令并获得MOVED错误的过程 而图17-21展示了客户端根据MOVED错误转向至节点7001并重新发送SET命令的过程 一个集群客户端通常会与集群中的多个节点创建套接字连接而所谓的节点转向实际上就是换一个套接字来发送命令。 如果客户端尚未与想要转向的节点创建套接字连接那么客户端会先根据MOVED错误提供的IP地址和端口号来连接节点然后再进行转向。 集群模式的redis-cli客户端在接收到MOVED错误时并不会打印出error字符串而是根据MOVED错误自动进行节点转向并打印出转向信息 但如果我们使用单机stand alone模式的redis-cli客户端再次向节点7000发送相同命令那么MOVED错误就会被客户端打印出来 这是因为单机模式的redis-cli客户端不清楚MOVED错误的作用所以它会直接将MOVED错误打印出来而不会进行自动转向。 17.3.4 节点数据库的实现 集群节点保存键值对以及处理过期时间键值对的方式与第9章里介绍的单机Redis服务器的处理方式完全相同。 节点和单机服务器在数据库方面的一个区别是节点只能使用0号数据库而单机Redis服务器没有这一限制。 例如图17-22展示了节点7000的数据库状态 数据库中包含列表键lst、哈希键book、字符串键date其中键lst和book带有过期时间。 节点还会用clusterState结构中的slots_to_keys跳跃表来保存槽和键之间的关系 typedef struct clusterState {// …zskiplist *slots_to_keys;// … } clusterState;slots_to_keys跳跃表每个节点的分值score都是一个槽号而每个节点的成员member都是一个数据库键 1.每当节点往数据库中添加一个新的键值对时节点就会将这个键以及键的槽号关联到slots_to_keys跳跃表。 2.当节点删除数据库中的某个键值对时节点就会在slots_to_keys跳跃表解除被删除键与槽号的关联。 例如对于图17-22所示的数据库节点7000将创建类似图17-23所示的slots_to_keys跳跃表 上图中 1.键book所在跳跃表节点的分值为1337.0这表示键book所在的槽为1337。 2.键date所在跳跃表节点的分值为2022.0这表示键date所在的槽为2022。 3.键lst所在跳跃表节点的分值为3347.0这表示键lst所在的槽为3347。 通过在slots_to_keys跳跃表中记录数据库键所属的槽节点可以很方便地对属于某个或某些槽的所有数据库键进行批量操作例如命令CLUSTER GETKEYINSLOT slot count可以返回最多count个属于槽slot的数据库键而这个命令就是通过遍历slots_to_keys跳跃表来实现的。 17.4 重新分片 Redis集群的重新分片操作可以将任意数量已经指派给某个节点源节点的槽改为指派给另一个节点目标节点且相关槽所属的键值对也会从源节点被移动到目标节点。 重新分片操作可以在线online进行在重新分片的过程中集群不需要下线且源节点和目标节点都可以继续处理命令请求。 例如对于之前提到的包含7000、7001、7002三个节点的集群来说我们可以向这个集群添加一个IP为127.0.0.1端口为7003的节点后面简称为节点7003 通过重新分片操作将原本指派给节点7002的槽15001至16383改为指派给节点7003。 以下是重新分片操作执行后节点的槽分配状态 重新分片的实现原理 Redis集群的重新分片操作是由Redis的集群管理软件redis-trib负责执行的Redis提供了进行重新分片所需的命令而redis-trib则通过向源节点和目标节点发送命令来进行重新分片操作。 redis-trib对集群的单个槽slot进行重新分片的步骤如下 1.redis-trib对目标节点发送CLUSTER SETSLOT slot IMPORTING source_id命令让目标节点准备好从源节点导入import属于槽slot的键值对。 2.redis-trib对源节点发送CLUSTER SETSLOT slot MIGRATING target_id命令让源节点准备好将属于槽slot的键值对迁移migrate至目标节点。 3.redis-trib向源节点发送CLUSTER GETKEYINSLOT slot count命令获得最多count个属于槽slot的键值对的键名key name。 4.对于步骤3获得的每个键名redis-trib都向源节点发送一个MIGRATE target_ip target_port key_name 0 timeout命令将被选中的键原子地从源节点迁移至目标节点。 5.重复执行步骤3和4直到源节点保存的所有属于槽slot的键值对都被迁移至目标节点为止。每次迁移键的过程如图17-24所示 6.redis-trib向集群中的任意一个节点发送CLUSTER SETSLOT slot NODE target_id命令将槽slot指派给目标节点这一指派信息会通过消息发送至整个集群最终集群中所有节点都会知道槽slot已经指派给了目标节点。 如果重新分片涉及多个槽那么redis-trib将对每个槽分别执行上面给出的步骤。 17.5 ASK错误 在进行重新分片期间源节点向目标节点迁移一个槽的过程中可能会出现这样一种情况属于被迁移槽的一部分键值对保存在源节点里另一部分键值对保存在目标节点里。 当客户端向源节点发送一个与数据库键有关的命令并且命令要处理的数据库键恰好属于正在被迁移的槽时 1.源节点会先在自己的数据库里查找指定的键如果找到了就直接执行客户端发送的命令。 2.如果源节点没能在自己的数据库里找到指定的键那么这个键可能已经被迁移到了目标节点源节点将向客户端返回一个ASK错误指引客户端转向正在导入槽的目标节点客户端会向目标节点再次发送之前想要执行的命令。 例如节点7002正在向节点7003迁移槽16198这个槽包含is和love两个键其中键is还留在节点7002而键love已经被迁移到了节点7003。 如果我们向节点7002发送关于键is的命令那么这个命令会直接被节点7002执行 而如果我们向节点7002发送关于键love的命令那么客户端会先被转向至节点7003然后再次执行命令 和接到MOVED错误时的情况类似集群模式的redis-cli在街道ASK错误时也不会打印error字符串而是自动根据错误提供的IP和端口进行转向动作。如果想看到节点发送的ASK错误的error字符串可使用单机模式的redis-cli客户端 在写这篇文章的时候集群模式的redis-cli并未支持ASK自动转向上面展示的ASK自动转向行为实际上是根据MOVED自动转向行为虚构出来的。因此当集群模式的redis-cli真正支持ASK自动转向时它的行为和上面展示的可能有所不同。 17.5.1 CLUSTER SETSLOT IMPORTING命令的实现 clusterState结构的importing_slots_from数组记录了当前节点正从其他节点导入的槽 typedef struct clusterState {// …clusterNode *importing_slots_from[16384];// … } clusterState;如果importing_slots_from[i]的值不为NULL而是指向一个clusterNode结构那么表示当前节点正从clusterNode所代表的节点导入槽i。 在对集群进行重新分片的时候向目标节点发送命令 可以将目标节点clusterState.importing_slots_from[i]的值设为source_id所代表的节点的clusterNode结构。 例如客户端向节点7003发送以下命令 那么节点7003的clusterState.importing_slots_from数组将变成图17-27所示的样子 17.5.2 CLUSTER SETSLOT MIGRATING命令的实现 clusterState结构的migrating_slots_to数组记录了当前节点正在迁移至其他节点的槽 typedef struct clusterState {// …clusterNode *migrating_slots_to[16384];// … } clusterState;如果migrating_slots_to[i]的值不为NULL而是指向一个clusterNode结构那么表示当前节点正在将槽i迁移至clusterNode所代表的节点。 在对集群进行重新分片时向源节点发送命令 可以将源节点clusterState.migrating_slots_to[i]的值设为target_id所代表节点的clusterNode结构。 例如如果客户端向节点7002发送以下命令 那么节点7002的clusterState.migrating_slots_to数组将变成图17-28所示的样子 17.5.3 ASK错误 如果节点收到一个关于键key的命令请求且键key所属的槽i正好就指派给了这个节点那么节点会尝试在自己的数据库里查找键key如果找到了节点就直接执行客户端发送的命令。 如果节点没有在自己的数据库里找到键key那么节点会检查自己的clusterState.migrating_slots_to[i]看键key所属的槽i是否正在进行迁移如果槽i正在进行迁移那么节点会向客户端发送一个ASK错误引导客户端到正在导入槽i的节点去查找键key。 例如假设在节点7002向节点7003迁移槽16198期间有一个客户端向节点7002发送命令 因为键love正好属于槽16198所以节点7002会首先在自己的数据库中查找键love但并没有找到通过检查自己的clusterState.migrating_slots_to[16198]节点7002发现自己正在将槽16198迁移至节点7003于是它向客户端返回错误 这个错误表示客户端可以尝试到IP为127.0.0.1端口为7003的节点去执行和槽16198有关的操作如图17-29所示 接到ASK错误的客户端会根据错误提供的IP和端口转向至正在导入槽的目标节点然后首先向目标节点发送一个ASKING命令之后再重新发送原本想要执行的命令。 以前面的例子来说当客户端收到节点7002返回的以下错误时 客户端会转向至节点7003首先发送命令 然后再次发送命令 并获得回复 17.5.4 ASKING命令 对于被转向的节点来说ASKING命令唯一要做的就是打开发送该命令的客户端的REDIS_ASKING标识以下是该命令的伪代码实现 def ASKING():# 打开标识client.flags | REDIS_ASKING# 向客户端返回OK回复reply(OK)一般情况下如果客户端向节点发送一个关于槽i的命令而槽i又没有指派给这个节点的话那么节点将向客户端返回一个MOVED错误但如果节点的clusterState.importing_slots_from[i]显示节点正在导入槽i并且发送命令的客户端带有REDIS_ASKING标识那么节点将破例执行这个关于槽i的命令一次图17-31展示了这个判断过程 当客户端接收到ASK错误并转向至正在导入槽的节点时客户端会先向节点发送一个ASKING命令然后才重新发送想要执行的命令这是因为如果客户端不发送ASKING命令而直接发送想要执行的命令的话那么客户端发送的命令将被节点拒绝执行并返回MOVED错误。 例如我们可以使用普通模式的redis-cli客户端向正在导入槽16198的节点7003发送以下命令 虽然节点7003正在导入槽16198但槽16198目前仍然被指派给节点7002所以节点7003会向客户端返回MOVED错误指引客户端转向至节点7002。 但是如果我们在发送GET命令前先向节点发送一个ASKING命令那么这个GET命令就会被节点7003执行 另外要注意的是客户端的REDIS_ASKING标识是一个一次性标识当节点执行了一个带有REDIS_ASKING表示的客户端发送的命令后客户端的REDIS_ASKING标识就会被移除。 例如我们在成功执行GET命令后再次向节点7003发送GET命令那么第二次发送的GET命令将执行失败因为这时客户端的REDIS_ASKING标识已经被移除 17.5.5 ASK错误和MOVED错误的区别 ASK错误和MOVED错误都会导致客户端转向它们的区别在于 1.MOVED错误代表槽的负责权已经从一个节点转移到了另一个节点在客户端收到关于槽i的MOVED错误后客户端每次遇到关于槽i的命令请求时都可以直接将命令请求发送至MOVED错误所指向的节点因为该节点就是目前负责槽i的节点。 2.ASK错误只是两个节点在迁移槽的过程中使用的一种临时措施在客户端收到关于槽i的ASK错误后客户端只会在接下来的一次命令请求中将关于槽i的命令请求发送至ASK错误所指示的节点但这种转向不会对客户端今后发送关于槽i的命令请求产生任何影响客户端仍然会将关于槽i的命令请求发送至目前负责处理槽i的节点除非ASK错误再次出现。 17.6 复制与故障转移 Redis集群中的节点分为主节点master和从节点slave其中主节点用于处理槽而从节点则用于复制某个主节点并在被复制的主节点下线时代替下线的主节点继续处理命令请求。 例如对于包含7000、7001、7002、7003四个主节点的集群来说我们可以将节点7004、7005添加到集群里并将这两个节点设为节点7000的从节点如图17-32所示图中双圆形表示主节点单圆形表示从节点 表17-1记录了集群各个节点的当前状态以及它们正在做的工作 如果此时节点7000进入下线状态那么集群中仍在正常运作的几个主节点将在节点7000的两个从节点——节点7004和7005中选出一个节点作为新的主节点这个新的主节点将接管原来节点7000负责处理的槽并继续处理客户端发送的命令请求。 例如节点7004被选中为新的主节点那么节点7004将接管原来由节点7000负责处理的槽0至5000节点7005也会从原来的复制节点7000改为复制节点7004如图17-33所示图中用虚线包围的节点为已下线节点 表17-2记录了在对节点7000进行故障转移后集群各个节点的当前状态以及他们正在做的工作 如果在故障转移完成后下线的节点7000重新上线那么它将成为节点7004的从节点如图17-34所示 表17-3展示了节点7000复制节点7004后集群中各个节点的状态 17.6.1 设置从节点 向一个节点发送命令 可以让接收命令的节点成为node_id所指定节点的从节点并开始对主节点进行复制 1.接收到该命令的节点首先会在自己的clusterState.nodes字典中找到node_id所对应节点的clusterNode结构并将自己的clusterState.myself.slaveof指针指向这个结构以此来记录这个节点正在复制的主节点 struct clusterNode {// …// 如果这是一个从节点那么指向主节点struct clusterNode *slaveof;// … };2.然后节点会修改自己在clusterState.myself.flags中的属性关闭原本的REDIS_NODE_MASTER表示打开REDIS_NODE_SLAVE标识表示这个节点已经由原来的主节点变成了从节点。 3.最后节点会调用复制代码并根据clusterState.myself.slaveof指向的clusterNode结构所保存的IP和端口对主节点进行复制。因为节点的复制功能和单机Redis服务器的复制功能使用了相同的代码所以让从节点复制主节点相当于向从节点发送命令SLAVEOF master_ip master_port。 图17-35展示了节点7004在复制节点7000时的clusterState结构 上图中 1.clusterState.myself.flags属性值为REDIS_NODE_SLAVE表示节点7004是一个从节点。 2.clusterState.myself.slaveof指针指向代表节点7000的结构表示节点7004正在复制的主节点为节点7000。 一个节点成为从节点并开始复制某个主节点这一信息会通过消息发送给集群中的其他节点最终集群中的所有节点都会知道某个从节点正在复制某个主节点。 集群中的所有节点都会在代表主节点的clusterNode结构的slaves属性和numslaves属性中记录正在复制这个主节点的从节点名单 struct clusterNode {// …// 正在复制这个主节点的从节点数量int numslaves;// 一个数组// 每个数组项指向一个正在复制这个主节点的从节点的clusterNode结构struct clusterNode **slaves;// … };例如图17-36记录了节点7004和7005成为节点7000的从节点后集群中的各个节点为节点7000创建的clusterNode结构的样子 上图中 1.代表节点7000的clusterNode结构的numslaves属性的值为2说明有两个从节点正在复制节点7000。 2.代表节点7000的clusterNode结构的slaves数组的两个项分别指向代表节点7004和7005的clusterNode结构这说明节点7000的两个从节点分别是节点7004和7005。 17.6.2 故障检测 集群中的每个节点都会定期向集群中的其他节点发送PING消息以此来检测对方是否在线如果接收PING消息的节点没有在规定时间内向发送PING消息的节点返回PONG消息那么发送PING消息的节点就会将接收PING消息的节点标记为疑似下线probable failPFAIL。 例如节点7001向节点7000发送了一条PING消息但是节点7000没有在规定时间内向节点7001返回一条PONG消息那么节点7001就会在自己的clusterState.nodes字典中找到节点7000所对应的clusterNode结构并在结构的flags属性中打开REDIS_NODE_PFAIL标识以此表示节点7000进入了疑似下线状态如图17-37所示 集群中的各个节点会通过互相发送消息的方式来交换集群中各个节点的状态信息例如某个节点是处于在线状态、疑似下线状态PFAIL还是已下线状态FAIL。 当一个主节点A通过消息得知主节点B认为主节点C进入了疑似下线状态时主节点A会在自己的clusterState.nodes字典中找到主节点C所对应的clusterNode结构并将主节点B的下线报告failure report添加到主节点C的clusterNode结构的fail_reports链表里 struct clusterNode {// …// 一个链表记录了所有其他节点对该节点的下线报告list *fail_reports;// … };每个下线报告由一个clusterNodeFailReport结构表示 struct clusterNodeFailReport {// 报告目标节点已经下线的节点struct clusterNode *node;// 最后一次从node节点收到下线报告的时间// 程序使用这个时间戳来检查下线报告是否过期// 与当前时间相差太久的下线报告会被删除mstime_t time; // 此处的typedef的用法不常见更多是放在struct关键字前面 } typedef clusterNodeFailReport;例如主节点7001在收到主节点7002、主节点7003发送的消息后得知主节点7002和主节点7003都认为主节点7000进入了疑似下线状态那么主节点7001将为主节点7000创建图17-38所示的下线报告 如果在一个集群里半数以上负责处理槽的主节点都将某个主节点x报告为疑似下线那么这个主节点x将被标记为已下线FAIL将主节点x标记为已下线的节点会向集群广播一条关于主节点x的FAIL消息所有收到这条FAIL消息的节点都会立即将主节点x标记为已下线。 例如对于图17-38所示的下线报告来说主节点7002和主节点7003都认为主节点7000进入了下线状态且主节点7001也认为主节点7000进入了疑似下线状态代码主节点7000的结构打开了REDIS_NODE_PFAIL标识综合起来在集群四个负责处理槽的主节点里有三个都将主节点7000标记为下线数量超过了半数所以主节点7001会将主节点7000标记为已下线并向集群广播一条关于主节点7000的FAIL消息如图17-39所示 17.6.3 故障转移 当一个从节点发现自己正在复制的主节点进入了已下线状态时从节点将开始对下线主节点进行故障转移以下是故障转移的执行步骤 1.复制下线主节点的所有从节点里会有一个从节点被选中。 2.被选中的从节点会执行SLAVEOF no one命令成为新的主节点。 3.新的主节点会撤销所有对已下线主节点的槽指派并将这些槽全部指派给自己。 4.新的主节点向集群广播一条PONG消息这条PONG消息可以让集群中的其他节点立即知道这个节点已经由从节点变成了主节点且这个主节点已经接管了原本由已下线节点负责处理的槽。 5.新的主节点开始接收和自己负责处理的槽有关的命令请求故障转移完成。 17.6.4 选举新的主节点 新的主节点是通过选举产生的。 以下是集群选举新的主节点的方法 1.集群的配置纪元是一个自增计数器它的初始值为0。 2.当集群中的某个节点开始一次故障转移操作时集群配置纪元的值会被增一。 3.对于每个配置纪元集群里每个负责处理槽的主节点都有一次投票的机会而第一个向主节点要求投票的从节点将获得主节点的投票。 4.当从节点发现自己正在复制的主节点进入已下线状态时从节点会向集群广播一条CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息要求所有收到这条消息、并且具有投票权的主节点向这个从节点投票。 5.如果一个主节点具有投票权它正在负责处理槽且这个主节点尚未投票给其他从节点那么主节点将向要求投票的从节点返回一条CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息表示这个主节点支持从节点成为新主节点。 6.每个参与选举的从节点都会接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息并根据自己收到了多少条这种消息来统计自己获得了多少主节点的支持。 7.如果集群里有N个具有投票权的主节点那么当一个从节点收集到大于等于N/21张支持票时这个从节点就会当选为新主节点。 8.因为在每一个配置纪元里每个具有投票权的主节点只能投一次票所以如果有N个主节点进行投票那么具有N/21张支持票的从节点只会有一个这确保了新的主节点只会有一个。 9.如果在一个配置纪元里没有从节点能收集到足够多的支持票那么集群进入一个新的配置纪元并再次进行选举直到选出新的主节点为止。 这个选举主节点的方法和第16章介绍的选举领头Sentinel的方法非常相似因为两者都是基于Raft算法的领头选举leader election方法来实现的。 17.7 消息 集群中的各个节点通过发送和接收消息message来进行通信我们称发送消息的节点为发送者sender接收消息的节点为接收者receiver如图17-40所示 节点发送的消息主要有以下五种 1.MEET消息当发送者接到客户端发送的CLUSTER MEET命令时发送者会向接收者发送MEET消息请求接收者加入到发送者当前所处的集群里。 2.PING消息集群里的每个节点默认每隔一秒就会从已知节点列表中随机选出五个节点然后对这五个节点中最长时间没有发送过PING消息的节点发送PING消息以此来检测被选中的节点是否在线。此外如果节点A最后一次收到节点B发送的PONG消息的时间距离当前时间已经超过了节点A的cluster-node-timeout选项设置时长的一半那么节点A也会向节点B发送PING消息这可以防止节点A因为长时间没有随机选中节点B作为PING消息的发送对象而导致对节点B的消息更新滞后。 3.PONG消息当接收者收到发送者发来的MEET或PING消息时为了向发送者确认这条MEET或PING消息已到达接收者会向发送者返回一条PONG消息。另外一个节点也可以通过向集群广播自己的PONG消息来让集群中的其他节点刷新关于这个节点的认识例如当一次故障转移操作成功执行后新的主节点会向集群广播一条PONG消息以此来让集群中的其他节点立即知道这个节点已经变成了主节点并且接管了已下线节点负责的槽。 4.FAIL消息当一个主节点A判断另一个主节点B已经进入FAIL状态时节点A会向集群广播一条关于节点B的FAIL消息所有收到这条消息的节点都会立即将节点B标记为已下线。 5.PUBLISH消息当节点接收到一个PUBLISH命令时节点会执行这个命令并向集群广播一条PUBLISH消息所有接收到这条PUBLISH消息的节点都会执行相同的PUBLISH命令。 一条消息由消息头header和消息正文data组成。 17.7.1 消息头 每个消息头都由一个cluster.h/clusterMsg结构表示 typedef struct {// 消息的长度包括这个消息头和消息正文的长度uint32_t totlen;// 消息的类型uint16_t type;// 只在发送MEET、PING、PONG这三种携带Gossip协议的消息时使用// Gossip协议中携带了多少个其他节点的信息uint16_t count;// 发送者所处的配置纪元uint64_t currentEpoch;// 如果发送者是一个主节点那么这里记录的是发送者的配置纪元// 如果发送者是一个从节点那么这里记录的是发送者正在复制的主节点的配置纪元uint64_t configEpoch;// 发送者的名字IDchar sender[REDIS_CLUSTER_NAMELEN];// 发送者目前的槽指派信息unsigned char myslots[REDIS_CLUSTER_SLOTS/8];// 如果发送者是一个从节点那么这里记录的是发送者正在复制的主节点的名字// 如果发送者是一个主节点那么这里记录的是// REDIS_NODE_NULL_NAME一个40字节长值全为0的字节数组char slaveof[REDIS_CLUSTER_NAMELEN];// 发送者的端口号uint16_t port;// 发送者的标识值uint16_t flags;// 发送者所处集群的状态unsigned char state;// 消息的正文或者说内容union clusterMsgData data; } clusterMsg;clusterMsg.data属性的类型为联合cluster.h/clusterMsgData这个联合就是消息的正文 union clusterMsgData {// MEET、PING、PONG消息的正文struct {// 每条MEET、PING、PONG消息都包含两个// clusterMsgDataGossip结构那为什么这里的数组只有一个元素可能只是起到指针的作用clusterMsgDataGossip gossip[1];} ping;// FAIL消息的正文struct {clusterMsgDataFail about;} fail;// PUBLISH消息的正文struct {clusterMsgDataPublish msg;} publish;// 其他消息的正文… };clusterMsg结构的currentEpoch、sender、myslots、flags等属性记录了发送者自身的节点信息接收者会根据这些信息在自己的clusterState.nodes字典中找到发送者对应的clusterNode结构并对结构进行更新。 例如通过发送者在消息头的flags属性记录的标识值接收者可以知道发送者的状态和角色是否发生了变化例如节点由在线变成了下线或由主节点变成了从节点等。通过发送者在消息头的myslots属性记录的槽指派信息接收者可知道发送者的槽指派信息是否发生了变化。 17.7.2 MEET、PING、PONG消息的实现 Redis集群中的各个节点通过Gossip协议来交换各自保存的关于集群中的其他节点的信息其中Gossip协议会放在MEET、PING、PONG三种消息中这三种消息的正文都由两个cluster.h/clusterMsgDataGossip结构组成 union clusterMsgData {// …// MEET、PING、PONG消息的正文struct {// 每条MEET、PING、PONG消息都包含两个// clusterMsgDataGossip结构clusterMsgDataGossip gossip[1];} ping;// 其他消息的正文 };每次发送MEET、PING、PONG消息时发送者都从自己的已知节点列表中随机选出两个节点可以是主节点或从节点并将这两个被选中节点的信息分别保存到两个clusterMsgDataGossip结构里。MEET、PING、PONG三种消息都使用相同的消息正文都是两个节点的信息所以节点通过消息头的type属性来区分MEET、PING、PONG消息。 clusterMsgDataGossip结构 typedef struct {// 节点的名字char nodename[REDIS_CLUSTER_NAMELEN];// 最后一次向该节点发送PING消息的时间戳uint32_t ping_sent;// 最后一次从该节点接收到PONG消息的时间戳uint32_t pong_received;// 节点的IP地址char ip[16];// 节点的端口号uint16_t port;// 节点的标识值uint16_t flags; } clusterMsgDataGossip;当接收者收到MEET、PING、PONG消息时接收者会访问消息正文中的两个clusterMsgDataGossip结构并根据自己是否认识clusterMsgDataGossip结构中的节点来选择进行哪种操作 1.如果节点不存在于接收者的已知节点列表说明接收者是第一次接触到该节点接收者将根据结构中的IP和端口与该节点进行握手。 2.如果节点已经存在于接收者的已知节点列表说明接收者之前已经与该节点进行过接触接收者将根据clusterMsgDataGossip结构记录的信息对该节点对应的clusterNode结构进行更新。 举个发送PING消息和返回PONG消息的例子假设在一个包含A、B、C、D、E、F六个节点的集群里 1.节点A向节点D发送PING消息且消息里包含了节点B和C的信息当节点D收到这条PING消息时它将更新自己对节点B和C的认识。 2.之后节点D向节点A返回一条PONG消息且消息里包含了节点E和F的信息当节点A收到这条PONG消息时它将更新自己对节点E和F的认识。 17.7.3 FAIL消息的实现 当集群里的主节点A将主节点B标记为已下线FAIL时主节点A将向集群广播一条关于主节点B的FAIL消息所有接收到这条FAIL消息的节点都会将主节点B标记为已下线。 在集群的节点数量比较大的情况下单纯使用Gossip协议来传播节点的已下线信息会给节点的信息更新带来一定延迟因为Gossip协议消息通常需要一段时间才能传播至整个集群而发送FAIL消息可以让集群中所有节点立即知道某个主节点已下线从而尽快判断是否需要将集群标记为下线又或者对下线主节点进行故障转移。 FAIL消息的正文由cluster.h/clusterMsgDataFail结构表示这个结构只包含一个nodename属性该属性记录了已下线节点的名字 typedef struct {char nodename[REDIS_CLUSTER_NAMELEN]; } clusterMsgDataFail;因为集群里的所有节点都有一个独一无二的名字所以FAIL消息里只需要保存下线节点的名字接收到消息的节点就可以根据这个名字来判断是哪个节点下线了。 例如对于包含7000、7001、7002、7003四个主节点的集群来说 1.如果主节点7001发现主节点7000已下线那么主节点7001将向主节点7002和7003发送FAIL消息其中FAIL消息中包含的节点名字为主节点7000的名字以此来表示主节点7000已下线。 2.当主节点7002和7003都接收到主节点7001发送的FAIL消息时它们也会将主节点7000标记为已下线。 3.因为这时集群已经有超过一半的主节点认为主节点7000已下线所以集群剩下的几个主节点可以判断是否需要将集群标记为下线又或者开始对主节点7000进行故障转移。 图17-42至17-44展示了节点发送和接收FAIL消息的整个过程 17.7.4 PUBLISH消息的实现 当客户端向集群中的某个节点发送命令 的时候接收到PUBLISH命令的节点不仅会向channel频道发送消息message它还会向集群广播一条PUBLISH消息所有接收到这条PUBLISH消息的节点都会向channel频道发送message消息。 换句话说向集群中的某个节点发送命令 将导致集群中的所有节点都向channel频道发送message消息。 例如对于包含7000、7001、7002、7003四个节点的集群来说如果节点7000收到了客户端发送的PUBLISH命令那么节点7000将向7001、7002、7003三个节点发送PUBLISH消息如图17-45所示 PUBLISH消息的正文由cluster.h/clusterMsgDataPublish结构表示 typedef struct {uint32_t channel_len;uint32_t message_len;// 定义为8字节只是为了对其其他消息结构实际的长度由保存的内容决定unsigned char bulk_data[8]; } clusterMsgDataPublish;clusterMsgDataPublish结构的bulk_data属性是一个字节数组这个字节数组保存了客户端通过PUBLISH命令发送给节点的channel参数和message参数而结构的channel_len和message_len成员则分别保存了channel参数的长度和message参数的长度 1.其中bulk_data的0至channel_len-1字节保存的是channel参数。 2.而bulk_data的channel_len至channel_lenmessage_len-1字节保存的则是message参数。 例如节点收到的PUBLISH命令为 那么节点发送的PUBLISH消息的clusterMsgDataPublish结构如图17-46所示 其中bulk_data数组的前七个字节保存了channel参数的值news.it而bulk_data数组的后五个字节则保存了message参数的值hello。 实际上要让集群的所有节点都执行相同的PUBLISH命令最简单的方法就是向所有节点广播相同的PUBLISH命令这也是Redis在复制PUBLISH命令时使用的方法但因为这种做法不符合Redis集群的“各个节点通过发送和接收消息来进行通信”这一规则所以节点没有采取广播PUBLISH命令的做法。 17.8 重点回顾 1.节点通过握手来将其他节点添加到自己所处的集群中。 2.集群中的16384个槽可以分别指派给集群中的各个节点每个节点都会记录哪些槽指派给了自己而哪些槽又被指派给了其他节点。 3.节点在接到一个命令请求时会先检查这个命令请求要处理的键所在的槽是否由自己负责如果不是的话节点将向客户端返回一个MOVED错误MOVED错误携带的信息可以指引客户端转向至正在负责相关槽的节点。 4.对Redis集群的重新分片工作是由redis-trib负责执行的重新分片的关键是将属于某个槽的所有键值对从一个节点转移至另一个节点。 5.如果节点A正在迁移槽i至节点B那么当节点A没能在自己的数据库中找到命令指定的属于槽i的数据库键时节点A会向客户端返回一个ASK错误指引客户端到节点B继续查找指定的数据库键。 6.MOVED错误表示槽的负责权已经从一个节点转移到了另一个节点而ASK错误只是两个节点在迁移槽的过程中使用的一种临时措施。 7.集群里的从节点用于复制主节点并在主节点下线时代替主节点继续处理命令请求。 8.集群中的节点通过发送和接收消息来进行通信常见的消息包括MEET、PING、PONG、PUBLISH、FAIL五种。