關於心跳、故障監測、lease機制

原文鏈接:https://www.cnblogs.com/xybaby/p/8710421.html

目錄

 

正文

   

  電話之於短信、微信的一個很大的不同點在於,前者更加及時,有更快速直接的反饋;而後面兩個雖然稱之爲instant message,但經常時發出去了就得等對方回覆,等多久是不確定的。打電話能明確知道對方在不在,我所表達的信息是否已經傳達;而短信或者微信,只知道消息發出去了,但對方是否收到,或者是否查看就不清楚了。

  在通過網絡通信的環境下,也是很難知道一個消息對方是否已經處理,因爲要知道對方是否處理,依賴於對方的回覆(ack),但即使對方沒有回覆,也不能說明對方就沒有處理,也許僅僅是對方回覆的那條消息丟失了

  很多時候,一個進程需要判斷另外一個進程是否還在工作,如何判斷呢?判斷是否準確呢,能否保證一致性呢?本文嘗試回答這些問題。

  本文中,節點通常就是指一個進程,一個提供服務的進程。後文中,只要不加以強調,進程和節點是同一個意思。

  本文地址:http://www.cnblogs.com/xybaby/p/8710421.html

進程的狀態

回到頂部

  一個進程是否crash(在本文中,只要進程是非預期的終止,就成爲crash),在單機環境下是很好判斷的,只要查看還有沒有這個進程就行了。這裏有兩個問題:

  第一:分佈式環境下能否準確判斷進程crash?

  第二:是否需要明確一個進程是否crash?

  對於第一個問題,答案是幾乎不能的,後面詳細分析。

  而第二個問題,有的時候是無需明確一個進程是否已經crash,而需要明確的是該進程是否持續對外提供服務,即使沒有crash,如果不按程序的預期工作了(比如進程死循環、死鎖、斷網),那麼也可以說這個服務掛了,“有的人活着,他已經死了”。分佈式環境中,我們關心的是,節點(進程)是否對外提供服務,真死(crash)假死(活着但不工作)都是死(從系統的角度看)。

 

  我們稱一個對外提供服務的進程處於active狀態,否則處於none-active狀態。

Failure detection

回到頂部

  如何判斷一個進程是否處於active狀態,最簡單明瞭的方式就是:每隔一段時間就和這個進程通通信,如果目標進程在一定的時間閾值內回覆,那麼我們就說這個進程是active的,否則就是none-active的。這種定時通信的策略我們統稱爲心跳。

  心跳有兩種方式:第一種是單向的heartbeat;第二種是ping pong(ping ack)

  在後文中,被檢測的進程稱之爲target,而負責檢測的進程稱之爲detector

  第一種方式,target進程需要告知detector進程自己的存活性,只需要定時給detector發消息就行了,“hi, duddy, I am Ok!”。detector無需給target回覆任何消息,detector的任務是,每隔一定時間去檢查一下target是否有來彙報,沒有的話,detector就認爲target處於none-active狀態了

  

 

  而第二種方式,ping pong或者ping ack,更爲常見:

  detector給target發消息:hi,man,are you ok?

  target回覆detector:Yes, I am ok!

  然後detector、target定時重複上面兩步

  

 

  detector負責發起檢測:如果target連續N次不回覆消息,那麼detector就可以認爲target處於none-active狀態了。

  這兩種方式,區別在於誰來主動發消息,而相同點在於:誰關心active狀態,誰來檢測。就是說,不管是簡單的heartbeat,還是ping ack,都是detector來檢測target的狀態。在實際中,ping ack的方式會應用得多一些,因爲在ack消息中也可以攜帶一些信息,比如後文會提到的lease。

gunicorn failure detection

  gunicorn是一個遵循python wsgi的http server,使用了prefork master-worker模型(在gunicorn中,master被稱爲Arbiter),能夠與各種wsgi web框架協作。在本文,關注的是Arbiter進程對Worker進程狀態的檢測。

  既然Worker進程是Arbiter fork出來的,即Arbiter是Worker進程的父進程,那麼當Worker進程退出的時候,Master是可以通過監聽signal.SIGCHLD信號來知道子進程的結束。但這還不夠,gunicorn還有另外一招,我在《gunicorn syncworker 源碼解析》中也有所提及:

  (1)gunicorn爲每一個Worker進程創建一個臨時文件

  (2)worker進程在每次輪訓的時候修改該臨時文件的屬性

  (3)Arbiter進程檢查臨時文件最新一次修改時間是否超過閾值,如果超過,那麼就給Worker發信號,kill掉該Worker

  不難看出,這是上面提到的第一種檢測方式(單向的heartbeat),worker進程(target)通過修改文件屬性的方式表明自己處於active狀態,Arbiter(detector)進程檢測文件屬性來判斷worker進程最近是否是active狀態。

  這個檢測的目的就是防止worker進程處於假死狀態,沒有crash,但是不工作。

tcp keepalive

  在計算機網絡中,心跳也使用非常廣泛。比如爲了檢測目標IP是否可達的ping命令、TCP的keepalive。

A keepalive (KA) is a message sent by one device to another to check that the link between the two is operating, or to prevent the link from being broken.  

   在TCP協議中,是自帶keepalive來保活的,有三個很重要的選項(option)

tcp_keepidle :對一個連接進行有效性探測之前運行的最大非活躍時間間隔
tcp_keepintvl :兩個探測的時間間隔
tcp_keepcnt :關閉一個非活躍連接之前進行探測的最大次數

  三個選項是這麼配合的:如果一條連接上tcp_keepidle 這麼長時間沒有發消息之後,則開始心跳檢測,心跳檢測的時間間隔是tcp_keepintvl ,如果連續探測tcp_keepcnt 這麼多次,還沒有收到對應的迴應,那麼主動關閉連接。

  這三個參數的默認值都比較大,就是說需要較長時間才能檢測網絡不可達,因此一般情況下,程序會將三個參數調小。

  可以看到,這是典型的ping ack探測方式。

應用層心跳

  如果我們使用TCP最爲傳輸層協議,那麼是否就可以依賴TCP的keepalive來檢查連接的狀態,或者說對端進程的active狀態呢?

  答案是不能的,因爲,TCP只能保證說鏈接是通的,但並不能表明對端是可用的,比如說對端進程處於死鎖狀態,但鏈接仍然是通的,tcp keepalive會持續收到迴應,因此傳輸層的心跳無法發現進程的不可用狀態。所以我們需要應用層的心跳,如果收到應用層的心跳回復,那麼對端肯定不會是none-active的。

Failure detector與consensus

回到頂部

  前面提到,通過心跳,是無法準確判斷target是否是crash,但有的情況下我們又需要明確知道對方是crash了?還是說只是網絡不通?畢竟這是兩個不同的狀態。

  而且target的crash還分爲crash-stop,crash-recovery兩種情況。crash-stop是說一個進程如果crash了,那麼不會重新啓動,也就不會接受任何後續請求;而crash-recovery是說,進程crash之後會重啓(是否在同一個物理機上重啓沒有任何關係)。

completeness vs accuracy

  如果目標是檢測出target的crash狀態,那麼檢測算法有兩個重要的衡量標準:

Completeness: every process failure is eventually detected (no misses)

Accuracy: every detected failure corresponds to a crashed process (no mistakes)

  前者(completeness)是說,如果一個進程掛掉,那麼一定能被檢測到;後者(accuracy)是說,如果detector認爲target進程掛掉了,那麼就一定掛掉了,不會出現誤判。

  對於crash-stop模型,只能保證completenss,不能保證accurary。completeness不難理解,而accuracy不能保證是因爲網絡通信中, 由於延時、丟包、網絡分割的存在,導致心跳消息(無論是單向的heartbeat還是ping ack)無法到達,也就沒法判斷target是否已經crash,也許還活得好好的,只是與detector之間的網絡出了問題。

  那如果是crash-recovery模型呢,通過簡單的心跳,不僅不能保證accuracy,甚至不能保證completeness,因爲target進程可能快速重啓,當然增加一些進程相關的信息還是能判斷crash-recovery的情況,比如target進程維護一個版本號,心跳需要對比版本號。

  總之,網絡環境下,很難保證故障檢測的準確性(accuracy)。

lease

  接下來,考慮一個很常見的場景,在系統中有兩類進程:一個master和多個slave,master給slave分配任務,master需要通過心跳知道每個slave的狀態,如果某個slave處於none-active狀態,那麼需要將該slave負責的任務轉移到其他處於active狀態的slave上。master也充當了detector的角色,每個slave都是被檢測的target。

  在這個場景中,有兩點需要注意,第一,使用了心跳作爲探測方式,而心跳探測只能保證completeness(完整性),而不能保證accuracy(準確性)。第二,slave負責的任務是否是可重複的?即同一份任務是否可以同時在多個slave上進行。failure detection的不準確性對slave任務的重新分配有至關重要的影響。

  如果任務是可重複的,比如冪等的運算,那麼當master通過心跳判斷slave處於none-active狀態的時候,只需要將任務重新分配給其他slave就行,不用管該slave是否還存活着。MapReduce就是這樣的例子,master在worker(MapReduce中的slave進程)進程失活(甚至只是運算進度太慢)的時候,將該worker負責的任務(task)調度到其他worker上。因此可能出現多個worker負責同一份任務的情況,但這並不會產生什麼錯誤,因爲任務的計算結果需要回報給master,master保證對於一份任務只會採納一份計算結果,而且同一份任務,不管是哪個worker執行,結果都是一致的。

  但如果任務只能由一個節點來執行呢,由於心跳檢測的不準確性,那麼將任務從本來還在工作的節點重新調度到其他節點時,就會出現嚴重的問題。比如在primary-secondary副本控制協議中,primary的角色只能由一個節點來扮演,如果同時有兩個primary,那麼就出現了腦裂(brain-split),從這名字就能聽出來問題的嚴重性。因此,即使在心跳檢測出現誤判的情況下,也要保證任務的唯一性,或者說,需要detector與target之間達成共識:target不要對外提供服務了。這個時候Lease機制就是很不錯的選擇,因爲Lease機制具有很好的容錯性,不會受到網絡故障、延遲的影響。

  關於lease機制,我在《帶着問題學習分佈式系統之數據分片》中有簡單介紹,這裏再簡單提一下Lease在GFS中的使用。

  GFS採用了primary-seconday副本控制協議,primary決定了數據修改的順序。而primary的選舉、維護是由master節點負責的,master與primary的心跳中,會攜帶lease信息。這個lease信息是master對primary的承諾:在lease_term這個時間範圍內,我不會選舉出新的primary。那麼對於primary,在這個時間內,執行primary的職責,一旦過了這個時間,就自動失去了primary的權利。如果primary節點本身是ok的,並且與master之間網絡正常,那麼在每次心跳的時候,會延長這個lease_term,primary節點持續對外服務。一旦超過lease_term約定的時間,master就會選出新的primary節點,而舊的primary節點如果沒有crash,在恢復與master的心跳之後,會意識到已經有了新的primary,然後將自己降級爲secondary。

關於detector

  從上面GFS的例子,可以看到master是一個單點,也就是說由一個節點來負責所以其它節點的failure detection,這就是集中式的故障檢測。如下圖所示:

   由於detector是單點,因此壓力會比較大。更爲嚴重的問題,在使用了lease機制的系統中,一旦detector故障,所以節點都無法獲取lease,也就無法提供服務,整個系統完全不可用。

  因此,detector的高性能、高可用非常重要,以一個集羣的形式提供failure detection功能。

references

回到頂部

buffalo cse486 failure_detectors.pdf

gunicorn.org

Failure_detector

Leases: An Efficient Fault-Tolerant Mechanism for Distributed File Cache Consistency 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章