第 17 章: 集群¶
节点¶
三个节点的握手过程。
![digraph {
label = "\n 图 17-1 三个独立的节点";
node [shape = circle];
subgraph cluster_a {
label = "集群";
style = dashed;
7000;
}
subgraph cluster_b {
label = "集群";
style = dashed;
7001;
}
subgraph cluster_c {
label = "集群";
style = dashed;
7002;
}
}](_images/graphviz-c514c9c329acf4aa4dfa5d4bf881752b98924bf7.png)
![digraph {
label = "\n 图 17-2 节点 7000 和 7001 进行握手";
rankdir = LR;
node [shape = circle];
subgraph cluster_a {
label = "集群";
style = dashed;
7000;
}
subgraph cluster_b {
label = "集群";
style = dashed;
7001;
}
subgraph cluster_c {
label = "集群";
style = dashed;
7002;
}
7000 -> 7001 [label = "握手"];
7000 -> 7001 [dir = back, label = "响应握手"];
}](_images/graphviz-e9fda87839dbd322e920bea2db87ab065d6bc9cc.png)
![digraph {
label = "\n 图 17-3 握手成功的 7000 与 7001 处于同一个集群";
rankdir = LR;
node [shape = circle];
subgraph cluster_a {
label = "集群";
style = dashed;
7000;
7001;
7000 -> 7001 [style = invis];
}
subgraph cluster_c {
label = "集群";
style = dashed;
7002;
}
}](_images/graphviz-c9cea63a6d44514838d1297e4433b0b255e31efc.png)
![digraph {
label = "\n 图 17-4 节点 7000 与节点 7002 进行握手";
rankdir = LR;
node [shape = circle];
subgraph cluster_a {
label = "集群";
style = dashed;
7000;
7001;
7000 -> 7001 [style = invis];
}
subgraph cluster_c {
label = "集群";
style = dashed;
7002;
}
7000 -> 7002 [label = "握手"];
7000 -> 7002 [dir = back, label = "响应握手"];
}](_images/graphviz-2ba672c38dbfefec1ee4f04acac6034a5857368f.png)
![digraph {
label = "\n 图 17-5 握手成功的三个节点处于同一个集群";
rankdir = LR;
subgraph cluster_a {
label = "集群";
style = dashed;
node [shape = circle];
7000;
7002;
7001;
edge [style = invis];
7000 -> 7001;
7000 -> 7002;
}
}](_images/graphviz-a7a5db20a5c16ba980bbb23844570ab8145c5bff.png)
clusterNode
结构的示例。
![digraph {
label = "\n 图 17-7 节点 7000 创建的 clusterState 结构";
rankdir = LR;
//
node [shape = record];
clusterState [label = " <head> clusterState | <myself> myself | currentEpoch \n 0 | state \n REDIS_CLUSTER_FAIL | size \n 0 | <nodes> nodes | ... "];
nodes [label = " <head> nodes | <0> \"5154...2939\" | <1> \"68ee...f2ff\" | <2> \"9dfb...5c26\" "];
node7000 [label = " <head> clusterNode | name \n \"5154...2939\" | flags \n REDIS_NODE_MASTER | configEpoch \n 0 | ip \n \"127.0.0.1\" | port \n 7000 | ... "];
node7001 [label = " <head> clusterNode | name \n \"68ee...f2ff\"| flags \n REDIS_NODE_MASTER | configEpoch \n 0 | ip \n \"127.0.0.1\" | port \n 7001 | ... "];
node7002 [label = " <head> clusterNode | name \n \"9dfb...5c26\"| flags \n REDIS_NODE_MASTER | configEpoch \n 0 | ip \n \"127.0.0.1\" | port \n 7002 | ... "];
//link7000 [label = " <head> clusterLink | ctime | fd | sndbuf | rcvbuf "];
clusterState:myself -> node7000:head;
clusterState:nodes -> nodes:head;
nodes:0 -> node7000:head;
nodes:1 -> node7001:head;
nodes:2 -> node7002:head;
}](_images/graphviz-f37146c49cdf0c6632c6914685005594fe5ca900.png)
节点之间进行握手时的通信过程。
![digraph {
label = "\n 图 17-8 节点的握手过程";
rankdir = LR;
splines = polyline;
//
node [shape = box, height = 2];
client [label = "客\n户\n端"];
A [label = "节\n点\nA"];
B [label = "节\n点\nB"];
//
client -> A [label = "发送命令 \n CLUSTER MEET \n <B_ip> <B_port>"];
A -> B [label = "发送 MEET 消息"];
A -> B [dir = back, label = "\n返回 PONG 消息"];
A -> B [label = "\n返回 PING 消息"];
}](_images/graphviz-4359d9044c024b155b0ad06fed30a186f91bd81c.png)
槽指派¶
图 17-9 展示了一个 slots
数组示例:
这个数组索引 0
至索引 7
上的二进制位的值都为 1
,
其余所有二进制位的值都为 0
,
这表示节点负责处理槽 0
至槽 7
。
![digraph {
label = "\n 图 17-9 一个 slots 数组示例";
node [shape = record];
slots [label = " { 字节 | 索引 | 值 } | { slots[0] | {{ 0 | 1} | { 1 | 1} | { 2 | 1 } | { 3 | 1} | { 4 | 1 } | { 5 | 1 } | { 6 | 1 } | { 7 | 1 } }} | { slots[1] ~ slots[2047] | {{ 8 | 0 } | { 9 | 0 } | { 10 | 0 } | { 11 | 0 } | { 12 | 0 } | { ... | ... } | { 16381 | 0 } | { 16382 | 0 } | { 16383 | 0 } }} "];
}](_images/graphviz-058c72f2b6ac877f854929b8cda8ab791b2f6d21.png)
图 17-10 展示了另一个 slots
数组示例:
这个数组索引 1
、 3
、 5
、 8
、 9
、 10
上的二进制位的值都为 1
,
而其余所有二进制位的值都为 0
,
这表示节点负责处理槽 1
、 3
、 5
、 8
、 9
、 10
。
![digraph {
label = "\n 图 17-10 另一个 slots 数组示例";
node [shape = record];
slots [label = " { 字节 | 索引 | 值 } | { slots[0] | {{ 0 | 0} | { 1 | 1} | { 2 | 0 } | { 3 | 1} | { 4 | 0 } | { 5 | 1 } | { 6 | 0 } | { 7 | 0 } }} | { slots[1] | {{ 8 | 1} | { 9 | 1 } | { 10 | 1 } | { 11 | 0 } | { 12 | 0 } | { 13 | 0 } | { 14 | 0 } | { 15 | 0 }}} | { ... | ... | ... } | { slots[2047] | { { ... | ... } | { 16382 | 0 } | { 16383 | 0 }} } "];
}](_images/graphviz-cfdf8de21b5f53acd83f5a6b929ecc12586c25e9.png)
节点之间互相传递槽指派信息的过程。
![digraph {
label = "\n 图 17-11 7000 告知 7001 和 7002 自己负责处理的槽";
rankdir = LR;
subgraph cluster_a {
label = "集群";
style = dashed;
node [shape = circle];
7000;
7002;
7001;
edge [style = dashed, label = "\n我负责处理\n槽 0 至槽 5000"];
7000 -> 7001;
7000 -> 7002;
}
}](_images/graphviz-9924f360668e793658d80963f0cd400378d327ad.png)
![digraph {
label = "\n 图 17-12 7001 告知 7000 和 7002 自己负责处理的槽";
subgraph cluster_a {
label = "集群";
style = dashed;
node [shape = circle];
7000;
7002;
7001;
edge [style = dashed, label = "我负责处理\n槽 5001 至槽 10000"];
7001 -> 7000;
7001 -> 7002;
}
}](_images/graphviz-f89903a13d129c31ac4f15bc610e8bf8e5e0d748.png)
![digraph {
label = "\n 图 17-13 7002 告知 7000 和 7001 自己负责处理的槽";
rankdir = BT;
subgraph cluster_a {
label = "集群";
labelloc = "b";
style = dashed;
node [shape = circle];
7000;
7002;
7001;
edge [style = dashed, label = "我负责处理\n槽 10001 至槽 16383"];
7002 -> 7000;
7002 -> 7001;
}
}](_images/graphviz-372e06d979f6d60580459894e4387b820a13253b.png)
clusterState
结构的 slots
数组示例。
![digraph {
label = "\n 图 17-14 clusterState 结构的 slots 数组";
rankdir = LR;
node [shape = record];
clusterState [label = " clusterState | ... | <slots> slots | ... "];
slots [label = " <head> clusterNode*[16384] | <0> 0 | <1> 1 | <2> ... | <5000> 5000 | <5001> 5001 | <5002> 5002 | <5003> ... | <10000> 10000 | <10001> 10001 | <10002> 10002 | <10003> ... | <16383> 16383 "];
node7000 [label = " <head> clusterNode | ... | ip \n \"127.0.0.1\" | port \n 7000 | ... "];
node7001 [label = " <head> clusterNode | ... | ip \n \"127.0.0.1\" | port \n 7001 | ... "];
node7002 [label = " <head> clusterNode | ... | ip \n \"127.0.0.1\" | port \n 7002 | ... "];
slots:0 -> node7000:head;
slots:1 -> node7000:head;
slots:2 -> node7000:head;
slots:5000 -> node7000:head;
slots:5001 -> node7001:head;
slots:5002 -> node7001:head;
slots:5003 -> node7001:head;
slots:10000 -> node7001:head;
slots:10001 -> node7002:head;
slots:10002 -> node7002:head;
slots:10003 -> node7002:head;
slots:16383 -> node7002:head;
clusterState:slots -> slots:head;
}](_images/graphviz-e2619e2673c45c5cdbff8f08c35ae1e72724e41a.png)
在集群中执行命令¶
在集群中执行命令时的判断过程。
![digraph {
label = "\n 图 17-18 判断客户端是否需要转向的流程";
node [shape = box];
client_send_command [label = "客户端向节点发送数据库键命令", width = 7];
get_slot_number [label = "节点计算键属于哪个槽"];
node_handle_right_slot_or_not [label = "当前节点就是\n负责处理键所在槽的节点?", shape = diamond];
execute_command [label = "节点执行命令"];
return_moved [label = "节点向客户端返回一个\n MOVED 错误"];
client_redirect [label = "客户端根据 MOVED 错误提供的信息\n转向至正确的节点"];
//
client_send_command -> get_slot_number;
get_slot_number -> node_handle_right_slot_or_not;
node_handle_right_slot_or_not -> execute_command [label = "是"];
node_handle_right_slot_or_not -> return_moved [label = "不是"];
return_moved -> client_redirect;
client_redirect -> client_send_command [label = "重试"];
}](_images/graphviz-779a4cae9206c3fe3d2d97d7ea31cabd978db1b9.png)
MOVED
错误引发的转向过程。
![digraph {
label = "\n 图 17-20 节点 7000 向客户端返回 MOVED 错误";
rankdir = LR;
splines = polyline;
node [shape = box];
client [label = "客户端", height = 2.5];
node7000 [label = "节点 7000", height = 2.5];
//
client -> node7000 [label = "\nSET msg \"happy new year!\""];
client -> node7000 [dir = back, label = "\nMOVED 6257 127.0.0.1:7001"];
}](_images/graphviz-b575b9fdb24af7a32bce3277c3eb12f813299534.png)
![digraph {
label = "\n 图 17-21 客户端根据 MOVED 错误的指示转向至节点 7001";
rankdir = LR;
splines = polyline;
node [shape = box];
client [label = "客户端", height = 2.5];
node7001 [label = "节点 7001", height = 2.5];
//
client -> node7001 [label = "\n转向"];
client -> node7001 [label = "\nSET msg \"happy new year!\""];
client -> node7001 [dir = back, label = "\nOK"];
}](_images/graphviz-a94c50044c5a7ccd36761fc58c5a9e68b962b6d0.png)
集群节点为记录键所在槽而创建的跳跃表。
![digraph {
rankdir = LR;
node [shape = record, width = "0.5"];
//
l [label = " <header> header | <tail> tail | level \n 5 | length \n 3 "];
subgraph cluster_nodes {
style = invisible;
header [label = " <l32> L32 | ... | <l5> L5 | <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 "];
bw_null [label = "NULL", shape = plaintext];
level_null [label = "NULL", shape = plaintext];
A [label = " <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 | <backward> BW | 1337.0 | StringObject \n \"book\" "];
B [label = " <l2> L2 | <l1> L1 | <backward> BW | 2022.0 | StringObject \n \"date\" "];
C [label = " <l5> L5 | <l4> L4 | <l3> L3 | <l2> L2 | <l1> L1 | <backward> BW | 3347.0 | StringObject \n \"lst\" "];
}
subgraph cluster_nulls {
style = invisible;
n1 [label = "NULL", shape = plaintext];
n2 [label = "NULL", shape = plaintext];
n3 [label = "NULL", shape = plaintext];
n4 [label = "NULL", shape = plaintext];
n5 [label = "NULL", shape = plaintext];
}
//
l:header -> header;
l:tail -> C;
/*
// 移除连接线的 span 分值
header:l32 -> level_null [label = "0"];
header:l5 -> C:l5 [label = "3"];
header:l4 -> A:l4 [label = "1"];
header:l3 -> A:l3 [label = "1"];
header:l2 -> A:l2 [label = "1"];
header:l1 -> A:l1 [label = "1"];
A:l4 -> C:l4 [label = "2"];
A:l3 -> C:l3 [label = "2"];
A:l2 -> B:l2 [label = "1"];
A:l1 -> B:l1 [label = "1"];
B:l2 -> C:l2 [label = "1"];
B:l1 -> C:l1 [label = "1"];
C:l5 -> n5 [label = "0"];
C:l4 -> n4 [label = "0"];
C:l3 -> n3 [label = "0"];
C:l2 -> n2 [label = "0"];
C:l1 -> n1 [label = "0"];
*/
header:l32 -> level_null;
header:l5 -> C:l5;
header:l4 -> A:l4;
header:l3 -> A:l3;
header:l2 -> A:l2;
header:l1 -> A:l1;
A:l4 -> C:l4;
A:l3 -> C:l3;
A:l2 -> B:l2;
A:l1 -> B:l1;
B:l2 -> C:l2;
B:l1 -> C:l1;
C:l5 -> n5;
C:l4 -> n4;
C:l3 -> n3;
C:l2 -> n2;
C:l1 -> n1;
bw_null -> A:backward -> B:backward -> C:backward [dir = back];
label = "\n 图 17-23 节点 7000 的 slots_to_keys 跳跃表";
}](_images/graphviz-28542868d331e880bf156aec2dbf7c03b47395a4.png)
重新分片¶
将一个键从一个节点迁移到另一个节点的过程。
![digraph {
label = "\n 图 17-24 迁移键的过程";
rankdir = LR;
splines = polyline;
//
node [shape = box, height = 3];
client [label = "redis-trib"];
source [label = "源\n节\n点"];
target [label = "目\n标\n节\n点"];
//
client -> source [label = "1) 发送命令 \n CLUSTER GETKEYSINSLOT <slot> <count>"];
client -> source [dir = back, label = "\n 2) 返回最多 count 个属于槽 slot 的键"];
client -> source [label = "\n 3) 对于每个返回键\n向源节点发送一个 MIGRATE 命令"];
source -> target [label = "\n 4) 根据 MIGRATE 命令的指示 \n 将键迁移至目标节点"];
}](_images/graphviz-2fe6898213cba042279d0fef44cedc3c31d97dc3.png)
重新分片的整个过程。
![digraph {
label = "\n 图 17-25 对槽 slot 进行重新分片的过程"
node [shape = box]
start [label = "开始对槽 slot 进行重新分片"]
send_importing_to_target [label = "目标节点准备导入槽 slot 的键值对"];
send_migrating_to_source [label = "源节点准备迁移槽 slot 的键值对"];
start -> send_importing_to_target -> send_migrating_to_source;
contain_slot_key_or_not [shape = diamond, label = "源节点是否保存了\n属于槽 slot 的键?"]
migrate [label = "将这些键\n全部迁移至目标节点"]
assign [label = "将槽 slot 指派给目标节点"]
done [label = "完成对槽 slot 的重新分片"]
send_migrating_to_source -> contain_slot_key_or_not
contain_slot_key_or_not -> migrate [label = "是"]
migrate -> assign
contain_slot_key_or_not -> assign [label = "否"]
assign -> done
}](_images/graphviz-0f9a36aa7fd08928376d5fb49f297fa8770fc2c2.png)
ASK 错误¶
源节点判断是否需要向客户端发送 ASK
错误的整个过程。
![digraph {
label = "\n 图 17-26 判断是否发送 ASK 错误的过程";
//
node [shape = box];
send_command [label = "客户端向源节点发送关于键 key 的命令"];
key_in_or_not [label = "键 key 是否存在于\n源节点的数据库?", shape = diamond];
execute_command [label = "节点执行客户端发送的命令"];
reshaing_or_not [label = "节点正在迁移槽 i ?", shape = diamond];
ask_redirect [label = "向客户端返回 ASK 错误"];
//
send_command -> key_in_or_not;
key_in_or_not -> execute_command [label = "是"];
key_in_or_not -> reshaing_or_not [label = "否"];
reshaing_or_not -> ask_redirect [label = "是\n键 key 有可能在目标节点"];
reshaing_or_not -> execute_command [label = "否\n键 key 不存在"];
}](_images/graphviz-a72dac22233e8c010883fb0fc3c581cf7f674ed7.png)
importing_slots_from
结构以及 migrating_slots_to
结构的示例。
![digraph {
label = "\n 图 17-27 节点 7003 的 importing_slots_from 数组";
rankdir = LR;
//
node [shape = record];
clusterState [label = " clusterState | ... | <slots> importing_slots_from | ... "];
slots [label = " <head> clusterNode*[16384] | <0> 0 | <1> 1 | <2> 2 | <3> ... | <16198> 16198 | <15002> ... | <16381> 16381 | <16382> 16382 | <16383> 16383 "];
node7002 [label = " <head> clusterNode | ... | ip \n \"127.0.0.1\" | port \n 7002 | ... "];
node [shape = plaintext, label = "NULL"];
//
slots:0 -> null0;
slots:1 -> null1;
slots:2 -> null2;
slots:16198 -> node7002:head [minlen = 2];
slots:16381 -> null16381;
slots:16382 -> null16382;
slots:16383 -> null16383;
clusterState:slots -> slots:head;
}](_images/graphviz-8323d13b2a055633b4204a11ac651a14e2be612e.png)
![digraph {
label = "\n 图 17-28 节点 7002 的 migrating_slots_to 数组";
rankdir = LR;
//
node [shape = record];
clusterState [label = " clusterState | ... | <slots> migrating_slots_to | ... "];
slots [label = " <head> clusterNode*[16384] | <0> 0 | <1> 1 | <2> 2 | <3> ... | <16198> 16198 | <15002> ... | <16381> 16381 | <16382> 16382 | <16383> 16383 "];
node7003 [label = " <head> clusterNode | ... | ip \n \"127.0.0.1\" | port \n 7003 | ... "];
node [shape = plaintext, label = "NULL"];
//
slots:0 -> null0;
slots:1 -> null1;
slots:2 -> null2;
slots:16198 -> node7003:head [minlen = 2];
slots:16381 -> null16381;
slots:16382 -> null16382;
slots:16383 -> null16383;
clusterState:slots -> slots:head;
}](_images/graphviz-428ca45e970d76669cfbc9b4a9926e9b279a9584.png)
客户端根据 ASK
错误进行转向的过程。
![digraph {
label = "\n 图 17-29 客户端接收到节点 7002 返回的 ASK 错误";
rankdir = LR;
splines = polyline;
//
node [shape = box, height = 2];
client [label = "客户端"];
node7002 [label = "节点 7002"];
//
client -> node7002 [label = "GET \"love\""];
client -> node7002 [dir = back, label = "\nASK 16198 127.0.0.1:7003"];
}](_images/graphviz-31647ca493bc54297312004c4ef1935c33de5364.png)
![digraph {
label = "\n 图 17-30 客户端转向至节点 7003";
rankdir = LR;
splines = polyline
//
node [shape = box, height = 3];
client [label = "客户端"];
node7003 [label = "节点 7003"];
//
client -> node7003 [label = "转向"];
client -> node7003 [label = "ASKING"];
client -> node7003 [label = "\n GET \"love\""];
client -> node7003 [dir = back, label = "\n\"you get the key 'love'\""];
}](_images/graphviz-1b246a47dc17857052f9d838e1a5da8514f24a36.png)
节点判断是否执行客户端命令的过程。
![digraph {
label = "\n 图 17-31 节点判断是否执行客户端命令的过程";
node [shape = box];
//
send_command [label = "客户端向节点发送关于槽 i 的命令"];
node_handle_slot_or_not [shape = diamond, label = "槽 i 是否指派给了节点?"];
exec_command [label = "节点执行客户端发送的命令"];
importing_slot_or_not [shape = diamond, label = "节点是否正在导入槽 i ?"];
client_get_asking_flag_or_not [shape = diamond, label = "客户端是否带有 \n ASKING 标识?"];
return_moved [label = "节点向客户端返回 MOVED 命令"];
//
send_command -> node_handle_slot_or_not;
node_handle_slot_or_not -> exec_command [label = "是"];
node_handle_slot_or_not -> importing_slot_or_not [label = "否"];
importing_slot_or_not -> client_get_asking_flag_or_not [label = "是"];
client_get_asking_flag_or_not -> exec_command [label = "是"];
importing_slot_or_not -> return_moved [label = "否"];
client_get_asking_flag_or_not -> return_moved [label = "否"];
}](_images/graphviz-2c3e37e591ccc16e8fd6fd95e5caacfca227acf6.png)
复制与故障转移¶
Redis 集群对失效节点进行故障转移的过程。
![digraph {
label = "\n 图 17-32 设置节点 7004 和节点 7005 成为节点 7000 的从节点";
rankdir = LR;
subgraph cluster_a {
label = "集群";
style = dashed;
node [shape = doublecircle]
7000;
7001;
7002;
7003;
node [shape = circle]
7005;
7004;
edge [dir = back, label = "复制"]
7000 -> 7004
7000 -> 7005
edge [style = invis, label = ""]
7002 -> 7003
}
}](_images/graphviz-869c52f36fb5c7d2062ebdf0dbe731d9431a791d.png)
![digraph {
label = "\n 图 17-33 节点 7004 成为新的主节点";
rankdir = LR;
subgraph cluster_a {
label = "集群";
style = dashed;
node [shape = doublecircle];
7000 [style = dashed]
7001;
7002;
7003;
7004;
node [shape = circle]
7005;
edge [dir = back, label = "复制"]
7004 -> 7005
edge [style = invis, label = ""]
7000 -> 7001
7002 -> 7003
}
}](_images/graphviz-8a6d3cc945349315ed6d258c9e19b66c5a1ce0c5.png)
![digraph {
label = "\n 图 17-34 重新上线的节点 7000 成为节点 7004 的从节点";
rankdir = LR;
subgraph cluster_a {
label = "集群";
style = dashed;
node [shape = doublecircle];
7001;
7002;
7003;
7004;
node [shape = circle]
7000
7005;
edge [dir = back, label = "复制"]
7004 -> 7005
7004 -> 7000
edge [style = invis, label = ""]
7001 -> 7002 -> 7003
}
}](_images/graphviz-98de48f54467cf283c2f31f9bc996eb6063bbc9d.png)
节点的下线报告示例。
![digraph {
label = "\n 图 17-38 节点 7000 的下线报告"
rankdir = LR
node [shape = record]
node7000 [label = " <head> clusterNode | ... | flags \n REDIS_NODE_MASTER \n & \n REDIS_NODE_PFAIL | ip \n \"127.0.0.1\" | port \n 7000 | <fail_reports> fail_reports | ... "]
node7002 [label = " <head> clusterNode | ... | flags \n REDIS_NODE_MASTER | ip \n \"127.0.0.1\" | port \n 7002 | ... "]
node7003 [label = " <head> clusterNode | ... | flags \n REDIS_NODE_MASTER | ip \n \"127.0.0.1\" | port \n 7003 | ... "]
report7002 [label = " <head> clusterNodeFailReport | <pnode> node | time \n 1390525039000"]
report7003 [label = " <head> clusterNodeFailReport | <pnode> node | time \n 1390525039321"]
node7000:fail_reports -> report7002:head -> report7003:head
report7002:pnode -> node7002:head
report7003:pnode -> node7003:head
}](_images/graphviz-86b819895f24b23ed7a4a9c08c2b1006702a367e.png)
节点向集群广播 FAIL
消息。
![digraph {
label = "\n 图 17-39 节点 7001 向集群广播 FAIL 消息"
rankdir = LR;
subgraph cluster_a {
label = "集群";
style = dashed;
node [shape = doublecircle]
7000 [style = dashed]
7001;
7002;
7003;
node [shape = circle]
7004;
7005;
edge [dir = back, label = "复制"]
7000 -> 7004
7000 -> 7005
edge [dir = forward, label = "发送 FAIL 消息"]
7001 -> 7002
7001 -> 7003
7001 -> 7004
7001 -> 7005
}
}](_images/graphviz-2b4a0f193bdab6d86cd52f207182934c1f259820.png)
消息¶
集群中的节点通过发送消息来将一个节点标记为下线的过程。
![digraph {
label = "\n 图 17-42 节点 7001 将节点 7000 标记为已下线";
node [shape = circle];
subgraph cluster_cluster {
label = "集群";
node7000 [label = "7000", style = dashed];
node7001 [label = "7001"];
node7002 [label = "7002"];
node7003 [label = "7003"];
node7001 -> node7000 [label = "标记为\n已下线"];
edge [style = invis];
node7001 -> node7002 [label = "发送 FAIL 消息"];
node7001 -> node7003 [label = "发送 FAIL 消息"];
style = dashed;
}
}](_images/graphviz-df938effdc68a2e2bd7cf1743083c5720f121008.png)
![digraph {
label = "\n 图 17-43 节点 7001 向集群广播 FAIL 消息";
node [shape = circle];
subgraph cluster_cluster {
label = "集群";
node7000 [label = "7000", style = dashed];
node7001 [label = "7001"];
node7002 [label = "7002"];
node7003 [label = "7003"];
node7001 -> node7000 [style = invis];
edge [label = "发送 \nFAIL 消息"];
node7001 -> node7002;
node7001 -> node7003;
style = dashed;
}
}](_images/graphviz-ad2d7e1fdc2f4da0376cdf5e7b7ce36cdc858b5d.png)
![digraph {
label = "\n 图 17-44 节点 7002 和节点 7003 也将节点 7000 标记为已下线";
node [shape = circle];
subgraph cluster_cluster {
label = "集群";
node7003 [label = "7003"];
node7002 [label = "7002"];
node7001 [label = "7001"];
node7000 [label = "7000", style = dashed];
node7001 -> node7000 [style = invis];
edge [label = "标记为\n已下线"];
node7002 -> node7000;
node7003 -> node7000;
style = dashed;
}
}](_images/graphviz-cd2eb29f4a5252883f65aae86a615f306b756aaa.png)
接收到 PUBLISH
命令的节点向集群中的其他节点发送 PUBLISH
消息。
![digraph {
label = "\n 图 17-45 接收到 PUBLISH 命令的节点 7000 向集群广播 PUBLISH 消息";
node [shape = circle];
rankdir = LR;
client [label = "客户端"];
subgraph cluster_cluster {
label = "集群";
node7003 [label = "7003"];
node7002 [label = "7002"];
node7001 [label = "7001"];
node7000 [label = "7000"];
edge [label = "发送 \n PUBLISH \n 消息"];
node7000 -> node7001;
node7000 -> node7002;
node7000 -> node7003;
style = dashed;
}
client -> node7000 [label = "发送 \n PUBLISH \n 命令"];
}](_images/graphviz-f92eb50b95a9eff488ca63067b45bd70bb5fbcab.png)
PUBLISH
消息中包含的 clusterMsgDataPublish
结构示例。
![digraph {
label = "\n 图 17-46 clusterMsgDataPublish 结构示例";
node [shape = record];
rankdir = LR;
clusterMsgDataPublish [label = " clusterMsgDataPublish | channel_len \n 7 | message_len \n 5 | <bulk_data> bulk_data "];
bulk_data [label = " { 'n' | 'e' | 'w' | 's' | '.' | 'i' | 't' | 'h' | 'e' | 'l' | 'l' | 'o' } "];
clusterMsgDataPublish:bulk_data -> bulk_data;
}](_images/graphviz-bfb46965722ec0f665a1e32b9caecbf15432835e.png)