自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

來(lái)聊聊 Redis 哨兵如何主觀認(rèn)定下線

數(shù)據(jù)庫(kù) Redis
本文我們分析 redis 如何通過(guò) raft 完成哨兵 leader 選舉,并完成主從節(jié)點(diǎn)故障轉(zhuǎn)移工作,

上一篇我們將redis哨兵初始化分析完成,接下來(lái)我們就可以開(kāi)始分析redis如何通過(guò)raft完成哨兵leader選舉,并完成主從節(jié)點(diǎn)故障轉(zhuǎn)移工作,因?yàn)槠?,關(guān)于redis故障轉(zhuǎn)移的內(nèi)容將分為兩個(gè)篇章,而這篇討論的是哨兵如何完成主觀下線的判定。

一、詳解哨兵的主觀認(rèn)定下線的流程

1. 簡(jiǎn)述raft協(xié)議

在正式開(kāi)始后續(xù)的文章討論前,我們先來(lái)簡(jiǎn)單介紹一下分布式共識(shí)raft協(xié)議,這個(gè)是分布式系統(tǒng)中保證高可用的選舉協(xié)議。該協(xié)議將所有分布式系統(tǒng)的節(jié)點(diǎn)分為3個(gè)角色:

  • leader: 當(dāng)前分布式集群中的主節(jié)點(diǎn),即集群中的領(lǐng)導(dǎo)角色,負(fù)責(zé)承載當(dāng)前系統(tǒng)中的核心業(yè)務(wù)。
  • follower: 從節(jié)點(diǎn),作為leader節(jié)點(diǎn)的跟隨節(jié)點(diǎn)。
  • candidate:一旦leader發(fā)生故障被slave感知,那么這些節(jié)點(diǎn)會(huì)將自身角色轉(zhuǎn)為Canadian,并發(fā)起選舉,得票數(shù)最多的Canadian將轉(zhuǎn)為新的leader。

正常情況下,被選舉為leader的節(jié)點(diǎn)會(huì)向follower節(jié)點(diǎn)發(fā)送心跳,告知自己當(dāng)前還未下線:

一旦follower感知到leader下線,就會(huì)將自己身份轉(zhuǎn)換為candidate,通過(guò)選舉競(jìng)爭(zhēng)leader,每一個(gè)candidate都會(huì)給自己投一票然后向其他選舉節(jié)點(diǎn)獲取選票,在選舉計(jì)時(shí)時(shí)間以?xún)?nèi),超過(guò)半數(shù)以上得票的candidate就會(huì)被選舉為新的leader節(jié)點(diǎn),其余candidate收到此leader的心跳消息后身份就會(huì)轉(zhuǎn)為最新leader節(jié)點(diǎn)的follower:

2. redis中的raft協(xié)議與核心流程

與傳統(tǒng)raft協(xié)議實(shí)現(xiàn)有所不同,redis哨兵在未發(fā)生選舉時(shí)地位是對(duì)等并無(wú)leader和follower等概念,只有感知到監(jiān)聽(tīng)主節(jié)點(diǎn)下線時(shí)才會(huì)借助raft的協(xié)議觸發(fā)選舉,選舉出一個(gè)哨兵作為leader完成故障轉(zhuǎn)移之后,leader哨兵會(huì)再次回歸對(duì)等地位。

redis哨兵執(zhí)行的生命周期還是交由時(shí)間事件定時(shí)執(zhí)行,它的整體工作流程為:

  • 檢查自己所監(jiān)聽(tīng)的master連接情況,檢查是否與監(jiān)聽(tīng)的master節(jié)點(diǎn)斷開(kāi)連接,如果發(fā)現(xiàn)連接斷開(kāi)則進(jìn)行斷線重連。
  • 再對(duì)master節(jié)點(diǎn)進(jìn)行消息通信,這期間哨兵會(huì)發(fā)送ping與主節(jié)點(diǎn)保持通信,再發(fā)送info請(qǐng)求master最新信息。
  • 一旦發(fā)現(xiàn)master長(zhǎng)時(shí)間未與自己進(jìn)行心跳,則主觀視為監(jiān)聽(tīng)節(jié)點(diǎn)下線,并通過(guò)頻道告知其他哨兵獲取其他哨兵對(duì)于主節(jié)點(diǎn)的結(jié)果判斷。
  • 如果哨兵一致認(rèn)定當(dāng)前監(jiān)聽(tīng)節(jié)點(diǎn)下線,則會(huì)選舉出一個(gè)哨兵作為leader進(jìn)行故障轉(zhuǎn)移,即在所有從節(jié)點(diǎn)中找到一個(gè)優(yōu)先級(jí)最高的從節(jié)點(diǎn)作為新的master。

對(duì)此我我們給出程序執(zhí)行的入口來(lái)查看這塊核心的主流程,可以看到serverCron定時(shí)執(zhí)行的時(shí)間時(shí)間會(huì)每100ms執(zhí)行一次哨兵的時(shí)間事件sentinelTimer,對(duì)此我們不妨步入sentinelTimer查看實(shí)現(xiàn)細(xì)節(jié):

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
//......    
    //100ms一次,如果是哨兵模式則運(yùn)行哨兵的時(shí)間事件
    run_with_period(100) {
        if (server.sentinel_mode) sentinelTimer();
    }
//......
}

步入sentinelTimer,該函數(shù)會(huì)先判斷哨兵執(zhí)行時(shí)間是否過(guò)長(zhǎng),如果發(fā)現(xiàn)時(shí)鐘回?fù)芑蛘唛L(zhǎng)時(shí)間才進(jìn)行處理則觸發(fā)tilt模式,該模式下哨兵只會(huì)定期發(fā)送和接收消息,不做其他任務(wù)處理。

再調(diào)用sentinelHandleDictOfRedisInstances遍歷哨兵中的master開(kāi)始開(kāi)始進(jìn)行我們上述所說(shuō)的判斷與master連接狀態(tài)、進(jìn)行通信和info消息獲取、主觀下線判斷、客觀下線判斷、故障轉(zhuǎn)移。

完成這些步驟之后,更新下一次的執(zhí)行時(shí)間,可以看到redis對(duì)于這個(gè)時(shí)間設(shè)置做了一個(gè)巧妙的設(shè)計(jì),我們都知道哨兵判定節(jié)點(diǎn)下線后就會(huì)發(fā)起選舉,為了避免哨兵集群所有節(jié)點(diǎn)同時(shí)發(fā)起選舉投票從而得到相同票數(shù)的情況而導(dǎo)致本輪選舉失敗而進(jìn)行反復(fù)選舉的情況,redis會(huì)在哨兵本次時(shí)間事件執(zhí)行完成之后,通過(guò)隨機(jī)種子調(diào)整哨兵時(shí)間下一次的執(zhí)行時(shí)機(jī),盡可能避免選舉時(shí)反復(fù)出現(xiàn)選票一致的情況:

對(duì)此我們也給出sentinelTimer的實(shí)現(xiàn)細(xì)節(jié):

void sentinelTimer(void) {
    // 前置檢查事件定期任務(wù)是否因?yàn)橄到y(tǒng)負(fù)載過(guò)大或者各種原因?qū)е聲r(shí)鐘回?fù)?,或者處理過(guò)長(zhǎng),進(jìn)入tilt模式,該模式哨兵只會(huì)定期發(fā)送和接收命令
    sentinelCheckTiltCondition();
    //監(jiān)聽(tīng)的master節(jié)點(diǎn)作為參數(shù)傳入,進(jìn)行逐個(gè)通信處理
    sentinelHandleDictOfRedisInstances(sentinel.masters);
    //......
    //隨機(jī)調(diào)整執(zhí)行頻率避免同時(shí)執(zhí)行,確保提高選舉一次性成功的概率
    server.hz = REDIS_DEFAULT_HZ + rand() % REDIS_DEFAULT_HZ;
}

我們?cè)俅尾饺牒诵姆椒╯entinelHandleDictOfRedisInstances它會(huì)遍歷每一個(gè)master節(jié)點(diǎn),然后調(diào)用sentinelHandleRedisInstance處理每一個(gè)哨兵所監(jiān)聽(tīng)的master實(shí)例:

void sentinelHandleDictOfRedisInstances(dict *instances) {
     //.......
    //迭代出每一個(gè)master實(shí)例再對(duì)主節(jié)點(diǎn)進(jìn)行處理
    di = dictGetIterator(instances);
    while((de = dictNext(di)) != NULL) {
        sentinelRedisInstance *ri = dictGetVal(de);
        //迭代并處理每一個(gè)master實(shí)例
        sentinelHandleRedisInstance(ri);
        //.......
    }
    //.......
}

步入sentinelHandleRedisInstance即可看到我們上文所說(shuō)的而核心邏輯,它對(duì)于筆者上文的每一個(gè)流程都做了抽象,可以看到它會(huì)先嘗試和斷線的master建立連接,然后發(fā)送ping和info獲取master節(jié)點(diǎn)的確認(rèn)和master實(shí)時(shí)消息,最后在檢查master是否超時(shí)未回復(fù)發(fā)起主觀下線,然后再發(fā)起客觀下線請(qǐng)求確認(rèn)其他哨兵回復(fù)。 最后明確master節(jié)點(diǎn)確實(shí)下線之后再發(fā)起選舉,得出leader后由leader進(jìn)行故障轉(zhuǎn)移,挑選出新的master承載核心業(yè)務(wù)。

//這個(gè)入?yún)∩诒鴮?shí)例和當(dāng)前主節(jié)點(diǎn)的從節(jié)點(diǎn)信息
void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {
    /* ========== MONITORING HALF ============ */
    /* Every kind of instance */
    //1. 嘗試和斷連的實(shí)例重新建立連接
    sentinelReconnectInstance(ri);
    //2. 向?qū)嵗l(fā)送ping和info等命令
    sentinelSendPeriodicCommands(ri);

  //......

    /* Every kind of instance */
    //3. 主觀判斷是否下線
    sentinelCheckSubjectivelyDown(ri);

    /* Masters and slaves */
    if (ri->flags & (SRI_MASTER|SRI_SLAVE)) {
        /* Nothing so far. */
    }

    /* Only masters */
    if (ri->flags & SRI_MASTER) {
        //4. 檢查其當(dāng)前是否客觀下線
        sentinelCheckObjectivelyDown(ri);
        //5.  判斷是否要進(jìn)行故障切換,如果要啟動(dòng)故障切換,則獲取其他哨兵對(duì)于該節(jié)點(diǎn)的判斷
        if (sentinelStartFailoverIfNeeded(ri))
            sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_ASK_FORCED);
        //6. 執(zhí)行故障切換
        sentinelFailoverStateMachine(ri);
        //7. 再次獲取哨兵實(shí)例對(duì)主節(jié)點(diǎn)狀態(tài)的判斷
        sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS);
    }
}

3. 斷線重連檢查

基于上文我們了解哨兵時(shí)間事件執(zhí)行的大體流程,接下來(lái)我們會(huì)針對(duì)每一個(gè)流程進(jìn)行詳細(xì)的分析,首先我們先來(lái)了解一下對(duì)于斷線重連檢查方法,對(duì)于斷線重連檢查,redis哨兵通過(guò)兩個(gè)異步的連接進(jìn)行處理,它通過(guò)cc這個(gè)異步連接和master建立通信完成PING和INFO的消息發(fā)送,再通過(guò)pc處理各種廣播消息:

我們都知道redis將哨兵中每一個(gè)維護(hù)的master封裝成sentinelRedisInstance ,這其中就有cc和pc兩個(gè)連接指針,用于和當(dāng)前哨兵建立連接和通信:

typedef struct sentinelRedisInstance {
    //......
    //異步發(fā)送命令的連接
    redisAsyncContext *cc; /* Hiredis context for commands. */
    //pub/sub發(fā)送通道,用于處理頻道消息的收發(fā)
    redisAsyncContext *pc; 
 //......
}

此時(shí)我們?cè)賮?lái)查看sentinelReconnectInstance方法內(nèi)部,即非常直觀了解到其內(nèi)部對(duì)于斷開(kāi)或者為空的連接會(huì)調(diào)用redisAsyncConnectBind方法通過(guò)外部遍歷master傳入的master結(jié)構(gòu)體信息發(fā)起異步連接重建:

void sentinelReconnectInstance(sentinelRedisInstance *ri) {
    if (!(ri->flags & SRI_DISCONNECTED)) return;

    /* Commands connection. */
    //如果命令指針cc為空,則進(jìn)行一次異步重連
    if (ri->cc == NULL) {
     //基于外部遍歷傳入的master指針進(jìn)行異步重連
        ri->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,REDIS_BIND_ADDR);
        //如果連接失敗則調(diào)用sentinelKillLink銷(xiāo)毀該連接
        if (ri->cc->err) {
            sentinelEvent(REDIS_DEBUG,"-cmd-link-reconnection",ri,"%@ #%s",
                ri->cc->errstr);
            sentinelKillLink(ri,ri->cc);
        } else {
          //......
        }
    }
   
    //檢查發(fā)布訂閱pc,如果為空則將外部傳入的master信息通過(guò)異步的方式重新和頻道建立連接
    if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && ri->pc == NULL) {
        ri->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,REDIS_BIND_ADDR);
        if (ri->pc->err) {
            sentinelEvent(REDIS_DEBUG,"-pubsub-link-reconnection",ri,"%@ #%s",
                ri->pc->errstr);
            sentinelKillLink(ri,ri->pc);
        } else {
            //......
          
        }
    }
  //......
}

4. 消息通信

完成連接重建之后,在所有連接正常的情況下,哨兵會(huì)檢查當(dāng)前發(fā)送上次ping間隔是否超過(guò)指定間隔,如果是則通過(guò)cc指指針向master發(fā)送ping。 同理如果info消息超過(guò)發(fā)送間隔也會(huì)生成當(dāng)前哨兵ip端口等基本信息通過(guò)cc通道發(fā)送給masrter:

對(duì)此我們給出命令定期發(fā)送函數(shù)sentinelSendPeriodicCommands的入口,可以看到它會(huì)依次檢查ping和hello消息的間隔邏輯,然后按需通過(guò)cc發(fā)送ping或者h(yuǎn)ello消息:

void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
    //......
 //和其他哨兵處理的邏輯
    if ((ri->flags & SRI_SENTINEL) == 0 &&
        (ri->info_refresh == 0 ||
        (now - ri->info_refresh) > info_period))
    {
       //......
    } else if ((now - ri->last_pong_time) > ping_period) {//超過(guò)ping間隔發(fā)ping
       
        sentinelSendPing(ri);
    } else if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {//超過(guò)pub間隔通過(guò)cc發(fā)送當(dāng)前哨兵個(gè)人信息
        sentinelSendHello(ri);
    }
}

我們步入sentinelSendPing可以看到其內(nèi)部邏輯比較簡(jiǎn)單,通過(guò)cc發(fā)送ping然后更新上次發(fā)送ping的時(shí)間戳字段last_ping_time:

int sentinelSendPing(sentinelRedisInstance *ri) {
 //通過(guò)cc異步命令接口發(fā)送ping
    int retval = redisAsyncCommand(ri->cc,
        sentinelPingReplyCallback, NULL, "PING");
      //如果得到正常響應(yīng)則更新last_ping_time 
    if (retval == REDIS_OK) {
        ri->pending_commands++;
       
        if (ri->last_ping_time == 0) ri->last_ping_time = mstime();
        return 1;
    } else {
        return 0;
    }
}

同理我們給出sentinelSendHello函數(shù),可以看到其內(nèi)部會(huì)組裝當(dāng)前哨兵的ip和端口以及master的地址信息通過(guò)cc發(fā)送到__sentinel__:hello這個(gè)頻道中進(jìn)行廣播:

int sentinelSendHello(sentinelRedisInstance *ri) {
   //......

    /* Format and send the Hello message. */
    //將哨兵ip 端口以及master地址信息數(shù)據(jù)拼接到payload中
    snprintf(payload,sizeof(payload),
        "%s,%d,%s,%llu," /* Info about this sentinel. */
        "%s,%s,%d,%llu", /* Info about current master. */
        announce_ip, announce_port, server.runid,
        (unsigned long long) sentinel.current_epoch,
        /* --- */
        master->name,master_addr->ip,master_addr->port,
        (unsigned long long) master->config_epoch);
        //通過(guò)cc異步發(fā)送到__sentinel__:hello頻道中
    retval = redisAsyncCommand(ri->cc,
        sentinelPublishReplyCallback, NULL, "PUBLISH %s %s",
            SENTINEL_HELLO_CHANNEL,payload);
    if (retval != REDIS_OK) return REDIS_ERR;
    ri->pending_commands++;
    return REDIS_OK;
}

5. 判定主觀下線

然后就開(kāi)始主觀下線的檢查,可以看到redis一旦發(fā)現(xiàn)master長(zhǎng)時(shí)間未與當(dāng)前哨兵進(jìn)行通信,亦或者在很長(zhǎng)一段時(shí)間都被報(bào)告為從節(jié)點(diǎn),則將主觀判定其下線,再通過(guò)或預(yù)運(yùn)算符將ri的flags標(biāo)志位注明這個(gè)master已經(jīng)主觀的被認(rèn)定為下線。最后通過(guò)通過(guò) +sdown這個(gè)channel 發(fā)送主觀下線的消息,讓他們各自檢查,從而開(kāi)始后續(xù)客觀下線檢查及選舉和故障轉(zhuǎn)移等操作:

對(duì)應(yīng)的我們也給出sentinelCheckSubjectivelyDown函數(shù)的實(shí)現(xiàn),可以我們補(bǔ)充弄一下down_after_period 這個(gè)是就是決定Sentinel判斷實(shí)例進(jìn)入主觀下線所需的時(shí)間長(zhǎng)度,默認(rèn)情況下是30000毫秒,如果需要修改我們可以在redis.conf中用down-after-milliseconds指定:

void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
   //......
   
    //如果上一次到現(xiàn)在的間隔elapsed 大于down_after_period ,則當(dāng)前哨兵會(huì)主觀認(rèn)定其下線
    if (elapsed > ri->down_after_period ||
    //或者當(dāng)前哨兵認(rèn)定它是master而其他報(bào)告長(zhǎng)時(shí)間的反饋都是從節(jié)點(diǎn),則當(dāng)前哨兵會(huì)主觀認(rèn)定其下線
        (ri->flags & SRI_MASTER &&
         ri->role_reported == SRI_SLAVE &&
         mstime() - ri->role_reported_time >
          (ri->down_after_period+SENTINEL_INFO_PERIOD*2)))
    {
       
        //通過(guò) +sdown這個(gè)channel 發(fā)送主觀下線的消息
        if ((ri->flags & SRI_S_DOWN) == 0) {
            sentinelEvent(REDIS_WARNING,"+sdown",ri,"%@");
            //設(shè)置當(dāng)前監(jiān)控的master實(shí)例為主觀下線
            ri->s_down_since_time = mstime();
            ri->flags |= SRI_S_DOWN;
        }
    } else {
       //......
    }
}

二、小結(jié)

自此我們將redis哨兵主觀下線的核心流程分析完成,我們來(lái)簡(jiǎn)單小結(jié)一下哨兵判斷主觀下線的流程:

  • 哨兵實(shí)例隨機(jī)一個(gè)hz參數(shù)作為定時(shí)器執(zhí)行間隔,即執(zhí)行一個(gè)哨兵定時(shí)事件sentinelTimer,
  • sentinelTimer會(huì)定期調(diào)用sentinelHandleDictOfRedisInstances遍歷檢查監(jiān)控的master進(jìn)行定時(shí)的交互。
  • 哨兵實(shí)例定期發(fā)送ping和hello亦或者info請(qǐng)求給master。
  • master超過(guò)down_after_period設(shè)置的時(shí)間沒(méi)有回應(yīng),或者其他角色長(zhǎng)時(shí)間報(bào)告這個(gè)master已經(jīng)是slave,則當(dāng)前哨兵會(huì)主觀認(rèn)定其下線,并將消息發(fā)送到+sdown中。
  • 結(jié)束一次定時(shí)任務(wù)后,定時(shí)器sentinelTimer執(zhí)行完后設(shè)置下一次隨機(jī)執(zhí)行時(shí)間,保證在主觀與客觀認(rèn)定master下線后通過(guò)隨機(jī)性提升選舉的效率。
責(zé)任編輯:趙寧寧 來(lái)源: 寫(xiě)代碼的SharkChili
相關(guān)推薦

2022-05-17 22:20:41

哨兵Redis機(jī)制

2025-02-24 10:07:09

Redis節(jié)點(diǎn)遷移集群

2025-02-19 10:27:48

哨兵Redis故障轉(zhuǎn)移

2025-03-03 10:25:10

2023-10-25 08:01:30

redis主庫(kù)服務(wù)

2021-02-01 08:28:24

Linux線程池Linux系統(tǒng)

2022-06-21 07:51:06

Redis高可用哨兵進(jìn)程

2020-09-02 17:28:26

Spring Boot Redis集成

2024-02-04 09:00:00

向量查詢(xún)數(shù)據(jù)檢索MyScale

2017-10-17 14:05:52

網(wǎng)站Redis 2Redis 3

2024-02-21 08:19:54

2024-07-16 08:38:06

2025-02-13 11:11:53

Redis哨兵代碼

2023-09-27 06:26:07

2021-09-30 10:59:11

DockerRedisExporter

2023-10-26 07:47:53

Redis哨兵集群

2020-02-07 09:44:30

Redis哨兵數(shù)據(jù)庫(kù)

2023-03-15 08:30:37

2024-01-01 08:46:32

2019-01-28 09:32:30

跳槽員工程序員
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)