redis 选主过程分析

 当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有多个slave。Failover的过程需要经过类Raft协议的过程在整个集群内达到一致, 其过程如下:

  • slave发现自己的master变为FAIL
  • 将自己记录的集群currentEpoch加1,并广播Failover Request信息
  • 其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack
  • 尝试failover的slave收集FAILOVER_AUTH_ACK,超过半数后变成新Master,广播Pong通知其他集群节点
redis选主过程.png

redis 选主代码分析

 在作为slave角色节点会定期发送ping命令来检测master的存活性,如果检测到master未响应,那么就将master节点标记为疑似下线。
 clusterHandleSlaveFailover执行重新选主的核心逻辑。

void clusterCron(void) {delay = now - node->ping_sent;// 等待 PONG 回复的时长超过了限制值,将目标节点标记为 PFAIL (疑似下线)
if (delay > server.cluster_node_timeout) {if (!(node->flags & (REDIS_NODE_PFAIL|REDIS_NODE_FAIL))) {redisLog(REDIS_DEBUG,"*** NODE %.40s possibly failing",node->name);// 打开疑似下线标记node->flags |= REDIS_NODE_PFAIL;update_state = 1;}
}if (nodeIsSlave(myself)) {clusterHandleManualFailover();clusterHandleSlaveFailover();if (orphaned_masters && max_slaves >= 2 && this_slaves == max_slaves)clusterHandleSlaveMigration(max_slaves);}
}

 clusterHandleSlaveFailover内部通过clusterRequestFailoverAuth方法向集群当中的所有节点发送CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST报文,通知大家slave准备执行failover。
 当节点收到超过n/2+1个master的response后即升级为主。

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;int j;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;// #define nodeFailed(n) ((n)->flags & REDIS_NODE_FAIL)if (nodeIsMaster(myself) ||myself->slaveof == NULL ||(!nodeFailed(myself->slaveof) && !manual_failover) ||myself->slaveof->numslots == 0) return;// 将 data_age 设置为从节点与主节点的断开秒数if (server.repl_state == REDIS_REPL_CONNECTED) {data_age = (mstime_t)(server.unixtime - server.master->lastinteraction)* 1000;} else {data_age = (mstime_t)(server.unixtime - server.repl_down_since) * 1000;}// node timeout 的时间不计入断线时间之内if (data_age > server.cluster_node_timeout)data_age -= server.cluster_node_timeout;// 检查这个从节点的数据是否较新:// 目前的检测办法是断线时间不能超过 node timeout 的十倍if (data_age >((mstime_t)server.repl_ping_slave_period * 1000) +(server.cluster_node_timeout * REDIS_CLUSTER_SLAVE_VALIDITY_MULT)){if (!manual_failover) return;}if (auth_age > auth_retry_time) {server.cluster->failover_auth_time = mstime() +500 + /* Fixed delay of 500 milliseconds, let FAIL msg propagate. */random() % 500; /* Random delay between 0 and 500 milliseconds. */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;/* However if this is a manual failover, no delay is needed. */if (server.cluster->mf_end) {server.cluster->failover_auth_time = mstime();server.cluster->failover_auth_rank = 0;}redisLog(REDIS_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;redisLog(REDIS_WARNING,"Slave rank updated to #%d, added %lld milliseconds of delay.",newrank, added_delay);}}// 如果执行故障转移的时间未到,先返回if (mstime() < server.cluster->failover_auth_time) return;// 如果距离应该执行故障转移的时间已经过了很久// 那么不应该再执行故障转移了(因为可能已经没有需要了)// 直接返回if (auth_age > auth_timeout) return;// 向其他节点发送故障转移请求if (server.cluster->failover_auth_sent == 0) {// 增加配置纪元server.cluster->currentEpoch++;// 记录发起故障转移的配置纪元server.cluster->failover_auth_epoch = server.cluster->currentEpoch;redisLog(REDIS_WARNING,"Starting a failover election for epoch %llu.",(unsigned long long) server.cluster->currentEpoch);// 向其他所有节点发送信息,看它们是否支持由本节点来对下线主节点进行故障转移clusterRequestFailoverAuth();// 打开标识,表示已发送信息server.cluster->failover_auth_sent = 1;// TODO:// 在进入下个事件循环之前,执行:// 1)保存配置文件// 2)更新节点状态// 3)同步配置clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_FSYNC_CONFIG);return; /* Wait for replies. */}// 如果当前节点获得了足够多的投票,那么对下线主节点进行故障转移if (server.cluster->failover_auth_count >= needed_quorum) {// 旧主节点clusterNode *oldmaster = myself->slaveof;redisLog(REDIS_WARNING,"Failover election won: I'm the new master.");/* *    将当前节点的身份由从节点改为主节点*/clusterSetNodeAsMaster(myself);// 让从节点取消复制,成为新的主节点replicationUnsetMaster();// 接收所有主节点负责处理的槽for (j = 0; j < REDIS_CLUSTER_SLOTS; j++) {if (clusterNodeGetSlotBit(oldmaster,j)) {// 将槽设置为未分配的clusterDelSlot(j);// 将槽的负责人设置为当前节点clusterAddSlot(myself,j);}}// 更新集群配置纪元myself->configEpoch = server.cluster->failover_auth_epoch;// 更新节点状态clusterUpdateState();// 并保存配置文件clusterSaveConfigOrDie(1);// 向所有节点发送 PONG 信息// 让它们可以知道当前节点已经升级为主节点了clusterBroadcastPong(CLUSTER_BROADCAST_ALL);// 如果有手动故障转移正在执行,那么清理和它有关的状态resetManualFailover();}
}
/* * 向其他所有节点发送 FAILOVE_AUTH_REQUEST 信息,* 看它们是否同意由这个从节点来对下线的主节点进行故障转移。** 信息会被发送给所有节点,包括主节点和从节点,但只有主节点会回复这条信息。 */
void clusterRequestFailoverAuth(void) {unsigned char buf[sizeof(clusterMsg)];clusterMsg *hdr = (clusterMsg*) buf;uint32_t totlen;// 设置信息头(包含当前节点的信息)clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST);if (server.cluster->mf_end) hdr->mflags[0] |= CLUSTERMSG_FLAG0_FORCEACK;totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);hdr->totlen = htonl(totlen);// 发送信息clusterBroadcastMessage(buf,totlen);
}

 在redis主从选举过程中报文相关的解析逻辑,clusterProcessPacket内部主要处理CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST和CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK报文。

  • request报文的处理逻辑:如果master就发回ack响应
  • ack报文的处理逻辑:增加支持投票数failover_auth_count++
int clusterProcessPacket(clusterLink *link) {// 这是一条请求获得故障迁移授权的消息: sender 请求当前节点为它进行故障转移投票else if (type == CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST) {if (!sender) return 1; // 如果条件允许的话,向 sender 投票,支持它进行故障转移clusterSendFailoverAuthIfNeeded(sender,hdr);// 这是一条故障迁移投票信息: sender 支持当前节点执行故障转移操作} else if (type == CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK) {if (!sender) return 1; // 只有正在处理至少一个槽的主节点的投票会被视为是有效投票// 只有符合以下条件, sender 的投票才算有效:// 1) sender 是主节点// 2) sender 正在处理至少一个槽// 3) sender 的配置纪元大于等于当前节点的配置纪元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 = mstime() + REDIS_CLUSTER_MF_TIMEOUT;server.cluster->mf_slave = sender;pauseClients(mstime()+(REDIS_CLUSTER_MF_TIMEOUT*2));redisLog(REDIS_WARNING,"Manual failover requested by slave %.40s.",sender->name);} return 1;
}
// 在条件满足的情况下,为请求进行故障转移的节点 node 进行投票,支持它进行故障转移
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) return;// 已经投过票了if (server.cluster->lastVoteEpoch == server.cluster->currentEpoch) return;if (nodeIsMaster(node) || master == NULL ||(!nodeFailed(master) && !force_ack)) return;// 如果之前一段时间已经对请求节点进行过投票,那么不进行投票if (mstime() - node->slaveof->voted_time < server.cluster_node_timeout * 2)return;for (j = 0; j < REDIS_CLUSTER_SLOTS; j++) {// 跳过未指派节点if (bitmapTestBit(claimed_slots, j) == 0) continue;// 查找是否有某个槽的配置纪元大于节点请求的纪元if (server.cluster->slots[j] == NULL ||server.cluster->slots[j]->configEpoch <= requestConfigEpoch){continue;}// 如果有的话,说明节点请求的纪元已经过期,没有必要进行投票return;}/* We can vote for this slave. */// 为节点投票clusterSendFailoverAuth(node);// 更新时间值server.cluster->lastVoteEpoch = server.cluster->currentEpoch;node->slaveof->voted_time = mstime();
}

参考文章

redis cluster集群的源码分析(1)
Redis Cluster 实现细节

redis cluster集群选主相关推荐

  1. Docker搭建3主3从Redis Cluster集群

    本文使用镜像由慕课网的神思者老师提供 本文使用镜像是已经配置好了的Redis镜像, 如果需要自定义可修改配置文件或用官方Redis镜像进行部署 1. 拉取配置好的Redis镜像 docker pull ...

  2. redis cluster 集群 HA 原理和实操(史上最全、面试必备)

    文章很长,建议收藏起来慢慢读!疯狂创客圈总目录 语雀版 | 总目录 码云版| 总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 经典图书:<Java高并发核心编程(卷1)> 面试必备 ...

  3. Redis——cluster集群原理

    摘要 在 redis3.0之前,redis使用的哨兵架构,它借助 sentinel 工具来监控 master 节点的状态:如果 master 节点异常,则会做主从切换,将一台 slave 作为 mas ...

  4. Ubuntu 16.04下Redis Cluster集群搭建(官方原始方案)

    前提:先安装好Redis,参考:http://www.cnblogs.com/EasonJim/p/7599941.html 说明:Redis Cluster集群模式可以做到动态增加节点和下线节点,使 ...

  5. centos7 docker-compose安装_Docker Compose 搭建 Redis Cluster 集群环境

    在前文<Docker 搭建 Redis Cluster 集群环境>中我已经教过大家如何搭建了,本文使用 Docker Compose 再带大家搭建一遍,其目的主要是为了让大家感受 Dock ...

  6. redis cluster 集群 安装 配置 详解

    redis cluster 集群 安装 配置 详解 张映 发表于 2015-05-01 分类目录: nosql 标签:cluster, redis, 安装, 配置, 集群 Redis 集群是一个提供在 ...

  7. Redis Cluster 集群模式原理和动态扩容

    Redis Cluster原理 详细参考 Redis cluster集群模式的原理, 在这里补充下要点 16384个slot, 平均分布在各个master, key-value 对存储在slot中; ...

  8. Redis Cluster 集群扩容与收缩

    2019独角兽企业重金招聘Python工程师标准>>> Redis Cluster 集群伸缩 1. 伸缩原理 Redis提供了灵活的节点扩容和收缩方案.在不影响集群对外服务的情况下, ...

  9. Redis Cluster集群知识学习总结

    Redis集群解决方案有两个: 1)  Twemproxy: 这是Twitter推出的解决方案,简单的说就是上层加个代理负责分发,属于client端集群方案,目前很多应用者都在采用的解决方案.Twem ...

最新文章

  1. 【Codeforces】835B The number on the board (贪心)
  2. jquery设置属性值或移除属性
  3. python timer使用-Python timer定时器两种常用方法解析
  4. BZOJ1457 棋盘游戏
  5. java List的用法
  6. UA MATH571A 多元线性回归V 自相关与非线性模型简介
  7. 网易云信入选杭州市优质产品推荐目录!
  8. 用wxpython做ui_wxPython - 如何强制UI刷新?
  9. Tax debug and BP number external generation
  10. 【干货】规模化敏捷DevOps四大实践之持续探索CE(中英对照版)
  11. Java多线程多个线程之间共享数据
  12. Oracle 隔离级别
  13. 如何解除国外听QQ音乐网易音乐地区版权限制解除
  14. java数据结构和算法——前缀表达式(即波兰表达式)、中缀表达式及后缀表达式(即逆波兰表达式)介绍
  15. winform中ComboBox下拉框控件的动态数据填充
  16. Django 表单 AuthenticationFrom自动检测user的name 和 password , has no attributes cleaned_data
  17. 移动端h5头像上传、头像裁切、上传图片
  18. 【Linux】Ubuntu 18.04桌面美化
  19. 牛客算法竞赛入门笔记1
  20. 低代码平台——未来和2023年需要考虑的6个关键趋势

热门文章

  1. 微信python天天学_刚学Python一礼拜!我就能模拟登录微信公众号!我是天才吧!...
  2. html中看到php代码_如何在HTML中嵌入PHP代码
  3. java interface class_java interface和class中的协变
  4. centos7使用蓝牙_Nmon的使用和APP测试要点
  5. 范德蒙德矩阵在MATLAB中怎么表示,Python 之 Python与MATLAB 矩阵操作总结
  6. php使用webservivce_基于SSM框架实现简单的登录注册的示例代码
  7. python redis 订阅发布_Python-Redis的发布与订阅
  8. layui中laydate兼容ie_layui菜鸟教程--乐字节前端
  9. 多个linux发行版本混合安装盘,使用 MultiBootUSB 安装多个 Linux 版本
  10. 测试一些利用PYTHON完成中英文翻译的效果