一、分片集群 分片集群(切片集群),就是指启动多个 Redis 实例组成一个集群,然后按照一定的规则,把收到的数据划分成多份,每一份用一个实例来保存。
在面向百万、千万级别的用户规模时,横向扩展的 Redis 分片集群会是一个非常好的选择。但是同时,又引申出来两个问题:
数据分片后,在多个实例之间如何分布?
客户端怎么确定想要访问的数据在哪个实例上?
从 3.0 开始,官方提供了一个名为 Redis Cluster 的方案,用于实现分片集群。Redis Cluster 方案中就规定了数据和实例的对应规则。
具体来说,Redis Cluster 方案采用哈希槽(Hash Slot,接下来我会直接称之为 Slot) ,来处理数据和实例之间的映射关系。在 Redis Cluster 方案中,一个分片集群共有 16384 个哈希槽(2^14),这些哈希槽类似于数据分区,每个键值对都会根据它的 key,被映射到一个哈希槽中。
具体的映射过程分为两大步:首先 根据键值对的 key,按照 CRC16 算法计算一个 16 bit 的值;然后 ,再用这个 16bit 值对 16384 取模,得到 0~16383 范围内的模数,每个模数代表一个相应编号的哈希槽。
那么,这些哈希槽又是如何被映射到具体的 Redis 实例上的呢?
我们在部署 Redis Cluster 方案时,可以使用 cluster create 命令创建集群,此时,Redis 会自动把这些槽平均分布在集群实例上。例当然,我们也可以使用 cluster meet 命令手动建立实例间的连接,形成集群,再使用 cluster addslots 命令,指定每个实例上的哈希槽个数(比如 Redis 实例的内存大小配置不一的情况)。
在手动分配哈希槽时,需要把 16384 个槽都分配完,否则 Redis 集群无法正常工作。
Availability: Redis Cluster is able to survive partitions where the majority of the master nodes are reachable and there is at least one reachable replica for every master node that is no longer reachable. Moreover using replicas migration, masters no longer replicated by any replica will receive one from a master which is covered by multiple replicas. 当集群中大多数的 master 都存活,并且,断连的 master 都有对应的 slave 时,集群仍然可以正常提供服务。 通过 replicas migration,对于没有 slave 的 master 节点,其他拥有多个 slave 节点的 master,会分一个 slave 给它(orphan master)。
客户端如何定位数据?
客户端在本地通过计算 CRC16 就可以知道数据被分配在了哪一个 slot,但是,要进一步定位到实例,还需要知道哈希槽分布在哪个实例上。
在集群刚刚创建的时候,每个实例通过配置文件(或者命令中的参数)可以知道自己被分配了哪些哈希槽
Redis 实例会把自己的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信息的扩散。
当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了;等到客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端缓存起来。
但是,在集群中,实例和哈希槽的对应关系并不是一成不变的,最常见的变化有两个:
在集群中,实例有新增或删除,Redis 需要重新分配哈希槽;
为了负载均衡,Redis 需要把哈希槽在所有实例上重新分布一遍。
此时,实例之间还可以通过相互传递消息,获得最新的哈希槽分配信息,但是,客户端是无法主动感知这些变化的,为此 Redis Cluster 方案提供了一种重定向机制 :
当客户端把一个键值对的操作请求发给一个实例时,如果这个实例上并没有这个键值对映射的哈希槽,那么,这个实例就会给客户端返回下面的 MOVED 命令响应结果,这个结果中就包含了新实例的访问地址。
1 2 GET hello:key (error) MOVED 13320 172.16 .19 .5 :6379
然后,客户端就会再次向新实例请求,同时还会更新本地缓存,把 slot 的对应关系更新过来。
在实际应用时,如果 Slot 中的数据比较多,就可能会出现一种情况:客户端发送请求时,Slot 中的数据只有一部分迁移到了新实例,还有部分数据没有迁移。在这种迁移部分完成的情况下,客户端就会收到一条 ASK 报错信息:
1 2 GET hello:key (error) ASK 13320 172.16 .19 .5 :6379
ASK 命令表示两层含义:第一,表明 Slot 数据还在迁移中;第二,ASK 命令把客户端所请求数据的最新实例地址返回给客户端,此时,客户端需要给实例 3 发送 ASKING 命令,然后再发送操作命令。
需要注意的是:和 MOVED 命令不同,ASK 命令并不会更新客户端缓存的哈希槽分配信息 。所以,在上图中,如果客户端再次请求 Slot 中的数据,它还是会给原实例发送请求。这也就是说,ASK 命令的作用只是让客户端能给新实例发送一次请求,而不像 MOVED 命令那样,会更改本地缓存,让后续所有命令都发往新实例。
Hash Tag
上面的方法可以高效的处理单个 key 的映射问题,但是,在处理 multi-key 操作的时候,如果他们被分散在不同的实例上,就会影响操作效率(事实上 redis 并没有提供跨实例的 multi-key operation)。
不过 redis 提供了另一种机制:Hash Tag ,来处理 multi-key operations:如果 key 中包含大括号{},那么只有括号中的内容会被用来计算 slot。(一些特殊情况,比如多组括号、括号中没有内容等,可以参考redis 官方文档 )
比如:键 foo{user1000}bar 和 foo{user1000}tee 会被映射在同一个 slot 中
二、源码: 0. 补充说明 1. PFAIL 和 FAIL redis 使用 pfail 和 fail 两个状态来标记异常的节点:
**pfail(Possible failure)**:如果 node A 向 node B 发送 ping 命令,超过NODE_TIMEOUT时间之后,仍没有收到 pong 回复,则 A 将 B 标记为 pfail 状态。
fail :如果 node A 发现,集群中超过半数的节点,都认为 node B 是 pfail 的,node A 就会将 node B 标记为 fail 状态。
redis 集群中,每个节点都通过下面的结构,保存着其他节点的状态信息:
从图中可以看到,redis 的核心结构 redisServer 中保存着 cluster 信息,而 cluster 结构中有一个字典,保存着所有的 node 信息。每一个 node 中,都有flags(红色)和fail_report(蓝色)两个字段。
flags:我们假设图中当前节点(redisServer)是 A,那么它表示在 A 的眼中,node1 目前的状态;如果 A 发送 ping 给 node1,超过NODE_TIMEOUT时间之后,仍没有收到 pong 回复,就会给 flags 中增加 pfail 状态。
fail_reports:在 cluster 节点之间定期 ping-pong 的过程中,会交换自己眼中集群节点的状态信息;比如:当前节点 A 会通过 ping-pong 收到大家(node2,node3)眼中 node1 的状态信息;如果 A 发现,node2 认为 node1 是 pfail,A 就会往 node1 的 fail_report 链表中增加一个记录(这里看似是个链表,其实是当 map 用的,如果已经有了,就替换掉);如果 node2 认为 node1 状态是 ok 的,就会清理掉对应的记录(如果有的话)。redis 会检查这个 fail_report,如果它的长度超过了 n/2+1,就给flags增加 fail 状态 ,即就是:如果集群中超过半数的节点都认为 node1 是 pfail 的,A 就会将它标记为 fail 状态。然后 A 会通知集群中其他节点,其他节点在收到信息之后,也会将 node1 标记为 fail 状态。
2. 选举 currentEpoch:
新节点的初始值是 0,集群中节点在交换信息的过程中,如果发现数据包中的 currentEpoch 字段大于自己的,就会更新自己的 currentEpoch;通过这样的方式,整个集群中的 currentEpoch 值会逐渐趋于一致。
这个参数主要是用在故障转移的过程中,可以粗略理解为投票的轮数:当一个 slave 节点,在执行 clusterCron 的过程中,发现自己 master 故障(FAIL),slave 会把自己的 currentEpoch+1,然后向集群中其他的 master 发起投票请求(其他节点收到这个节点的任意数据包之后,就会更新自己本地的 currentEpoch,这样,集群中的 currentEpoch 就统一更新了)
configEpoch:
configEpoch 的默认初始值是 0,在 failover 过程中,当一个 slave 赢得了足够的选票时,它会先把自己的 configEpoch 设置为发起投票时候的 currentEpoch,然后再执行升主操作。
configEpoch 每次改变之后,都会保存到磁盘上的nodes.conf文件中(slave 的 configEpoch 是从它的 master 那里取得的);cluster 中的节点会通过 ping、pong、meet 等数据包交换它们的 configEpoch,其他节点收到新 master 的信息之后,发现它的 configEpoch 比以前大了,就会依据数据包中的信息来更新本地的信息(诸如 master 身份、slots 等)。
它的主要用途是:当集群中的节点对集群的认知出现差异时,更大的 configEpoch 会胜出(比如一个离线的 master 重新加入集群,它的 configEpoch 会更小,所以其他节点不会认可他)(再比如,如果同时有两个节点声称自己拥有 slot[1] 的所有权,configEpoch 更大的那一个节点会被大家认可)
一般情况下,两个 master 之间 configEpoch 不会重复(因为每一个 epoch,每个 master 只会投一次票,所以只会有一个节点胜出,并将这个 epoch 设置为自己的 configEpoch),但是特殊情况下,比如管理员手动 resharding 刚好遇到了集群选举,就有可能出现 configEpoch 相同的情况。当 configEpoch 相同时,nodeId 较小的那一个,会给自己的 confgEpoch 加一。
达到以下条件时,replica 节点就可以发起选举请求了:
replica 的 master 处于 pfail 状态
master 中包含有 slot
replica 和 master 的数据同步连接断开超过一定的时间
但 replica 并不是立刻发起选举,而是会等待一小段时间再发起选举,这样做是为了让 fail 状态在集群里充分的传播(否则,可能会短时间内发起多次选举,导致短时间内连续切换 master)
DELAY = 500 milliseconds + random delay between 0 and 500 milliseconds + REPLICA_RANK * 1000 milliseconds. random 是为了让多个 replica 不要同时发起请求;REPLICA_RANK 是根据 replica 同步的 offset 得来的,某个 replica 的 offset 最接近 master,它的 REPLICA_RANK 就是 0,第二接近的 REPLICA_RANK 就是 1,以此类推。
首先 :replica 会增加自己的 currentEpoch,然后通过广播 FAILOVER_AUTH_REQUEST 向其他的 master 请求投票,然后等待最多 NODE_TIMEOUT * 2 的时间。针对同一个 master 的故障转移(failover),其他每个 master 在 NODE_TIMEOUT * 2 时间内,只会投一次票。
这里只会投一次票的含义是: 假设 masterA 有两个从节点 replicaA1 和 replicaA2,如果 masterA 挂了,replicaA1 和 replicaA2 会向 masterB 发起投票请求;masterB 只会答复(投票给)其中的一个,对于另一个请求,会任由它超时。 与此同时,如果还有一个 masterC 有两个从节点 replicaC1 和 replicaC2,也向 masterB 发起投票请求,masterB 会查看自己的 lastVoteEpoch,如果一致,则不会投票,如果不一致,就会投票给 replicaC1 或 replicaC2 中的一个。
如果 reply 中的 epoch 小于 currentEpoch,replica 会忽略掉它,因为这表示这是上一轮选举。
如果在 NODE_TIMEOUT * 2 内,某个 replica 获得了超过半数 master 的选票,就说明他赢得了选举。如果任何 replica 都没有拿到足够的选票,则本轮投票无效,replica 会在 NODE_TIMEOUT * 4 时间后发起新一轮投票。
一旦某个 replica 赢得了选举,他将获得一个新的 configEpoch,这个 configEpoch 比其他所有的 master 都大 。这之后,它就开始以 master 的身份来 ping-pong 交换数据了,数据包中带 configEpoch 以及它所承载的 slots
当其他 master 节点检测到这段 slots 换了主人,并且携带者更大的 configEpoch 的时候,就会更新自己的配置信息(upgrade their configuration)。
3. Replica migration Redis 提供了一种 Replica migration 机制,来提高集群的可用性。它主要被用于处理这样的情况:如果一个集群中,某个 master 和它的 replica 都下线了,集群就无法正常提供服务了。
它的主要思路是 :集群中每个节点都会检测是否有 orphaned master,当检测到有 orphaned master(即没有 replica 的 master)时,拥有最多数量 replica 并且 nodeId 最小 的 master 就会将它的一个 replica 节点,转移给 orphaned master。
这个机制是通过配置文件中的这两个参数来控制的:
cluster-allow-replica-migration:表示是否打开这个功能;
cluster-migration-barrier:数字,表示至少拥有这么多存活的 replica 的 master 才允许执行 migration;
需要注意的是,这个过程是节点自发进行的,而不像选举一样在集群中达成共识,所以,当集群状态不稳定时,有可能出现两个 master 都分配 replica 给 orphaned master 的情况;但是这种情况其实是无害的,并不会对集群造成影响。
4. manual failover 手动故障转移,即管理员在 slave 节点手动发送 CLUSTER FAILOVER 让其晋升为 master:
用户手动给 slave 发送 CLUSTER FAILOVER 命令。mf_end(manual failover end)被初始化为未来的一个时间点,超过这个时间,本次 failover 就会被抛弃。
slave 发送 MFSTART 消息给 master,让 master 暂停(pause)与 clients 的交互。master 成功 pause 之后,数据包中就会携带 CLUSTERMSG_FLAG0_PAUSED 字段。
slave 等待 master 发送它的 replication offset(消息中携带 CLUSTERMSG_FLAG0_PAUSED)
如果 master 的 offset 和 slave 自身相同,说明数据已经同步了,mf_can_start 被设置为 1,表示可以开始 failover,之后就和普通的 failover 一样,通过 clusterHandleSlaveFailover 方法执行了(下文会详细介绍)。
1. 接收数据包 在 redis 的启动过程中,initServer 方法会针对 cluster mode 的节点,运行 clusterInit 方法
1 if (server.cluster_enabled) clusterInit ();
在 clusterInit 方法中,通过下面的调用链,为 cluster 专用的 fd: server.cfd设置了一个监听器,每当有其他节点过来连接(或发送信息)的时候,都会通过 clusterReadHandler 来处理后续接收到的信息:
clusterReadHandler的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 void clusterReadHandler (connection *conn) { clusterMsg buf[1 ]; ssize_t nread; clusterMsg *hdr; clusterLink *link = connGetPrivateData (conn); unsigned int readlen, rcvbuflen; while (1 ) { rcvbuflen = link->rcvbuf_len; if (rcvbuflen < 8 ) { readlen = 8 - rcvbuflen; } else { hdr = (clusterMsg*) link->rcvbuf; if (rcvbuflen == 8 ) { if (memcmp (hdr->sig,"RCmb" ,4 ) != 0 || ntohl (hdr->totlen) < CLUSTERMSG_MIN_LEN) { serverLog (LL_WARNING, "Bad message length or signature received " "from Cluster bus." ); handleLinkIOError (link); return ; } } readlen = ntohl (hdr->totlen) - rcvbuflen; if (readlen > sizeof (buf)) readlen = sizeof (buf); } nread = connRead (conn,buf,readlen); if (nread == -1 && (connGetState (conn) == CONN_STATE_CONNECTED)) return ; if (nread <= 0 ) { serverLog (LL_DEBUG,"I/O error reading from node link: %s" , (nread == 0 ) ? "connection closed" : connGetLastError (conn)); handleLinkIOError (link); return ; } else { size_t unused = link->rcvbuf_alloc - link->rcvbuf_len; if ((size_t )nread > unused) { size_t required = link->rcvbuf_len + nread; link->rcvbuf_alloc = required < RCVBUF_MAX_PREALLOC ? required * 2 : required + RCVBUF_MAX_PREALLOC; link->rcvbuf = zrealloc (link->rcvbuf, link->rcvbuf_alloc); } memcpy (link->rcvbuf + link->rcvbuf_len, buf, nread); link->rcvbuf_len += nread; hdr = (clusterMsg*) link->rcvbuf; rcvbuflen += nread; } if (rcvbuflen >= 8 && rcvbuflen == ntohl (hdr->totlen)) { if (clusterProcessPacket (link)) { if (link->rcvbuf_alloc > RCVBUF_INIT_LEN) { zfree (link->rcvbuf); link->rcvbuf = zmalloc (link->rcvbuf_alloc = RCVBUF_INIT_LEN); } link->rcvbuf_len = 0 ; } else { return ; } } } }
所有的 packet 都包含下面的部分(common header):
Node ID:node 创建的时候分配的 160 bit 随机字符串(会被写在硬盘上配置文件里,除非删掉文件,或者执行 cluster reset 命令,否则不会改变)
currentEpoch 和 configEpoch: 用于支撑 Redis Cluster 的分布式算法(会在下面做详细介绍)。如果节点是 replica,它的 configEpoch 是 master 的 configEpoch.
flags:包含着 node 的身份(master or replica)等其他信息。
包含着它自己的 slot 信息的 bitmap
发送方用于接收连接的 port(used by Redis to accept client commands.)
发送方用于交换 cluster 信息的 port
在发送方看来,目前的集群状态 (down or ok).
如果发送方是 replica,这里会携带 master node ID
2. 处理数据包 下来就是重要的clusterProcessPacket方法了,当前 node 接收到其他 node 发来的完整的消息以后,就会调用这个方法处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 int clusterProcessPacket (clusterLink *link) { clusterMsg *hdr = (clusterMsg*) link->rcvbuf; uint32_t totlen = ntohl (hdr->totlen); uint16_t type = ntohs (hdr->type); mstime_t now = mstime (); if (type < CLUSTERMSG_TYPE_COUNT) server.cluster->stats_bus_messages_received[type]++; ······ if (ntohs (hdr->ver) != CLUSTER_PROTO_VER) { return 1 ; } uint16_t flags = ntohs (hdr->flags); uint64_t senderCurrentEpoch = 0 , senderConfigEpoch = 0 ; clusterNode *sender; ······ ······ sender = clusterLookupNode (hdr->sender); if (sender) sender->data_received = now; if (sender && !nodeInHandshake (sender)) { senderCurrentEpoch = ntohu64 (hdr->currentEpoch); senderConfigEpoch = ntohu64 (hdr->configEpoch); if (senderCurrentEpoch > server.cluster->currentEpoch) server.cluster->currentEpoch = senderCurrentEpoch; if (senderConfigEpoch > sender->configEpoch) { sender->configEpoch = senderConfigEpoch; clusterDoBeforeSleep (CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG); } sender->repl_offset = ntohu64 (hdr->offset); sender->repl_offset_time = now; if (server.cluster->mf_end && nodeIsSlave (myself) && myself->slaveof == sender && hdr->mflags[0 ] & CLUSTERMSG_FLAG0_PAUSED && server.cluster->mf_master_offset == -1 ) { server.cluster->mf_master_offset = sender->repl_offset; clusterDoBeforeSleep (CLUSTER_TODO_HANDLE_MANUALFAILOVER); serverLog (LL_WARNING, "Received replication offset for paused master manual failover: %lld" , server.cluster->mf_master_offset); } } if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_MEET) { serverLog (LL_DEBUG,"Ping packet received: %p" , (void *)link->node); if ((type == CLUSTERMSG_TYPE_MEET || myself->ip[0 ] == '\0' ) && server.cluster_announce_ip == NULL ) { char ip[NET_IP_STR_LEN]; if (connSockName (link->conn,ip,sizeof (ip),NULL ) != -1 && strcmp (ip,myself->ip)) { memcpy (myself->ip,ip,NET_IP_STR_LEN); serverLog (LL_WARNING,"IP address for this node updated to %s" , myself->ip); clusterDoBeforeSleep (CLUSTER_TODO_SAVE_CONFIG); } } if (!sender && type == CLUSTERMSG_TYPE_MEET) { clusterNode *node; node = createClusterNode (NULL ,CLUSTER_NODE_HANDSHAKE); nodeIp2String (node->ip,link,hdr->myip); node->port = ntohs (hdr->port); node->pport = ntohs (hdr->pport); node->cport = ntohs (hdr->cport); clusterAddNode (node); clusterDoBeforeSleep (CLUSTER_TODO_SAVE_CONFIG); } if (!sender && type == CLUSTERMSG_TYPE_MEET) clusterProcessGossipSection (hdr,link); clusterSendPing (link,CLUSTERMSG_TYPE_PONG); } if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_PONG || type == CLUSTERMSG_TYPE_MEET) { serverLog (LL_DEBUG,"%s packet received: %.40s" ,clusterGetMessageTypeString (type), link->node ? link->node->name : "NULL" ); if (link->node) { if (nodeInHandshake (link->node)) { if (sender) { serverLog (LL_VERBOSE, "Handshake: we already know node %.40s, " "updating the address if needed." , sender->name); if (nodeUpdateAddressIfNeeded (sender,link,hdr)) { clusterDoBeforeSleep (CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE); } clusterDelNode (link->node); return 0 ; } clusterRenameNode (link->node, hdr->sender); serverLog (LL_DEBUG,"Handshake with node %.40s completed." ,link->node->name); link->node->flags &= ~CLUSTER_NODE_HANDSHAKE; link->node->flags |= flags&(CLUSTER_NODE_MASTER|CLUSTER_NODE_SLAVE); clusterDoBeforeSleep (CLUSTER_TODO_SAVE_CONFIG); } else if (memcmp (link->node->name,hdr->sender, CLUSTER_NAMELEN) != 0 ) { serverLog (LL_DEBUG,"PONG contains mismatching sender ID. About node %.40s added %d ms ago, having flags %d" , link->node->name,(int )(now-(link->node->ctime)),link->node->flags); link->node->flags |= CLUSTER_NODE_NOADDR; link->node->ip[0 ] = '\0' ; link->node->port = 0 ; link->node->pport = 0 ; link->node->cport = 0 ; freeClusterLink (link); clusterDoBeforeSleep (CLUSTER_TODO_SAVE_CONFIG); return 0 ; } } if (sender) { int nofailover = flags & CLUSTER_NODE_NOFAILOVER; sender->flags &= ~CLUSTER_NODE_NOFAILOVER; sender->flags |= nofailover; } if (sender && type == CLUSTERMSG_TYPE_PING && !nodeInHandshake (sender) && nodeUpdateAddressIfNeeded (sender,link,hdr)) { clusterDoBeforeSleep (CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE); } if (!link->inbound && type == CLUSTERMSG_TYPE_PONG) { link->node->pong_received = now; link->node->ping_sent = 0 ; if (nodeTimedOut (link->node)) { link->node->flags &= ~CLUSTER_NODE_PFAIL; clusterDoBeforeSleep (CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_UPDATE_STATE); } else if (nodeFailed (link->node)) { clearNodeFailureIfNeeded (link->node); } } if (sender) { if (!memcmp (hdr->slaveof,CLUSTER_NODE_NULL_NAME, sizeof (hdr->slaveof))) { clusterSetNodeAsMaster (sender); } else { clusterNode *master = clusterLookupNode (hdr->slaveof); if (nodeIsMaster (sender)) { clusterDelNodeSlots (sender); sender->flags &= ~(CLUSTER_NODE_MASTER| CLUSTER_NODE_MIGRATE_TO); sender->flags |= CLUSTER_NODE_SLAVE; clusterDoBeforeSleep (CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE); } if (master && sender->slaveof != master) { if (sender->slaveof) clusterNodeRemoveSlave (sender->slaveof,sender); clusterNodeAddSlave (master,sender); sender->slaveof = master; clusterDoBeforeSleep (CLUSTER_TODO_SAVE_CONFIG); } } } clusterNode *sender_master = NULL ; int dirty_slots = 0 ; if (sender) { sender_master = nodeIsMaster (sender) ? sender : sender->slaveof; if (sender_master) { dirty_slots = memcmp (sender_master->slots, hdr->myslots,sizeof (hdr->myslots)) != 0 ; } } if (sender && nodeIsMaster (sender) && dirty_slots) clusterUpdateSlotsConfigWith (sender,senderConfigEpoch,hdr->myslots); if (sender && dirty_slots) { int j; for (j = 0 ; j < CLUSTER_SLOTS; j++) { if (bitmapTestBit (hdr->myslots,j)) { if (server.cluster->slots[j] == sender || server.cluster->slots[j] == NULL ) continue ; if (server.cluster->slots[j]->configEpoch > senderConfigEpoch) { serverLog (LL_VERBOSE, "Node %.40s has old slots configuration, sending " "an UPDATE message about %.40s" , sender->name, server.cluster->slots[j]->name); clusterSendUpdate (sender->link, server.cluster->slots[j]); break ; } } } } if (sender && nodeIsMaster (myself) && nodeIsMaster (sender) && senderConfigEpoch == myself->configEpoch) { clusterHandleConfigEpochCollision (sender); } if (sender) { clusterProcessGossipSection (hdr,link); } } else if (type == CLUSTERMSG_TYPE_FAIL) { clusterNode *failing; if (sender) { failing = clusterLookupNode (hdr->data.fail.about.nodename); if (failing && !(failing->flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_MYSELF))) { serverLog (LL_NOTICE, "FAIL message received from %.40s about %.40s" , hdr->sender, hdr->data.fail.about.nodename); failing->flags |= CLUSTER_NODE_FAIL; failing->fail_time = now; failing->flags &= ~CLUSTER_NODE_PFAIL; clusterDoBeforeSleep (CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_UPDATE_STATE); } } else { serverLog (LL_NOTICE, "Ignoring FAIL message from unknown node %.40s about %.40s" , hdr->sender, hdr->data.fail.about.nodename); } } else if (type == CLUSTERMSG_TYPE_PUBLISH) { robj *channel, *message; uint32_t channel_len, message_len; if (dictSize (server.pubsub_channels) || dictSize (server.pubsub_patterns)) { ...... pubsubPublishMessage (channel,message); ...... } } else if (type == CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST) { if (!sender) return 1 ; clusterSendFailoverAuthIfNeeded (sender,hdr); } else if (type == CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK) { if (!sender) return 1 ; if (nodeIsMaster (sender) && sender->numslots > 0 && senderCurrentEpoch >= server.cluster->failover_auth_epoch) { server.cluster->failover_auth_count++; clusterDoBeforeSleep (CLUSTER_TODO_HANDLE_FAILOVER); } } else if (type == CLUSTERMSG_TYPE_MFSTART) { if (!sender || sender->slaveof != myself) return 1 ; resetManualFailover (); server.cluster->mf_end = now + CLUSTER_MF_TIMEOUT; server.cluster->mf_slave = sender; pauseClients (PAUSE_DURING_FAILOVER, now + (CLUSTER_MF_TIMEOUT * CLUSTER_MF_PAUSE_MULT), CLIENT_PAUSE_WRITE); serverLog (LL_WARNING,"Manual failover requested by replica %.40s." , sender->name); clusterSendPing (link, CLUSTERMSG_TYPE_PING); } else if (type == CLUSTERMSG_TYPE_UPDATE) { clusterNode *n; uint64_t reportedConfigEpoch = ntohu64 (hdr->data.update.nodecfg.configEpoch); if (!sender) return 1 ; n = clusterLookupNode (hdr->data.update.nodecfg.nodename); if (!n) return 1 ; if (n->configEpoch >= reportedConfigEpoch) return 1 ; if (nodeIsSlave (n)) clusterSetNodeAsMaster (n); n->configEpoch = reportedConfigEpoch; clusterDoBeforeSleep (CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_FSYNC_CONFIG); clusterUpdateSlotsConfigWith (n,reportedConfigEpoch, hdr->data.update.nodecfg.slots); } else if (type == CLUSTERMSG_TYPE_MODULE) { } else { serverLog (LL_WARNING,"Received unknown packet type: %d" , type); } return 1 ; }
更新slot信息 clusterUpdateSlotsConfigWith
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 void clusterUpdateSlotsConfigWith (clusterNode *sender, uint64_t senderConfigEpoch, unsigned char *slots) { int j; clusterNode *curmaster = NULL , *newmaster = NULL ; uint16_t dirty_slots[CLUSTER_SLOTS]; int dirty_slots_count = 0 ; int sender_slots = 0 ; int migrated_our_slots = 0 ; curmaster = nodeIsMaster (myself) ? myself : myself->slaveof; if (sender == myself) { serverLog (LL_WARNING,"Discarding UPDATE message about myself." ); return ; } for (j = 0 ; j < CLUSTER_SLOTS; j++) { if (bitmapTestBit (slots,j)) { sender_slots++; if (server.cluster->slots[j] == sender) continue ; if (server.cluster->importing_slots_from[j]) continue ; if (server.cluster->slots[j] == NULL || server.cluster->slots[j]->configEpoch < senderConfigEpoch) { if (server.cluster->slots[j] == myself && countKeysInSlot (j) && sender != myself) { dirty_slots[dirty_slots_count] = j; dirty_slots_count++; } if (server.cluster->slots[j] == curmaster) { newmaster = sender; migrated_our_slots++; } clusterDelSlot (j); clusterAddSlot (sender,j); clusterDoBeforeSleep (CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE| CLUSTER_TODO_FSYNC_CONFIG); } } } ...... if (newmaster && curmaster->numslots == 0 && (server.cluster_allow_replica_migration || sender_slots == migrated_our_slots)) { serverLog (LL_WARNING, "Configuration change detected. Reconfiguring myself " "as a replica of %.40s" , sender->name); clusterSetMaster (sender); clusterDoBeforeSleep (CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE| CLUSTER_TODO_FSYNC_CONFIG); } else if (dirty_slots_count) { for (j = 0 ; j < dirty_slots_count; j++) delKeysInSlot (dirty_slots[j]); } }
投票 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 void clusterSendFailoverAuthIfNeeded (clusterNode *node, clusterMsg *request) { clusterNode *master = node->slaveof; uint64_t requestCurrentEpoch = ntohu64 (request->currentEpoch); uint64_t requestConfigEpoch = ntohu64 (request->configEpoch); unsigned char *claimed_slots = request->myslots; int force_ack = request->mflags[0 ] & CLUSTERMSG_FLAG0_FORCEACK; int j; if (nodeIsSlave (myself) || myself->numslots == 0 ) return ; if (requestCurrentEpoch < server.cluster->currentEpoch) { serverLog (LL_WARNING, "Failover auth denied to %.40s: reqEpoch (%llu) < curEpoch(%llu)" , node->name, (unsigned long long ) requestCurrentEpoch, (unsigned long long ) server.cluster->currentEpoch); return ; } if (server.cluster->lastVoteEpoch == server.cluster->currentEpoch) { serverLog (LL_WARNING, "Failover auth denied to %.40s: already voted for epoch %llu" , node->name, (unsigned long long ) server.cluster->currentEpoch); return ; } if (nodeIsMaster (node) || master == NULL || (!nodeFailed (master) && !force_ack)) { if (nodeIsMaster (node)) { serverLog (LL_WARNING, "Failover auth denied to %.40s: it is a master node" , node->name); } else if (master == NULL ) { serverLog (LL_WARNING, "Failover auth denied to %.40s: I don't know its master" , node->name); } else if (!nodeFailed (master)) { serverLog (LL_WARNING, "Failover auth denied to %.40s: its master is up" , node->name); } return ; } if (mstime () - node->slaveof->voted_time < server.cluster_node_timeout * 2 ) { serverLog (LL_WARNING, "Failover auth denied to %.40s: " "can't vote about this master before %lld milliseconds" , node->name, (long long ) ((server.cluster_node_timeout*2 )- (mstime () - node->slaveof->voted_time))); return ; } for (j = 0 ; j < CLUSTER_SLOTS; j++) { if (bitmapTestBit (claimed_slots, j) == 0 ) continue ; if (server.cluster->slots[j] == NULL || server.cluster->slots[j]->configEpoch <= requestConfigEpoch) { continue ; } serverLog (LL_WARNING, "Failover auth denied to %.40s: " "slot %d epoch (%llu) > reqEpoch (%llu)" , node->name, j, (unsigned long long ) server.cluster->slots[j]->configEpoch, (unsigned long long ) requestConfigEpoch); return ; } server.cluster->lastVoteEpoch = server.cluster->currentEpoch; node->slaveof->voted_time = mstime (); clusterDoBeforeSleep (CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG); clusterSendFailoverAuth (node); serverLog (LL_WARNING, "Failover auth granted to %.40s for epoch %llu" , node->name, (unsigned long long ) server.cluster->currentEpoch); }
3. clusterCron 定期任务 在 redis 的 serverCron 中,针对 cluster 模式的节点,会运行 clusterCron(10次/秒),它的主要工作是:
(1)向其他节点发送MEET消息,将其加入集群; (2)每1s会随机选择一个节点,发送 ping 消息; (3)如果一个节点在超时时间之内仍未收到 ping 包的响应(cluster-node-timeout配置项指定的时间),则将其 标记为 pfail; (4)检查是否需要进行主从切换,如果需要则执行切换; (5)检查是否需要进行副本漂移,如果需要,执行副本漂移操作。
对于步骤(1),当在一个集群节点A执行 CLUSTER MEET ip port 命令时,会将“ip-port”指定的节点B加入该集群中,但该命令执行时只是将B的“ip-port”信息保存起来,然后在 clusterCron 函数中才和B节点建立连接并发送MEET类型的数据包 对于步骤(3),Redis集群中节点的故障状态有两种。一种为pfail(Possible failure),当一个节点A未在指定时间收到另一个节点B对ping包的响应时,A节点会将B节点标记为pfail。另一种是,当大多数Master节点确认B为pfail之后,就会将B标记为fail。fail状态的节点才会需要执行主从切换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 void clusterCron (void ) { dictIterator *di; dictEntry *de; int update_state = 0 ; int orphaned_masters; int max_slaves; int this_slaves; mstime_t min_pong = 0 , now = mstime (); clusterNode *min_pong_node = NULL ; static unsigned long long iteration = 0 ; mstime_t handshake_timeout; iteration++; { static char *prev_ip = NULL ; char *curr_ip = server.cluster_announce_ip; int changed = 0 ; if (prev_ip == NULL && curr_ip != NULL ) changed = 1 ; else if (prev_ip != NULL && curr_ip == NULL ) changed = 1 ; else if (prev_ip && curr_ip && strcmp (prev_ip,curr_ip)) changed = 1 ; if (changed) { if (prev_ip) zfree (prev_ip); prev_ip = curr_ip; if (curr_ip) { prev_ip = zstrdup (prev_ip); strncpy (myself->ip,server.cluster_announce_ip,NET_IP_STR_LEN); myself->ip[NET_IP_STR_LEN-1 ] = '\0' ; } else { myself->ip[0 ] = '\0' ; } } } handshake_timeout = server.cluster_node_timeout; if (handshake_timeout < 1000 ) handshake_timeout = 1000 ; clusterUpdateMyselfFlags (); server.cluster->stats_pfail_nodes = 0 ; di = dictGetSafeIterator (server.cluster->nodes); while ((de = dictNext (di)) != NULL ) { clusterNode *node = dictGetVal (de); if (node->flags & (CLUSTER_NODE_MYSELF|CLUSTER_NODE_NOADDR)) continue ; if (node->flags & CLUSTER_NODE_PFAIL) server.cluster->stats_pfail_nodes++; if (nodeInHandshake (node) && now - node->ctime > handshake_timeout) { clusterDelNode (node); continue ; } if (node->link == NULL ) { clusterLink *link = createClusterLink (node); link->conn = server.tls_cluster ? connCreateTLS () : connCreateSocket (); connSetPrivateData (link->conn, link); if (connConnect (link->conn, node->ip, node->cport, NET_FIRST_BIND_ADDR, clusterLinkConnectHandler) == -1 ) { if (node->ping_sent == 0 ) node->ping_sent = mstime (); serverLog (LL_DEBUG, "Unable to connect to " "Cluster Node [%s]:%d -> %s" , node->ip, node->cport, server.neterr); freeClusterLink (link); continue ; } node->link = link; } } dictReleaseIterator (di); if (!(iteration % 10 )) { int j; for (j = 0 ; j < 5 ; j++) { de = dictGetRandomKey (server.cluster->nodes); clusterNode *this = dictGetVal (de); if (this ->link == NULL || this ->ping_sent != 0 ) continue ; if (this ->flags & (CLUSTER_NODE_MYSELF|CLUSTER_NODE_HANDSHAKE)) continue ; if (min_pong_node == NULL || min_pong > this ->pong_received) { min_pong_node = this ; min_pong = this ->pong_received; } } if (min_pong_node) { serverLog (LL_DEBUG,"Pinging node %.40s" , min_pong_node->name); clusterSendPing (min_pong_node->link, CLUSTERMSG_TYPE_PING); } } orphaned_masters = 0 ; max_slaves = 0 ; this_slaves = 0 ; di = dictGetSafeIterator (server.cluster->nodes); while ((de = dictNext (di)) != NULL ) { clusterNode *node = dictGetVal (de); now = mstime (); if (node->flags & (CLUSTER_NODE_MYSELF|CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE)) continue ; if (nodeIsSlave (myself) && nodeIsMaster (node) && !nodeFailed (node)) { int okslaves = clusterCountNonFailingSlaves (node); if (okslaves == 0 && node->numslots > 0 && node->flags & CLUSTER_NODE_MIGRATE_TO) { orphaned_masters++; } if (okslaves > max_slaves) max_slaves = okslaves; if (nodeIsSlave (myself) && myself->slaveof == node) this_slaves = okslaves; } mstime_t ping_delay = now - node->ping_sent; mstime_t data_delay = now - node->data_received; if (node->link && now - node->link->ctime > server.cluster_node_timeout && node->ping_sent && ping_delay > server.cluster_node_timeout/2 && data_delay > server.cluster_node_timeout/2 ) { freeClusterLink (node->link); } if (node->link && node->ping_sent == 0 && (now - node->pong_received) > server.cluster_node_timeout/2 ) { clusterSendPing (node->link, CLUSTERMSG_TYPE_PING); continue ; } if (server.cluster->mf_end && nodeIsMaster (myself) && server.cluster->mf_slave == node && node->link) { clusterSendPing (node->link, CLUSTERMSG_TYPE_PING); continue ; } if (node->ping_sent == 0 ) continue ; mstime_t node_delay = (ping_delay < data_delay) ? ping_delay : data_delay; if (node_delay > server.cluster_node_timeout) { if (!(node->flags & (CLUSTER_NODE_PFAIL|CLUSTER_NODE_FAIL))) { serverLog (LL_DEBUG,"*** NODE %.40s possibly failing" , node->name); node->flags |= CLUSTER_NODE_PFAIL; update_state = 1 ; } } } dictReleaseIterator (di); if (nodeIsSlave (myself) && server.masterhost == NULL && myself->slaveof && nodeHasAddr (myself->slaveof)) { replicationSetMaster (myself->slaveof->ip, myself->slaveof->port); } manualFailoverCheckTimeout (); if (nodeIsSlave (myself)) { clusterHandleManualFailover (); if (!(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER)) clusterHandleSlaveFailover (); ...... if (orphaned_masters && max_slaves >= 2 && this_slaves == max_slaves && server.cluster_allow_replica_migration) clusterHandleSlaveMigration (max_slaves); } if (update_state || server.cluster->state == CLUSTER_FAIL) clusterUpdateState (); }
手动故障转移 其实 manual failover 的本质还是 failover,所以 redis 其实是把它转化成普通的 failover 来进行处理的,在升主阶段,核心就是看 repl_offset 是否一致,如果一致了,就可以当做普通 failover 处理了,如果不一致,就持续等待。
所以,你可以看到这个方法的最后,clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_MANUALFAILOVER) 其实就是在 clusterBeforeSleep 中再做一次检测;当然与此同时,clusterCron 也会每 100ms 检测一次。直到 repl_offset 一致为止,这时候,flag 就变成 CLUSTER_TODO_HANDLE_FAILOVER 了,redis 就会在下一次 clusterBeforeSleep 中,进行真正的故障转移了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void clusterHandleManualFailover (void ) { if (server.cluster->mf_end == 0 ) return ; if (server.cluster->mf_can_start) return ; if (server.cluster->mf_master_offset == -1 ) return ; if (server.cluster->mf_master_offset == replicationGetSlaveOffset ()) { server.cluster->mf_can_start = 1 ; serverLog (LL_WARNING, "All master replication stream processed, " "manual failover can start." ); clusterDoBeforeSleep (CLUSTER_TODO_HANDLE_FAILOVER); return ; } clusterDoBeforeSleep (CLUSTER_TODO_HANDLE_MANUALFAILOVER); }
新 slave 加入集群 replicationSetMaster方法,我们在高可靠主线-主从复制 一节已经介绍过了,主要工作是:
与 master 建立物理连接
给 master 发送自己的信息
从 master 同步数据(从 master 下载 rdb 文件)
4. clusterBeforeSleep 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 void clusterBeforeSleep (void ) { server.cluster->todo_before_sleep = 0 ; if (flags & CLUSTER_TODO_HANDLE_MANUALFAILOVER) { if (nodeIsSlave (myself)) { clusterHandleManualFailover (); if (!(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER)) clusterHandleSlaveFailover (); } } else if (flags & CLUSTER_TODO_HANDLE_FAILOVER) { clusterHandleSlaveFailover (); } if (flags & CLUSTER_TODO_UPDATE_STATE) clusterUpdateState (); if (flags & CLUSTER_TODO_SAVE_CONFIG) { int fsync = flags & CLUSTER_TODO_FSYNC_CONFIG; clusterSaveConfigOrDie (fsync); } }
故障转移 clusterHandleSlaveFailover
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 void clusterHandleSlaveFailover (void ) { mstime_t data_age; mstime_t auth_age = mstime () - server.cluster->failover_auth_time; int needed_quorum = (server.cluster->size / 2 ) + 1 ; int manual_failover = server.cluster->mf_end != 0 && server.cluster->mf_can_start; mstime_t auth_timeout, auth_retry_time; server.cluster->todo_before_sleep &= ~CLUSTER_TODO_HANDLE_FAILOVER; auth_timeout = server.cluster_node_timeout*2 ; if (auth_timeout < 2000 ) auth_timeout = 2000 ; auth_retry_time = auth_timeout*2 ; if (nodeIsMaster (myself) || myself->slaveof == NULL || (!nodeFailed (myself->slaveof) && !manual_failover) || (server.cluster_slave_no_failover && !manual_failover) || myself->slaveof->numslots == 0 ) { server.cluster->cant_failover_reason = CLUSTER_CANT_FAILOVER_NONE; return ; } if (server.repl_state == REPL_STATE_CONNECTED) { data_age = (mstime_t )(server.unixtime - server.master->lastinteraction) * 1000 ; } else { data_age = (mstime_t )(server.unixtime - server.repl_down_since) * 1000 ; } if (data_age > server.cluster_node_timeout) data_age -= server.cluster_node_timeout; if (server.cluster_slave_validity_factor && data_age > (((mstime_t )server.repl_ping_slave_period * 1000 ) + (server.cluster_node_timeout * server.cluster_slave_validity_factor))) { if (!manual_failover) { clusterLogCantFailover (CLUSTER_CANT_FAILOVER_DATA_AGE); return ; } } if (auth_age > auth_retry_time) { server.cluster->failover_auth_time = mstime () + 500 + random () % 500 ; server.cluster->failover_auth_count = 0 ; server.cluster->failover_auth_sent = 0 ; server.cluster->failover_auth_rank = clusterGetSlaveRank (); server.cluster->failover_auth_time += server.cluster->failover_auth_rank * 1000 ; if (server.cluster->mf_end) { server.cluster->failover_auth_time = mstime (); server.cluster->failover_auth_rank = 0 ; clusterDoBeforeSleep (CLUSTER_TODO_HANDLE_FAILOVER); } serverLog (LL_WARNING, "Start of election delayed for %lld milliseconds " "(rank #%d, offset %lld)." , server.cluster->failover_auth_time - mstime (), server.cluster->failover_auth_rank, replicationGetSlaveOffset ()); clusterBroadcastPong (CLUSTER_BROADCAST_LOCAL_SLAVES); return ; } if (server.cluster->failover_auth_sent == 0 && server.cluster->mf_end == 0 ) { int newrank = clusterGetSlaveRank (); if (newrank > server.cluster->failover_auth_rank) { long long added_delay = (newrank - server.cluster->failover_auth_rank) * 1000 ; server.cluster->failover_auth_time += added_delay; server.cluster->failover_auth_rank = newrank; serverLog (LL_WARNING, "Replica rank updated to #%d, added %lld milliseconds of delay." , newrank, added_delay); } } if (mstime () < server.cluster->failover_auth_time) { clusterLogCantFailover (CLUSTER_CANT_FAILOVER_WAITING_DELAY); return ; } if (auth_age > auth_timeout) { clusterLogCantFailover (CLUSTER_CANT_FAILOVER_EXPIRED); return ; } if (server.cluster->failover_auth_sent == 0 ) { server.cluster->currentEpoch++; server.cluster->failover_auth_epoch = server.cluster->currentEpoch; serverLog (LL_WARNING,"Starting a failover election for epoch %llu." , (unsigned long long ) server.cluster->currentEpoch); clusterRequestFailoverAuth (); server.cluster->failover_auth_sent = 1 ; clusterDoBeforeSleep (CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE| CLUSTER_TODO_FSYNC_CONFIG); return ; } if (server.cluster->failover_auth_count >= needed_quorum) { serverLog (LL_WARNING, "Failover election won: I'm the new master." ); if (myself->configEpoch < server.cluster->failover_auth_epoch) { myself->configEpoch = server.cluster->failover_auth_epoch; serverLog (LL_WARNING, "configEpoch set to %llu after successful failover" , (unsigned long long ) myself->configEpoch); } clusterFailoverReplaceYourMaster (); } else { clusterLogCantFailover (CLUSTER_CANT_FAILOVER_WAITING_VOTES); } }
升主 clusterFailoverReplaceYourMaster
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void clusterFailoverReplaceYourMaster (void ) { int j; clusterNode *oldmaster = myself->slaveof; if (nodeIsMaster (myself) || oldmaster == NULL ) return ; clusterSetNodeAsMaster (myself); replicationUnsetMaster (); for (j = 0 ; j < CLUSTER_SLOTS; j++) { if (clusterNodeGetSlotBit (oldmaster,j)) { clusterDelSlot (j); clusterAddSlot (myself,j); } } clusterUpdateState (); clusterSaveConfigOrDie (1 ); clusterBroadcastPong (CLUSTER_BROADCAST_ALL); resetManualFailover (); }
clusterUpdateState 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 void clusterUpdateState (void ) { int j, new_state; int reachable_masters = 0 ; static mstime_t among_minority_time; static mstime_t first_call_time = 0 ; server.cluster->todo_before_sleep &= ~CLUSTER_TODO_UPDATE_STATE; if (first_call_time == 0 ) first_call_time = mstime (); if (nodeIsMaster (myself) && server.cluster->state == CLUSTER_FAIL && mstime () - first_call_time < CLUSTER_WRITABLE_DELAY) return ; new_state = CLUSTER_OK; if (server.cluster_require_full_coverage) { for (j = 0 ; j < CLUSTER_SLOTS; j++) { if (server.cluster->slots[j] == NULL || server.cluster->slots[j]->flags & (CLUSTER_NODE_FAIL)) { new_state = CLUSTER_FAIL; break ; } } } { dictIterator *di; dictEntry *de; server.cluster->size = 0 ; di = dictGetSafeIterator (server.cluster->nodes); while ((de = dictNext (di)) != NULL ) { clusterNode *node = dictGetVal (de); if (nodeIsMaster (node) && node->numslots) { server.cluster->size++; if ((node->flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) == 0 ) reachable_masters++; } } dictReleaseIterator (di); } { int needed_quorum = (server.cluster->size / 2 ) + 1 ; if (reachable_masters < needed_quorum) { new_state = CLUSTER_FAIL; among_minority_time = mstime (); } } if (new_state != server.cluster->state) { mstime_t rejoin_delay = server.cluster_node_timeout; if (rejoin_delay > CLUSTER_MAX_REJOIN_DELAY) rejoin_delay = CLUSTER_MAX_REJOIN_DELAY; if (rejoin_delay < CLUSTER_MIN_REJOIN_DELAY) rejoin_delay = CLUSTER_MIN_REJOIN_DELAY; if (new_state == CLUSTER_OK && nodeIsMaster (myself) && mstime () - among_minority_time < rejoin_delay) { return ; } serverLog (LL_WARNING,"Cluster state changed: %s" , new_state == CLUSTER_OK ? "ok" : "fail" ); server.cluster->state = new_state; } }
三、一些特殊的情况
如果 master 离线一段时间又上线,会怎么样?
新旧 master 都声称自己有同一段 slot,而新 master 的 configEpoch 更大,所以新 master 仍然是 master,旧 master 在广播自己的信息之后,会收到其他节点的 upgrade 请求,旧 master 会更新自己的配置,重新以 replica 的身份加入集群。
重新加入集群的节点,它的 slot 是如何变化的?
通常来说 ,触发 failover 之后,旧 master 会变为新 master 的 replica。但是如果发生了 hash slot 的变动,就会通过下面的规则来处理:
A master node will change its configuration to replicate (be a replica of) the node that stole its last hash slot
举例来说:假设一个节点 A 包含有 slot 1,2 ,A 离线之后,slot 1 被分配给 B,slot 2 被分配给 C。如果 A 又重新加入了集群,其他节点在收到 A 的广播之后,会给 A 发送 upgrade 请求,让 A 更新自己的配置,此时 A 会变为 C 的 replica。即:收到 A 广播的 master 节点,会找到当前承载 A 的最后一个 slot(也就是2)的节点(也就是C),让 A 变为 C 的 replica。
其他的 replica 节点也遵守相同的规则:
they reconfigure to replicate the node that stole the last hash slot of its former master.
他们都会被通知变为 C 的 replica(如果没有人工介入的话)
四、参考 不得不说,**官方文档 - Cluster Spec ** 才是最好的老师,很多细节上的问题、边界问题、特殊情况,都在这里有说明;缺点就是纯英文,我断断续续看了快一个月才看完,但是好处也是明显的,整个 cluster 机制,不再像以前一样是一个黑盒了
这里还有一个网友翻译的中文站点 ,不过机翻痕迹太严重了,基本没法看。
https://blog.csdn.net/Edidaughter/article/details/116403296
https://time.geekbang.org/column/article/276545