Redis叢集分析(31)
1、 頭領選舉
在(30)中分析了在頭領選舉時哨兵伺服器之際的互動方式,接著我們繼續分析頭領選舉時如何統計選票,確認頭領。
在(30)中提到了如下程式碼:
在(30)中解析了互動用的sentinelAskMasterStateToOtherSentinels方法,這裡繼續解析下面的sentinelFailoverStateMachine方法,其內容如下:
void sentinelFailoverStateMachine(sentinelRedisInstance *ri) {
serverAssert(ri->flags & SRI_MASTER) ;
if (!(ri->flags & SRI_FAILOVER_IN_PROGRESS)) return;
switch(ri->failover_state) {
case SENTINEL_FAILOVER_STATE_WAIT_START:
sentinelFailoverWaitStart(ri);
break;
case SENTINEL_FAILOVER_STATE_SELECT_SLAVE:
sentinelFailoverSelectSlave (ri);
break;
case SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE:
sentinelFailoverSendSlaveOfNoOne(ri);
break;
case SENTINEL_FAILOVER_STATE_WAIT_PROMOTION:
sentinelFailoverWaitPromotion(ri);
break;
case SENTINEL_FAILOVER_STATE_RECONF_SLAVES:
sentinelFailoverReconfNextSlave(ri);
break;
}
}
這個方法很簡單,就是一個switch語句,其中switch的引數ri->failover_state在(30)中提到過。在具體分析這個引數前,先看其取值,即:
SENTINEL_FAILOVER_STATE_WAIT_START
SENTINEL_FAILOVER_STATE_SELECT_SLAVE
SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE
SENTINEL_FAILOVER_STATE_WAIT_PROMOTION
SENTINEL_FAILOVER_STATE_RECONF_SLAVES
他們都有相同的字首SENTINEL_FAILOVER_STATE。如果去掉這些字首,他們就變成了WAIT_START、SELECT_SLAVE、SEND_SLAVEOF_NOONE、WAIT_PROMOTION、RECONF_SLAVES。這裡我們首先看第一個:WAIT_START(第7行),即等待開始。這裡要需要先解釋故障轉移的具體操作流程,才能更好的理解WAIT_START的意義。
對於哨兵的故障轉移的起點是主客觀下線,主客觀下線的具體操作在之前解析過了。當哨兵判斷主伺服器客觀下線之後,就需要對主伺服器進行故障轉移,但是哨兵是一個叢集,不止一臺機器,而故障轉移只需要一臺機器進行執行便可。所以在開始真正的故障轉移前需要選擇一臺哨兵,而這個選擇就是通過頭領選舉來實現的。上面的WAIT_START的意義就是等待頭領選舉完成,然後開進行真正的故障轉移。
然後是第二個:SELECT_SLAVE(第10行),即選擇從伺服器。真正的故障轉移其實很簡單,就是從剩餘的還活著的從伺服器中選擇一臺作為新的主伺服器,然後對外提供伺服器。所以這裡的第二步是SELECT_SLAVE(選擇一個從伺服器)。
然後是第三個:SEND_SLAVEOF_NOONE(第13行),即傳送slaveof no one命令。這一步的意義在與將從伺服器變為主伺服器。在分析redis的主從模式的時候,講解了slaveof命令,當時提到了no one的意思是將從伺服器轉變為主伺服器。
然後是第四個:WAIT_PROMOTION(第16行),即等待轉變成功。
最後是第五個:RECONF_SLAVES(第19行),即重新配置從伺服器。有了新的主伺服器後,需要將其他的從伺服器設定為新的主伺服器的從伺服器。
然後我們再繼續分析ri->failover_state引數的取值問題。在(30)中我們分析了,在sentinelStartFailoverIfNeeded方法中,如果主伺服器是客觀下線的話,會執行一個sentinelStartFailover方法。這個方法會將ri->failover_state的值修改為SENTINEL_FAILOVER_STATE_WAIT_START。所以這裡我們首先看第7,8行failover_state為SENTINEL_FAILOVER_STATE_WAIT_START的情況。
這裡的處理也很簡單就是執行了一個sentinelFailoverWaitStart方法,其內容如下:
/* ---------------- Failover state machine implementation ------------------- */
void sentinelFailoverWaitStart(sentinelRedisInstance *ri) {
char *leader;
int isleader;
/* Check if we are the leader for the failover epoch. */
leader = sentinelGetLeader(ri, ri->failover_epoch);
isleader = leader && strcasecmp(leader,sentinel.myid) == 0;
sdsfree(leader);
/* If I'm not the leader, and it is not a forced failover via
* SENTINEL FAILOVER, then I can't continue with the failover. */
if (!isleader && !(ri->flags & SRI_FORCE_FAILOVER)) {
int election_timeout = SENTINEL_ELECTION_TIMEOUT;
/* The election timeout is the MIN between SENTINEL_ELECTION_TIMEOUT
* and the configured failover timeout. */
if (election_timeout > ri->failover_timeout)
election_timeout = ri->failover_timeout;
/* Abort the failover if I'm not the leader after some time. */
if (mstime() - ri->failover_start_time > election_timeout) {
sentinelEvent(LL_WARNING,"-failover-abort-not-elected",ri,"%@");
sentinelAbortFailover(ri);
}
return;
}
sentinelEvent(LL_WARNING,"+elected-leader",ri,"%@");
if (sentinel.simfailure_flags & SENTINEL_SIMFAILURE_CRASH_AFTER_ELECTION)
sentinelSimFailureCrash();
ri->failover_state = SENTINEL_FAILOVER_STATE_SELECT_SLAVE;
ri->failover_state_change_time = mstime();
sentinelEvent(LL_WARNING,"+failover-state-select-slave",ri,"%@");
}
首先是第7行,這裡呼叫了一個sentinelGetLeader方法,這個方法會統計投票的結果,其程式碼如下:
/* Scan all the Sentinels attached to this master to check if there
* is a leader for the specified epoch.
*
* To be a leader for a given epoch, we should have the majority of
* the Sentinels we know (ever seen since the last SENTINEL RESET) that
* reported the same instance as leader for the same epoch. */
char *sentinelGetLeader(sentinelRedisInstance *master, uint64_t epoch) {
dict *counters;
dictIterator *di;
dictEntry *de;
unsigned int voters = 0, voters_quorum;
char *myvote;
char *winner = NULL;
uint64_t leader_epoch;
uint64_t max_votes = 0;
serverAssert(master->flags & (SRI_O_DOWN|SRI_FAILOVER_IN_PROGRESS));
counters = dictCreate(&leaderVotesDictType,NULL);
voters = dictSize(master->sentinels)+1; /* All the other sentinels and me.*/
/* Count other sentinels votes */
di = dictGetIterator(master->sentinels);
while((de = dictNext(di)) != NULL) {
sentinelRedisInstance *ri = dictGetVal(de);
if (ri->leader != NULL && ri->leader_epoch == sentinel.current_epoch)
sentinelLeaderIncr(counters,ri->leader);
}
dictReleaseIterator(di);
/* Check what's the winner. For the winner to win, it needs two conditions:
* 1) Absolute majority between voters (50% + 1).
* 2) And anyway at least master->quorum votes. */
di = dictGetIterator(counters);
while((de = dictNext(di)) != NULL) {
uint64_t votes = dictGetUnsignedIntegerVal(de);
if (votes > max_votes) {
max_votes = votes;
winner = dictGetKey(de);
}
}
dictReleaseIterator(di);
/* Count this Sentinel vote:
* if this Sentinel did not voted yet, either vote for the most
* common voted sentinel, or for itself if no vote exists at all. */
if (winner)
myvote = sentinelVoteLeader(master,epoch,winner,&leader_epoch);
else
myvote = sentinelVoteLeader(master,epoch,sentinel.myid,&leader_epoch);
if (myvote && leader_epoch == epoch) {
uint64_t votes = sentinelLeaderIncr(counters,myvote);
if (votes > max_votes) {
max_votes = votes;
winner = myvote;
}
}
voters_quorum = voters/2+1;
if (winner && (max_votes < voters_quorum || max_votes < master->quorum))
winner = NULL;
winner = winner ? sdsnew(winner) : NULL;
sdsfree(myvote);
dictRelease(counters);
return winner;
}
這個方法會統計選票,確定選舉的結果。首先看第18行,這裡建立了一個名為counters的字典,字典的key為候選伺服器的runid,value為其票數。然後是第20行的voters,這個引數代表了投票的總數。然後是第22行到29行,這段程式碼在統計每臺伺服器的票數。這段程式碼其實也很簡單,首先是23行從引數master->sentinels(這個引數在解析哨兵如何發現其他哨兵伺服器的時候提到過,這個引數中儲存的是其發現的其他哨兵伺服器。)中取出所有的哨兵伺服器。然後是第24行使用一個while迴圈遍歷所有的伺服器,對於其中的每一個伺服器,首先檢查其epoch和leader是否符合條件(第26行),若符合條件則執行sentinelLeaderIncr方法(第27行),統計票數。
sentinelLeaderIncr方法的內容如下:
/* Helper function for sentinelGetLeader, increment the counter
* relative to the specified runid. */
int sentinelLeaderIncr(dict *counters, char *runid) {
dictEntry *existing, *de;
uint64_t oldval;
de = dictAddRaw(counters,runid,&existing);
if (existing) {
oldval = dictGetUnsignedIntegerVal(existing);
dictSetUnsignedIntegerVal(existing,oldval+1);
return oldval+1;
} else {
serverAssert(de != NULL);
dictSetUnsignedIntegerVal(de,1);
return 1;
}
}
這個方法很簡單,如果傳入的runid在counters中已經存在,那麼在已經存在的資料上加一,若不存在則新建一個並將其值設定為1。
到這裡其統計票數的程式碼便結束了。為了更好的理解其統計方式我們需要簡單總結一下其投票的過程。同樣是從主客觀下線開始,當其判斷主伺服器客觀下線後,便會立刻呼叫sentinelAskMasterStateToOtherSentinels方法,這個方法我們之前解析過,他會向其他的哨兵投票命令,並註冊一個名叫sentinelReceiveIsMasterDownReply的方法來處理其返回結果。當其他的哨兵接收到這個投票命令後,若未投票則將票投給他,若已投票則向其返回其投票的伺服器的runid。傳送投票的哨兵在接收到其返回後,會將資料記錄在代表該伺服器的例項(ri)中,而這個例項是儲存在引數master->sentinels中的。
然後便是這裡的統計票數的程式碼,它只需要遍歷一下所有的伺服器將其投的票統計出來便可,統計的方法便是sentinelLeaderIncr方法。
然後是sentinelGetLeader方法的第34行到43行,這段程式碼很簡單就是遍歷一下統計結果,拿到票數最多的伺服器runid和其票數。winner為其runid,max_votes為其得票數。
然後是第48行到60行,這段程式碼主要是統計當前哨兵伺服器的票。其中49行和51行的sentinelVoteLeader方法,在之前分析過,它會根據epoch來判斷是否投過票,不會重複投票。
最後是63行的if語句,這裡會有兩個條件:1、得票數過半;2、得票數超過其設定的quorum(配置哨兵伺服器時候設定的滿足客觀下線的哨兵數)。如果不滿足這兩個條件,那麼這次選舉不成立winner會被設定null。
至此,統計投票結果的sentinelGetLeader方法便分析完了。接著我們繼續看呼叫這個方法的sentinelFailoverWaitStart方法。呼叫統計投票結果的程式碼在第7行,拿到leader後,第8行會比較leader和其自身的runid,判斷其自身是否是leader。如果不是leader,則執行第13行到26行的程式碼。這段程式碼的主要作用是退出故障轉移。若是leader,則執行第27行及之後的程式碼,繼續執行故障轉移。其中重點在第30行,會將引數 ri->failover_state 的值設定為 SENTINEL_FAILOVER_STATE_SELECT_SLAVE。