为什么创建 Redis 集群时会自动错开主从节点?

在《一台服务器上部署 redis 伪集群》这篇文章中 , 咸鱼在创建 Redis 集群时并没有明确指定哪个 Redis 实例将担任 master , 哪个将担任 slave , 然而 Redis 却自动完成了主从节点的分配工作 。

为什么创建 Redis 集群时会自动错开主从节点?

文章插图
如果大家在多台服务器部署过 Redis 集群的话 , 比如说在三台机器上部署三主三从的 redis 集群 , 你会观察到 Redis 自动地将主节点和从节点的部署位置错开 。
举个例子:master 1 和 slave 3 在同一台机器上;master 2和 slave 1 在同一台机器上;master 3 和 slave 2 在同一台机器上 , 这是为什么呢?
我们知道老版本的 Redis 集群管理命令是 redis-trib.rb , 新版本则换成了 redis-cli , 这两个可执行文件其实是一个用 C 编写的脚本 , 小伙伴们如果看过这两个文件的源码就会发现原因就在下面这段代码里:
为什么创建 Redis 集群时会自动错开主从节点?

文章插图
通过注释我们可以得知 , clusterManagerGetAntiAffinityScore 函数是用来计算反亲和性得分 , 这个得分表示了当前 Redis 集群布局中是否符合反亲和性的要求 , 反亲和性指的是 master 和 slave 不应该在同一台机器上 , 也不应该在相同的 IP 地址上 , 那如何计算反亲和性得分呢?
如果有多个 slave 与同一个 master 在相同的 IP 地址上 , 那么对于每个这样的 slave , 得分增加 10000 如果有多个 slave 在相同的 IP 地址上 , 但它们彼此之间不是同一个 master , 那么对于每个这样的 slave , 得分增加 1 最终得分是上述两部分得分之和 。也就是说 , 得分越高 , 亲和性越高;得分越低 , 反亲和性越高;得分为零表示完全符合反亲和性的要求 。
获得得分之后 , 就会对得分高(反亲和性低)的节点进行优化 。
为了让 Redis 主从之间的反亲和性更高 , clusterManagerOptimizeAntiAffinity 函数会对那些反亲和性很低的节点进行优化 , 它会尝试通过交换从节点的主节点 , 来改善集群中主从节点分布 , 从而减少反亲和性低问题 。
接下来我们分别来看下这两个函数:
反亲和性得分计算
为什么创建 Redis 集群时会自动错开主从节点?

文章插图
可以看到 , 该函数接受了四个参数:
ipnodes:一个包含多个 clusterManagerNodeArray 结构体的数组 , 每个结构体表示一个 IP 地址上的节点数组 ip_count:IP 地址的总数 offending:用于存储违反反亲和性规则的节点的指针数组(可选参数) offending_len:存储 offending 数组中节点数量的指针(可选参数)第一层 for 循环是遍历 ip 地址 , 第二层循环是遍历每个 IP 地址的节点数组:
为什么创建 Redis 集群时会自动错开主从节点?

文章插图
我们来看下第二层 for 循环:
为什么创建 Redis 集群时会自动错开主从节点?

文章插图
首先遍历每个 IP 地址的节点数组 , 对于每个 IP 地址上的节点数组 , 函数通过字典related来记录相同主节点和从节点的关系 。
其中字典 related的 key 是节点的名称 , value 是一个字符串 , 表示该节点类型 types , 对于每个节点 , 根据节点构建一个字符串类型的关系标记(types) , 将主节点标记为 m , 从节点标记为 s , 然后通过字典将相同关系标记的节点关联在一起 , 构建了一个记录相同主从节点关系的字典 related:
为什么创建 Redis 集群时会自动错开主从节点?

文章插图
上面代码片段可知 , while 循环遍历字典 related中的分组信息 , 计算相同主从节点关系的得分:
获取节点类型信息并长度 如果是主节点类型 , 得分 += (10000 * (typeslen - 1));否则 , 得分 += (1 * typeslen)如果有提供 offending 参数 , 将找到违反反亲和性规则的节点并存储到 offending 数组中 , 同时更新违反规则节点的数量 , 如下代码所示:
最后返回得分 score , 完整代码如下:
static int clusterManagerGetAntiAffinityScore(clusterManagerNodeArray *ipnodes, int ip_count, clusterManagerNode ***offending, int *offending_len) { int score = 0, i, j; int node_len = cluster_manager.nodes->len; clusterManagerNode **offending_p = NULL; if (offending != NULL) { *offending = zcalloc(node_len * sizeof(clusterManagerNode*)); offending_p = *offending; } /* For each set of nodes in the same host, split by * related nodes (masters and slaves which are involved in * replication of each other) */ for (i = 0; i < ip_count; i++) { clusterManagerNodeArray *node_array = &(ipnodes[i]); dict *related = dictCreate(&clusterManagerDictType); char *ip = NULL; for (j = 0; j < node_array->len; j++) { clusterManagerNode *node = node_array->nodes[j]; if (node == NULL) continue; if (!ip) ip = node->ip; sds types; /* We always use the Master ID as key. */ sds key = (!node->replicate ? node->name : node->replicate); assert(key != NULL); dictEntry *entry = dictFind(related, key); if (entry) types = sdsdup((sds) dictGetVal(entry)); else types = sdsempty(); /* Master type 'm' is always set as the first character of the * types string. */ if (node->replicate) types = sdscat(types, "s"); else { sds s = sdscatsds(sdsnew("m"), types); sdsfree(types); types = s; } dictReplace(related, key, types); } /* Now it's trivial to check, for each related group having the * same host, what is their local score. */ dictIterator *iter = dictGetIterator(related); dictEntry *entry; while ((entry = dictNext(iter)) != NULL) { sds types = (sds) dictGetVal(entry); sds name = (sds) dictGetKey(entry); int typeslen = sdslen(types); if (typeslen < 2) continue; if (types[0] == 'm') score += (10000 * (typeslen - 1)); else score += (1 * typeslen); if (offending == NULL) continue; /* Populate the list of offending nodes. */ listIter li; listNode *ln; listRewind(cluster_manager.nodes, &li); while ((ln = listNext(&li)) != NULL) { clusterManagerNode *n = ln->value; if (n->replicate == NULL) continue; if (!strcmp(n->replicate, name) && !strcmp(n->ip, ip)) { *(offending_p++) = n; if (offending_len != NULL) (*offending_len)++; break; } } } //if (offending_len != NULL) *offending_len = offending_p - *offending; dictReleaseIterator(iter); dictRelease(related); } return score; } 反亲和性优化计算出反亲和性得分之后 , 对于那些得分很高的节点 , redis 就需要对其进行优化 , 提高集群中节点的分布 , 以避免节点在同一主机上


推荐阅读