本文档次要基于集体了解,剖析 Redis Cluster 设计中对于 Availability 的一些考量。
当 Redis 以 Cluster 模式启动时,对于一个 master 节点,只有当集群为 CLUSTER\_OK 状态时,能力失常承受拜访,这在之前的博客《Redis Cluster write safety 剖析》探讨过。
Redis Cluster 设计的三个指标,最初一个才是可用性。
Redis Cluster is able to survive partitions where the majority of the master nodes are reachable and there is at least one reachable slave for every master node that is no longer reachable. Moreover using replicas migration, masters no longer replicated by any slave will receive one from a master which is covered by multiple slaves.
上面次要探讨三种状况。
partition 故障
Redis Cluster 在产生 partition 后,minority 局部是不可用的。
假如 majority 局部有过半数 master 及每个 unreachable master 的一个 slave。那么,通过 NODE\_TIMEOUT 工夫加额定几秒钟(给 slave 进行 failover),cluster 复原可用状态。
当集群中有过半数 master 可达,cluster 就不会标记成 CLUSTER\_FAIL。
replicas migration 性能
Redis Cluster 的设计,保障了多数节点产生故障时,集群仍然可用。
举个例子,蕴含 N 个 master 的集群,每个 master 有惟一 slave。
单个 node 呈现故障,cluster 依然可用,第二个 node 再呈现故障,集群依然可用的概率是 1-(1/(N*2-1)。
计算形式如下,第一个 node fail 后,集群剩下 N*2-1 个衰弱节点,此时 orphan master 恰好 fail 的概率是 1/(N*2-1)。
套用公式,假如 N = 5,那么,2 个节点从 majority partition 进来,集群不可用的概率是 11.11%。
redis 为了进步集群可用性,提供了 replicas migration 性能,代码剖析如下,
void clusterCron(void) {
int orphaned_masters;
int max_slaves;
int this_slaves;
...
di = dictGetSafeIterator(server.cluster->nodes);
while((de = dictNext(di)) != NULL) {clusterNode *node = dictGetVal(de);
...
if (nodeIsSlave(myself) &&
nodeIsMaster(node) &&
!nodeFailed(node)) {
// 统计 node 有几个衰弱的 slave
int okslaves = clusterCountNonFailingSlaves(node);
// 没有 slave 但仍然负责 slot 的 master
if (okslaves == 0 &&
node->numslots > 0 &&
node->flags & CLUSTER_NODE_MIGRATE_TO)
{orphaned_masters++;}
if (okslaves > max_slaves) max_slaves = okslaves;
// node 是我的 master
if (nodeIsSlave(myself) && myself->slaveof == node)
this_slaves = okslaves;
}
}
...
if (nodeIsSlave(myself)) {
...
if (orphaned_masters && max_slaves >= 2 && this_slaves == max_slaves)
clusterHandleSlaveMigration(max_slaves);
}
...
}
首先定义了什么样的节点算 orphaned master,即,负责局部 slot 但没有衰弱 slave 的 master。orphaned master 有可用性危险,一旦挂掉,则整个 sharding 不可用。
以上代码能够看出,当 slave 检测到本人的 master 领有不少于 2 个衰弱 slave,且 cluster 中恰好有 orphan master 时,触发 clusterHandleSlaveMigration
函数逻辑,尝试进行 slave 漂移,漂移步骤有 4 步,上面进行分步阐明。
(1)CLUSTER\_FAIL 集群漂移。
if (server.cluster->state != CLUSTER_OK) return;
非 CLUSTER\_OK 集群原本就无奈失常承受申请,漂移多此一举,疏忽掉这种状况。
(2)查看 cluster-migration-barrier 参数。
redis conf 提供了 cluster-migration-barrier 参数 ,用来决定 slave 数量达到多少个才会把冗余 slave 漂移进来。
for (j = 0; j < mymaster->numslaves; j++)
if (!nodeFailed(mymaster->slaves[j]) &&
!nodeTimedOut(mymaster->slaves[j])) okslaves++;
if (okslaves <= server.cluster_migration_barrier) return;
只有 mymaster 衰弱 slave 的个数超过 cluster-migration-barrier 配置的数量时,才会漂移。
(3)选出要漂移的 slave 以及漂给谁。
candidate = myself;
di = dictGetSafeIterator(server.cluster->nodes);
while((de = dictNext(di)) != NULL) {clusterNode *node = dictGetVal(de);
int okslaves = 0, is_orphaned = 1;
if (nodeIsSlave(node) || nodeFailed(node)) is_orphaned = 0;
if (!(node->flags & CLUSTER_NODE_MIGRATE_TO)) is_orphaned = 0;
if (nodeIsMaster(node)) okslaves = clusterCountNonFailingSlaves(node);
if (okslaves > 0) is_orphaned = 0;
if (is_orphaned) {if (!target && node->numslots > 0) target = node;
if (!node->orphaned_time) node->orphaned_time = mstime();} else {node->orphaned_time = 0;}
if (okslaves == max_slaves) {for (j = 0; j < node->numslaves; j++) {if (memcmp(node->slaves[j]->name,
candidate->name,
CLUSTER_NAMELEN) < 0)
{candidate = node->slaves[j];
}
}
}
}
dictReleaseIterator(di);
抉择 node name 最小的 slave,漂移给遍历到的第一个 orphaned master,如果有多个。
(4)执行漂移。
#define CLUSTER_SLAVE_MIGRATION_DELAY 5000
if (target && candidate == myself &&
(mstime()-target->orphaned_time) > CLUSTER_SLAVE_MIGRATION_DELAY)
{
serverLog(LL_WARNING,"Migrating to orphaned master %.40s",
target->name);
clusterSetMaster(target);
}
在 failover 期间,master 有一段时间是没有 slave 的,为避免误漂,漂移必须有肯定的提早,工夫为 CLUSTER\_SLAVE\_MIGRATION\_DELAY,现版本为 5s。
sharding 缺失故障
默认状况下,当检测到有 slot 没有绑定,Redis Cluster 就会进行接管申请。
在这种配置下,如果 cluster 局部节点挂掉,也就是说一个范畴内的 slot 不再有节点负责,最终整个 cluster 会变得不能提供服务。
有时候,服务局部可用比整个不可用更有意义 ,因而,即便一部分 sharding 可用,也要让 cluster 提供服务。redis 将这种选择权交到了用户手中,conf 里提供 cluster-require-full-coverage 参数。
void clusterUpdateState(void) {
...
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;
}
}
}
...
}
以上代码能够看到,如果配置 cluster-require-full-coverage 为 yes,那么,有 slot 未绑定或者 sharding 缺失,会将 cluster 状态设置为 CLUSTER\_FAIL,server 就会拒绝请求。