Hey,man,are you ok? -- 關於心跳、故障監測、lease機制

   

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

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

  不少時候,一個進程須要判斷另一個進程是否還在工做,如何判斷呢?判斷是否準確呢,可否保證一致性呢?本文嘗試回答這些問題。web

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

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

進程的狀態

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

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

  第二:是否須要明確一個進程是否crash?框架

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

  而第二個問題,有的時候是無需明確一個進程是否已經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 

相關文章
相關標籤/搜索