一、zookeeper主从选举
zookeeper作为一个分布式应用程序协调服务,在大型网站中,其本身也是集群部署的,安装zookeeper的时候最好是单数节点,因为要选举。Zookeeper的leader节点是集群工作的核心,用来更新并保证leader和server具有相同的系统状态,Follower服务器是leader的跟随者,用于接收客户端的请求并向客户端返回结果,在选举过程中参与投票。对于客户端而言,每个zookeeper都是一样的。
zookeeper提供了三种选举策略:
1、LeaderElection
2、AuthFastLeaderElection
3、FastLeaderElection
下面主要讲解FastLeaderElection的选举原理
1、myId
每个ZooKeeper服务器,都需要在数据文件夹下创建一个名为myid的文件,该文件包含整个ZooKeeper集群唯一的ID(整数)。例如,某ZooKeeper集群包含三台服务器,hostname分别为zoo1、zoo2和zoo3,其myid分别为1、2和3,则在配置文件中其ID与hostname必须一一对应,如下所示。在该配置文件中,server.后面的数据即为myid
server.1=zoo1:2888:3888
server.2=zoo2:2888:3888
server.3=zoo3:2888:3888
2、zxid
服务器的事务id,数据越新,zxid越大;
3、epoch
逻辑时钟,在服务端是一个自增序列,每次进入下一轮投票后,就会加1;
4、服务器状态
LOOKING: 不确定Leader状态。该状态下的服务器认为当前集群中没有Leader,会发起Leader选举。
FOLLOWING: 跟随者状态。表明当前服务器角色是Follower,并且知道Leader是谁。
LEADING: 领导者状态。表明当前服务器角色为Leader角色,它会维护与Follower间的心跳。
OBSERVING: 观察者状态。表明当前服务器角色是Observer,与Follower的唯一不同在于不参与选举,也不参与集群写操作时的投票。
5、选票的数据结构
每个服务器在进行领导选举时,会发送如下关键信息:
logicClock 每个服务器会维护一个自增的整数,名为logicClock,它表示这是该服务器发起的第多少轮投票。
state 当前服务器的状态。
self_id 当前服务器的myid。
self_zxid 当前服务器上所保存的数据的最大zxid。
vote_id 被推举的服务器的myid。
vote_zxid 被推举的服务器上所保存的数据的最大zxid。
6、投票流程
当系统启动或者leader崩溃后,就会开始leader的选举。
1、状态变更。服务器启动的时候每个server的状态时Looking,如果是leader挂掉后进入选举,那么余下的非Observer的Server就会将自己的服务器状态变更为Looking,然后开始进入Leader的选举状态;
2、发起投票。每个server会产生一个(myid,zxid)的投票,系统初始化的时候zxid都是0,如果是运行期间,每个server的zxid可能都不同,这取决于最后一次更新的数据。将投票发送给集群中的所有机器;
3、接收并检查投票。server收到投票后,会先检查是否是本轮投票,是否来自looking状态的server;
4、处理投票。对自己的投票和接收到的投票进行PK:
**先检查zxid,较大的优先为leader**;
**如果zxid一样,myid较大的为leader**;
**根据PK结果更新自己的投票,在次发送自己的投票**;
5、统计投票。每次投票后,服务器统计投票信息,如果有过半机器接收到相同的投票,那么leader产生,如果否,那么进行下一轮投票;
6、改变server状态。一旦确定了Leader,server会更新自己的状态为Following或者是Leading。选举结束。
补充说明:
1、在步骤2发送投票的时候,投票的信息除了sid和zxid,还有:
electionEpoch:逻辑时钟,用来判断多个投票是否在同一轮选举周期中,该值在服务端是一个自增序列,每次进入新一轮的投票后,都会对该值进行加1操作。
peerEpoch:被推举的Leader的epoch。
state:当前服务器的状态。
2、为了能够相互投票,每两台服务器之间都会建立网络连接,为避免重复建立TCP连接,zk的server只允许sid大于自己的服务器与自己建立连接,否则断开当前连接,并主动和对方建立连接。
二、ES master选举
选举时间点
Elasticsearch在满足如下时间点的时候会触发选举
1、集群启动初始化。
2、集群Master崩溃的时候。
3、任何一个节点发现当前集群中Master节点没有得到n/2+1个节点的认可的时候,触发选举。
选举流程
1、筛选activeMaster列表
Es的master就是从activeMasters列表或者masterCandidates(master候选节点)列表选举出来,所以选举之前es首先需要得到这两个列表。Elasticsearch节点成员首先向集群中的所有成员发送Ping请求,elasticsearch默认等待discovery.zen.ping_timeout时间,然后elasticsearch针对获取的全部response进行过滤,筛选出其中activeMasters列表,activeMaster列表是其它节点认为的当前集群的Master节点。
源码如下:
List<DiscoveryNode> activeMasters = new ArrayList<>();
for (ZenPing.PingResponse pingResponse : pingResponses) {
//不允许将自己放在activeMasters列表中
if (pingResponse.master() != null && !localNode.equals(pingResponse.master())) {
activeMasters.add(pingResponse.master());
}
}
可以看到elasticsearch在获取activeMasters列表的时候会排除本地节点,目的是为了避免脑裂,假设这样一个场景,当前最小编号的节点P0认为自己就是master并且P0和其它节点发生网络分区,同时es允许将自己放在activeMaster中,因为P0编号最小,那么P0永远会选择自己作为master节点,那么就会出现脑裂的情况。
2、筛选masterCandidates列表
masterCandidates列表是当前集群有资格成为Master的节点,如果我们在elasticsearch.yml中配置了如下参数,那么这个节点就没有资格成为Master节点,也就不会被筛选进入masterCandidates列表。
# 配置某个节点没有成为master资格
node.master:false
源代码如下所示:
List<ElectMasterService.MasterCandidate> masterCandidates = new ArrayList<>();
for (ZenPing.PingResponse pingResponse : pingResponses) {
if (pingResponse.node().isMasterNode()) {
masterCandidates.add(new ElectMasterService.MasterCandidate(pingResponse.node(), pingResponse.getClusterStateVersion()));
}
}
3、从activeMasters列表选举Master节点
activeMaster列表是其它节点认为的当前集群的Master节点列表,如果activeMasters列表不为空,elasticsearch会优先从activeMasters列表中选举,选举的算法是Bully算法,Bully算法会涉及到优先级比较, 在activeMasters列表优先级比较的时候,如果节点有成为master的资格,那么优先级比较高,如果activeMaster列表有多个节点具有master资格,那么选择id最小的节点。
代码如下:
private static int compareNodes(DiscoveryNode o1, DiscoveryNode o2) {
if (o1.isMasterNode() && !o2.isMasterNode()) {
return -1;
}
if (!o1.isMasterNode() && o2.isMasterNode()) {
return 1;
}
return o1.getId().compareTo(o2.getId());
}
//取ID最小的
public DiscoveryNode tieBreakActiveMasters(Collection<DiscoveryNode> activeMasters) {
return activeMasters.stream().min(ElectMasterService::compareNodes).get();
}
4. 从masterCandidates列表选举Master节点
如果activeMaster列表为空,那么会在masterCandidates中选举,masterCandidates选举也会涉及到优先级比较,masterCandidates选举的优先级比较和activemasters选举的优先级比较不同。它首先会判断masterCandidates列表成员数目是否达到了最小数目discovery.zen.minimum_master_nodes。如果达到的情况下比较优先级,优先级比较的时候首先比较节点拥有的集群状态版本编号,然后再比较id,这一流程的目的是让拥有最新集群状态的节点成为master。
代码如下:
public static int compare(MasterCandidate c1, MasterCandidate c2) {
//比较集群状态版本编码,取版本大的,使拥有集群最新状态的节点成为master
int ret = Long.compare(c2.clusterStateVersion, c1.clusterStateVersion);
//如果版本号一样,则比较ID
if (ret == 0) {
ret = compareNodes(c1.getNode(), c2.getNode());
}
return ret;
}
5、本地节点是master
经过上述选举之后,会选举出一个准master节点, 准master节点会等待其它节点的投票,如果有discovery.zen.minimum_master_nodes-1个节点投票认为当前节点是master,那么选举就成功,准master会等待discovery.zen.master_election.wait_for_joins_timeout时间,如果超时,那么就失败。在代码实现上准master通过注册一个回调来实现,同时借助了AtomicReference和CountDownLatch等并发构建实现
if (clusterService.localNode().equals(masterNode)) {
final int requiredJoins = Math.max(0, electMaster.minimumMasterNodes() - 1);
nodeJoinController.waitToBeElectedAsMaster(requiredJoins, masterElectionWaitForJoinsTimeout,
new NodeJoinController.ElectionCallback() {
@Override
public void onElectedAsMaster(ClusterState state) {
joinThreadControl.markThreadAsDone(currentThread);
nodesFD.updateNodesAndPing(state); // start the nodes FD
}
@Override
public void onFailure(Throwable t) {
logger.trace("failed while waiting for nodes to join, rejoining", t);
joinThreadControl.markThreadAsDoneAndStartNew(currentThread);
}
}
);
本地节点是Master的时候,Master节点会开启错误检测(NodeFaultDetection机制),它节点会定期扫描集群所有的成员,将失活的成员移除集群,同时将最新的集群状态发布到集群中,集群成员收到最新的集群状态后会进行相应的调整,比如重新选择主分片,进行数据复制等操作。
6、本地节点不是master
当前节点判定在集群当前状态下如果自己不可能是master节点,首先会禁止其他节点加入自己,然后投票选举出准Master节点。同时监听master发布的集群状态(MasterFaultDetection机制),如果集群状态显示的master节点和当前节点认为的master节点不是同一个节点,那么当前节点就重新发起选举。
非Master节点也会监听Master节点进行错误检测,如果成员节点发现master连接不上,重新加入新的Master节点,如果发现当前集群中有很多节点都连不上master节点,那么会重新发起选举。
三、哨兵选举
哨兵的工作方式:
1、每个Sentinel(哨兵)进程以每秒一次的频率向整个集群中的Master主服务器、Slave从服务器以及其他哨兵进程发送一个PING命令。
2、如果一个实例距离最后一次有效回复PING命令的时间超过down-after-milliseconds选型所指定的值,则这个实例会被哨兵进程标记为主观下线。
3、如果一个Master主服务器被标记为主观下线,则正在监视这个Master主服务器的所有哨兵进程要以每秒一次的频率来确认Master主服务器是否的确进入了主观下线状态。
4、如果有足够数量(quorum数量)的哨兵进程认为Master主服务器下线,则Master主服务器会被标记成客观下线。
5、在一般情况下,每个哨兵进程以每秒一次的频率向集群中的所有master主服务器、Slave从服务器发送Info命令。当Master主服务器被标记为客观下线后,Info命令的频率从10秒一次改成1秒一次。
6、若没有足够数量的 Sentinel(哨兵)进程同意 Master主服务器下线, Master主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel(哨兵)进程发送 PING 命令返回有效回复,Master主服务器的主观下线状态就会被移除。
选举方式:
如果一个Master主服务器被标记为客观下线,并且Majority数量的哨兵都允许进行主备切换(如果没有Majority数量的哨兵同一主备切换时,是不允许进行主备切换的),选举一个领头的哨兵进行主备切换。
(一)选举领头的哨兵
选举领头sentinel遵循以下规则:
1、所有的sentinel都有公平被选择成领头的资格。
2、所有的sentinel都只有一次将某个sentinel选举成领头的机会(在一轮选举中),一旦选举,则不能修改。
3、先到先得,一旦当前sentinel设置了领头sentinel,以后要求设置sentinel都会被拒绝。
4、每个发现服务器客观下线的哨兵,都会要求其他哨兵将自己设置成领头哨兵。
5、当一个哨兵(源哨兵)向另一个哨兵(目标哨兵)发送is-master-down-by-addr ip port current_epoch runid命令的时候,runid参数不是*,而是sentinel运行id,就表示源sentinel要求目标sentinel选举其为领头。
6、源哨兵会检查目标哨兵对其设置成领头的回复,如果回复的leader_runid和leader_epoch为源哨兵,表示目标哨兵同意将源哨兵设置成领头。
7、如果某个哨兵被半数以上的哨兵设置成领头哨兵,则该哨兵就成为领头哨兵。
8、如果在限定时间内,没有选举出领头哨兵,暂停一段时间,再选举。
(二)Master主服务器选举
领头哨兵会对已客观宕机的Master主服务器和Slave从服务器进行主备切换。
选举Slave需要考虑以下的信息:
1、跟master断开连接的时长
2、slave的优先级
3、复制的offset
4、run Id
1、如果一个slave跟master断开连接的时间已经超过了down-after-milliseconds的10倍,外加master宕机的时长,那么认为该slave不适合选举为master。
2、按照slave的优先级进行排序,slave priority越低,优先级越高。
3、如果slave的优先级一致,那么看replica offset,哪个slave复制了越多的数据,offset越靠后,优先级就越高。
4、如果优先级和offset都一样,那么选择一个run ID比较小的那个slave。
四、Redis cluster选举
判断节点宕机
如果一个节点认为另外一个节点宕机,那么就是 pfail ,主观宕机。如果多个节点都认为另外一个节点宕机了,那么就是 fail ,客观宕机,跟哨兵的原理几乎一样,sdown,odown。
在 cluster-node-timeout 内,某个节点一直没有返回 pong ,那么就被认为 pfail 。
如果一个节点认为某个节点 pfail 了,那么会在 gossip ping 消息中, ping 给其他节点,如果超过半数的节点都认为 pfail 了,那么就会变成 fail 。
Redis Cluster Master主服务器选举:
对宕机的master node,从其所有slave node中,选择一个切换成master node。
1、检查每个slave node与master node断开连接的时间,如果超过了cluster-node-timeout * cluster-slave-validity-factor,那么没有资格切换成master。
2、每个从节点,根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制的数据越多)的从节点,选举时间越靠前,优先进行选举。
3、所有的master node开始slave选举投票,给要进行选举的slave node进行投票,如果大部分master(N/2+1)都投票给某个从节点,那么选举通过,那个从节点可以切换成master。
4、从节点进行主备切换,从节点切换成主节点。
五、kafka parition选举
Kafka的Leader选举是通过在zookeeper上创建/controller临时节点来实现leader选举,并在该节点中写入当前broker的信息{“version”:1,”brokerid”:1,”timestamp”:”1512018424988”}。利用Zookeeper的强一致性特性,一个节点只能被一个客户端创建成功,创建成功的broker即为leader,即先到先得原则,leader也就是集群中的controller,负责集群中所有大小事务。当leader和zookeeper失去连接时,临时节点会删除,而其他broker会监听该节点的变化,当节点删除时,其他broker会收到事件通知,重新发起leader选举。
kafka在所有broker中选出一个controller,所有Partition的Leader选举都由controller决定。controller会将Leader的改变直接通过RPC的方式(比zookeeper queue的方式有效)通知给需要为此作出响应的Broker。同时controller也负责增删Topic以及Replica的分配。