<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          你管這破玩意叫哨兵?

          共 6053字,需瀏覽 13分鐘

           ·

          2021-04-17 12:02


          低并發(fā)編程
          戰(zhàn)略上藐視技術(shù),戰(zhàn)術(shù)上重視技術(shù)

          我是一個苦逼的運(yùn)維,有一次老板過來找我。
          老板:現(xiàn)在有四個 redis 節(jié)點擺在你面前,一主三從,你負(fù)責(zé)盯著點,主節(jié)點掛了你趕緊想辦法拿從節(jié)點頂上來,交給你了!
          這還不簡單!
          首先我先分別連上這四臺 redis 節(jié)點。
          redis-cli -h 10.232.0.0 -p 6379
          redis-cli -h 10.232.0.1 -p 6379
          redis-cli -h 10.232.0.2 -p 6379
          redis-cli -h 10.232.0.3 -p 6379
          然后每隔 1s 分別發(fā)送 redis 專屬的命令 PING
          我就這樣一直不斷地發(fā)送著 PING 命令,日復(fù)一日。
          終于有一天,發(fā)送給主節(jié)點的 PING 命令收到了無效回復(fù)!
          我立刻打起了精神,開始操作了起來。
          但我沒有慌亂了手腳,很快我就梳理好了即將要做的三件事。

          選擇一個從節(jié)點,將其變?yōu)橹鞴?jié)點。


          選哪個節(jié)點好呢?先別管那么多了,隨便選一個,就 10.232.0.3:6379 這個吧!
          我對著這個節(jié)點,發(fā)送了一個命令。
          10.232.0.3:6379> slaveof no one
          OK
          我想,這個節(jié)點應(yīng)該就已經(jīng)變成了主節(jié)點了,但我不太敢確定,于是又發(fā)送了一個命令進(jìn)行確認(rèn)。
          10.232.0.3:6379> info
          ...
          role:slave
          誒,還沒有變成主節(jié)點呢,那再給他點時間。一秒鐘之后,我再次進(jìn)行查看。
          10.232.0.3:6379> info
          ...
          role:master
          嗯,這回已經(jīng)成功變成主節(jié)點啦,進(jìn)行下一步!

          修改其他從節(jié)點的附屬主節(jié)點

          很簡單,向另外兩臺從節(jié)點發(fā)送命令。
          10.232.0.1:6379> slaveof 10.232.0.3 6379
          OK
          10.232.0.2:6379> slaveof 10.232.0.3 6379
          OK

          將掛掉的主節(jié)點變?yōu)閺墓?jié)點

          這一步充分體現(xiàn)了我多年的運(yùn)維經(jīng)驗,很多人都想不到。
          原來的主節(jié)點我可不能不管,萬一他又復(fù)活了,就得乖乖成為新主節(jié)點的從節(jié)點。
          10.232.0.0:6379> slaveof 10.232.0.3 6379
          但是我不能直接發(fā)送這個命令給它,因為它還掛著呢,所以我將命令保存起來,只要它一復(fù)活我就發(fā)給它這個命令。
          整個三步看起來是這個樣子。
          經(jīng)過多次這樣的操作,我終于熟悉了整個流程。
          為了解放我自己的雙手,我把這個固定的流程,寫成了一個程序。
          這個程序能實時監(jiān)控這些 redis 節(jié)點的狀態(tài),并能自動報告并處理突發(fā)情況,我給他命名為哨兵程序
          而這個哨兵程序我單獨用一臺服務(wù)器部署,這個服務(wù)器就稱為哨兵節(jié)點
          哨兵一開始就連接這 4 個 redis 節(jié)點,并持續(xù)我剛剛的操作過程。

          優(yōu)化

          我還發(fā)現(xiàn)了一個小的優(yōu)化點,我無需知道這 4 個節(jié)點的全部信息,只需要知道主節(jié)點即可。
          從節(jié)點的信息,我通過向主節(jié)點發(fā)送 info 命令即可獲取,而且可以不斷獲取來更新。
          10.232.0.0:6379> info
          ...
          role:master
          ...
          slave0:ip=10.232.0.1,port=6379,state=online ...
          slave0:ip=10.232.0.2,port=6379,state=online ...
          slave0:ip=10.232.0.3,port=6379,state=online ...
          ...
          這樣,我在啟動哨兵時,只要知道主節(jié)點即可,而且這樣獲取的從節(jié)點信息更準(zhǔn)確,也更實時,就不用一直問老板啦。
          雖然已經(jīng)可以解放雙手,但興致來了的我仍然沒有收手。
          剛剛主節(jié)點掛了之后,我隨機(jī)從三個從節(jié)點中選擇了一個作為主節(jié)點,不妨讓這個隨機(jī)也智能一些吧,不然總覺得太 low。
          首先,我把所有的從節(jié)點的主要信息列出來(這里假設(shè)多一些節(jié)點方便分析)

          節(jié)點

          狀態(tài)

          距離上次回復(fù)的時間

          復(fù)制偏移量

          uid

          1

          DISCONNECTED

          8

          50

          12345

          2

          DOWN

          8

          50

          12346

          3

          7

          50

          12347

          4

          1

          50

          12348

          5

          DOWN

          8

          50

          12349

          6

          1

          50

          12350

          先去掉所有斷線或下線的節(jié)點。

          節(jié)點

          狀態(tài)

          距離上次

          回復(fù)的時間

          復(fù)制偏移量

          uid

          1

          DISCONNECTED

          8

          50

          12345

          2

          DOWN

          8

          50

          12346

          3

          7

          50

          12347

          4

          1

          50

          12348

          5

          DOWN

          8

          50

          12349

          6

          1

          50

          12350

          再去掉最后一個 ping 請求過去后,未回應(yīng)的時間大于 5s 的。

          節(jié)點

          狀態(tài)

          距離上次

          回復(fù)的時間

          復(fù)制偏移量

          uid

          1

          DISCONNECTED

          8

          50

          12345

          2

          DOWN

          8

          50

          12346

          3

          7

          50

          12347

          4

          1

          50

          12348

          5

          DOWN

          8

          50

          12349

          6

          1

          50

          12350

          剩下兩個,是至少狀態(tài)健康的節(jié)點,繼續(xù)擇優(yōu)錄取。
          我們比較其復(fù)制偏移量的值,這個代表其從主節(jié)點成功復(fù)制了多少數(shù)據(jù),選擇一個復(fù)制偏移量最多的,也就是與主節(jié)點最接近同步的。

          節(jié)點

          狀態(tài)

          距離上次

          回復(fù)的時間

          復(fù)制偏移量

          uid

          4

          1

          50

          12348

          6

          1

          50

          12350

          不過我們發(fā)現(xiàn)其偏移量一樣。
          到現(xiàn)在,這兩個節(jié)點無論從健康狀態(tài),還是同步狀態(tài),都是完全一樣的,沒辦法分出誰好誰壞了,那怎么辦呢?
          沒關(guān)系,還有一個終極武器,就是唯一標(biāo)識 uid,這兩個 uid 在啟動節(jié)點時就保證了必然不相同,我們選擇一個相對較小的。

          節(jié)點

          狀態(tài)

          距離上次

          回復(fù)的時間

          復(fù)制偏移量

          uid

          4

          1

          50

          12348

          OK,最終可以唯一確定一個從節(jié)點,就把它變?yōu)橹鞴?jié)點了!
          我把這個復(fù)雜的過程,寫成了一個方法,sentinelSelectSlave(),放在了哨兵程序中,用來選擇一個從節(jié)點。
          嗯,現(xiàn)在這個程序看起來,已經(jīng)很完善了!
          我放心地把這個哨兵程序啟動起來,之后的很長一段時間,我就靠著我的哨兵程序,成功自動應(yīng)對了很多次突發(fā)情況,有一次甚至在半夜兩點多迅速將問題發(fā)現(xiàn)并解決。
          老板一直夸我堅守崗位,半夜了還這么負(fù)責(zé),我很快得到了晉升。
          直到有一次,我正在開開心心摸魚,老板氣哄哄地走來。
          老板:redis 都掛了一個小時了!你怎么還不處理!額?你這是看什么?leetcode?是準(zhǔn)備跳槽了么!
          我一臉懵逼,趕緊看了一下我的哨兵進(jìn)程,我擦,哨兵服務(wù)器掛掉了!
          我被降了職,但仍然要負(fù)責(zé)看著這些 redis 節(jié)點,這回我可不敢怠慢了。
          我繼續(xù)用哨兵程序監(jiān)控著這些節(jié)點的生死,但我自己又多了一項任務(wù),就是監(jiān)控哨兵節(jié)點的狀態(tài),仿佛一夜回到解放前。
          怎么樣再次解放我的雙手,讓程序幫我去監(jiān)控和處理這個哨兵節(jié)點的健康狀態(tài)呢?
          我靈機(jī)一動,部署多個哨兵節(jié)點,成為哨兵集群!只要有一個節(jié)點活著就行,這樣同時都掛掉的概率就非常小了。
          當(dāng)然,有三個哨兵時,每個哨兵就不能太自我了,得聽從組織統(tǒng)一安排。

          主客觀問題

          比如說,當(dāng)哨兵 1 認(rèn)為主節(jié)點已經(jīng)掛掉時,不能認(rèn)為主節(jié)點就真的掛掉了,這種判斷叫做主觀下線
          哨兵 1 主觀認(rèn)為主節(jié)點下線時,需要詢問其他節(jié)點,主節(jié)點是否已經(jīng)下線。
          如果其中哨兵 2 回復(fù),主節(jié)點下線了,哨兵 3 回復(fù),主節(jié)點沒下線。
          那么這個時候,哨兵集群中,一共有 2 個哨兵都主觀認(rèn)為主節(jié)點下線。
          當(dāng)主觀下線的數(shù)量達(dá)到一定值時,比如說 >=2 時,我們就可以認(rèn)為,主節(jié)點客觀下線
          一旦主節(jié)點客觀下線了,那就又可以走之前的故障處理流程,即選擇一個從節(jié)點變成主節(jié)點。

          領(lǐng)頭問題

          接下來,將從節(jié)點變成主節(jié)點,也就是后續(xù)的這個故障處理流程,由哪個哨兵來完成呢?
          總不能同時來操作吧。
          那就必然需要選舉出一個領(lǐng)頭來完成這個事。
          怎么選舉出一個領(lǐng)頭呢?我總不能再用一個哨兵去做吧,那樣就無限套娃了,最好的方式就是讓他們仨自發(fā)地決定。
          這部分有點復(fù)雜,在這里展開不太合適,可以單獨水一篇文章來講解,感興趣的同學(xué)可以看一下 Raft 算法,哨兵集群正是通過這個算法來選舉領(lǐng)頭的。
          OK,我終于再次解放了我的雙手!
          我把這個破玩意,稱為哨兵系統(tǒng),或者哨兵集群!
          我再給哨兵起個英文名字,叫 Sentinel 吧!


          后記





          本次選取的 redis 代碼為 redis-3.0.0。

          之所以能夠通過"我"這個視角來寫哨兵,正是因為哨兵這個程序,完全可以由人不斷輸入 redis 命令來輕松完成,并不需要什么其他協(xié)議的支持。

          比如判斷節(jié)點健康狀態(tài)的 ping,拿到節(jié)點信息的 info,設(shè)置主從節(jié)點的 slaveof,甚至詢問其他哨兵節(jié)點是否在線的命令 sentinel is-master-down-by addr 等等,都是 redis 支持的客戶端命令,對用戶端非常友好。

          redis 的源代碼也是非常干凈,而且設(shè)計得很精妙,建議有興趣的讀者可以深入源碼進(jìn)行閱讀,不算難。

          比如上面講的,如何從一堆從節(jié)點中,選取一個作為主節(jié)點。

          這個知識點網(wǎng)上搜,你會搜到很多云里霧里的解釋,而如果你看源碼,你會發(fā)現(xiàn)這個過程非常清晰。

          sentinelRedisInstance *sentinelSelectSlave() {
              ...
              // 去掉一些節(jié)點
              while((de = dictNext(di)) != NULL) {
                  ...
                  if (slave->flags & (DOWN||DISCONNECTED)) continue;
                  if (mstime() - slave->last_avail_time > 5000continue;
                  if (slave->slave_priority == 0continue;
                  if (...) continue;
                  ...
              }
              // 剩下的節(jié)點排個序
              qsort(..., compareSlavesForPromotion);
              // 取第一個
              return instance[0];
          }


          // 怎么排序呢?就這么排
          int compareSlavesForPromotion(const void *a, const void *b) {
              // 先按優(yōu)先級排
              if ((*sa)->slave_priority != (*sb)->slave_priority)
                  return (*sa)->slave_priority - (*sb)->slave_priority;
              // 優(yōu)先級一樣按偏移量排
              if ((*sa)->slave_repl_offset > (*sb)->slave_repl_offset) {
                  return -1;
              } else if ((*sa)->slave_repl_offset < (*sb)->slave_repl_offset) {
                  return 1;
              }
              ...
              // 偏移量一樣按唯一標(biāo)識排
              return strcasecmp(sa_runid, sb_runid);
          }

          我想相信如果你停下來仔細(xì)看幾秒,哪怕你對 c 語言并不熟悉,也能看懂個大概了,再結(jié)合網(wǎng)上或者書上關(guān)于這塊的描述,你就有了很直觀的印象。

          關(guān)于 redis 源碼的深入學(xué)習(xí),我建議先閱讀黃健宏的《Redis 設(shè)計與實現(xiàn)》,這本書代碼量很少,但邏輯描述完全按照寫代碼的思維來講,你讀一下就知道了。

          讀完這本書,直接上手 redis 源碼的閱讀,你可以選擇 redis-1.0.0 代碼,非常少,主要閱讀其整個網(wǎng)絡(luò) IO 以及命令處理的流程。

          接著,從 redis-3.0.0 開始,有針對性研究其主從、集群、哨兵等特性。

          這樣,redis 在你這,就不再是模模糊糊了。

          戰(zhàn)略上藐視技術(shù),戰(zhàn)術(shù)上重視技術(shù)
          瀏覽 51
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青操视频网 | 在线观看二区 | 亚洲午夜影院在线 | 黄色高清网站 | 白领诱惑免费福利 |