昨晚十點下班,回家花了1個小時寫了一篇《一分鐘實現分佈式鎖》,引發讀者一些反響,有些朋友反饋「setnx算什麼方案」,「沒有考慮超時」,「爲啥不用zookeeper」,有甚者上升到 「質疑58同城的技術水平」,「拉低了架構師的層次」,「適合小學生閱讀」。 redis
給58帶來負面的影響實在對不起公司,也抱歉耽誤部分同窗1分鐘時間(還好是1分鐘系列),不過大部分讀者的反饋是正向的,只生氣了5分鐘。 數據庫
技術領域,我以爲瞭解前因後果,瞭解本質原理,比用什麼工具實現更重要: 緩存
(1)進程多線程如何互斥? 服務器
(2)一個手機上兩個APP訪問一個文件如何互斥? session
(3)分佈式環境下多個服務訪問一個資源如何互斥? 多線程
歸根結底,是利用一個互斥方可以訪問的公共資源來實現分佈式鎖,具體這個公共資源是redis來setnx,仍是zookeeper,相反沒有這麼重要。 架構
言歸正傳,今天把昨天文章的緣起講一講,並經過Google Chubby的論文閱讀筆記聊一聊分佈式鎖。 併發
1、需求緣起 異步
58到家APP新上線了導入通信錄好友功能,測試的同窗發現,連續點擊導入會導入重複數據: 分佈式
客戶端同一個用戶同時發出了多個請求,分佈式環境下,多臺機器上部署的多個service進行了併發操做,故插入了冗餘數據。
解決思路:同一個用戶同時只能有一個導入請求,須要作互斥,最簡易的方案,使用setnx快速解決。
(1)同一個用戶,多個service進行併發操做,service須要先去搶鎖
(2)搶到鎖的service,纔去數據庫操做
具體這個鎖用setnx,仍是zookeeper都不過重要,利用一個互斥方可以訪問的公共資源來實現分佈式鎖,這纔是《一分鐘實現分佈式鎖》的重點。
2、Google Chubby分佈式鎖閱讀筆記
上一篇文章的評論中,有些朋友提到了zookeeper,會使用不夠,藉着Google Chubby瞭解下分佈式鎖的實現也是有必要的。
早年Google的四大基礎設施,分別是GFS、MapReduce、BigTable、Chubby,其中Chubby用於提供分佈式的鎖服務。
1.簡介
Chubby系統提供粗粒度的分佈式鎖服務,Chubby的使用者不須要關注複雜的同步協議,而是經過已經封裝好的客戶端直接調用Chubby的鎖服務,就能夠保證數據操做的一致性。
Chubby具備普遍的應用場景,例如:
(1)GFS選主服務器;
(2)BigTable中的表鎖;
2.背景
Chubby本質上是一個分佈式文件系統,存儲大量小文件。每一個文件就表明一個鎖,而且能夠保存一些應用層面的小規模數據。用戶經過打開、關閉、讀取文件來獲取共享鎖或者獨佔鎖;並經過反向通知機制,向用戶發送更新信息。
3.系統設計
3.1設計目標
Chubby系統設計的目標基於如下幾點:
(1)粗粒度的鎖服務;
(2)高可用、高可靠;
(3)可直接存儲服務信息,而無需另建服務;
(4)高擴展性;
在實現時,使用瞭如下特性:
(1)緩存機制:客戶端緩存,避免頻繁訪問master;
(2)通知機制:服務器會及時通知客戶端服務變化;
3.2總體架構
Chubby架構並不複雜,如上圖分爲兩個重要組件:
(1)Chubby庫:客戶端經過調用Chubby庫,申請鎖服務,並獲取相關信息,同時經過租約保持與服務器的鏈接;
(2)Chubby服務器組:一個服務器組通常由五臺服務器組成(至少3臺),其中一臺master,服務維護與客戶端的全部通訊;其餘服務器不斷和主服務器通訊,獲取用戶操做。
4.系統實現
4.1文件系統
Chubby文件系統相似於簡單的unix文件系統,但它不支持文件移動操做與硬鏈接。文件系統由許多Node組成,每一個Node表明一個文件,或者一個目錄。文件系統使用Berkeley DB來保存每一個Node的數據。文件系統提供的API不多:建立文件系統、文件操做、目錄操做等簡易操做。
4.2基於ICE的Chubby通訊機制
一種基於ICE的RPC異步機制,核心就是異步,部分組件負責發送,部分組件負責接收。
4.3客戶端與master的通訊
(1)長鏈接保持鏈接,鏈接有效期內,客戶端句柄、鎖服務、緩存數據均一直有效;
(2)定時雙向keep alive;
(3)出錯回調是客戶端與服務器通訊的重點。
下面將說明正常、客戶端租約過時、主服務器租約過時、主服務器出錯等狀況。
(1)正常狀況
keep alive是週期性發送的一種消息,它有兩方面功能:延長租約有效期,攜帶事件信息告訴客戶端更新。正常狀況下,租約會由keep alive一直不斷延長。
潛在回調事件包括:文件內容修改、子節點增刪改、master出錯等。
(2)客戶端租約過時
客戶端沒有收到master的keep alive,租約隨之過時,將會進入一個「危險狀態」。因爲此時不能肯定master是否已經終止,客戶端必須主動讓cache失效,同時,進入一個尋找新的master的階段。
這個階段中,客戶端會輪詢Chubby Cell中非master的其餘服務器節點,當客戶端收到一個確定的答覆時,他會向新的master發送keep alive信息,告之本身處於「危險狀態」,並和新的master創建session,而後把cache中的handler發送給master刷新。
一段時間後,例如45s,新的session仍然不能創建,客戶端立馬認爲session失效,將其終止。固然這段時間內,不能更改cache信息,以求保證數據的一致性。
(3)master租約過時
master一段時間沒有收到客戶端的keep alive,則其進入一段等待期,此期間內仍沒有響應,則master認爲客戶端失效。失效後,master會把客戶端得到的鎖,機器打開的臨時文件清理掉,並通知各副本,以保持一致性。
(4)主服務器出錯
master出錯,須要內部進行從新選舉,各副本只響應客戶端的讀取命令,而忽略其餘命令。新上任的master會進行如下幾步操做:
a,選擇新的編號,再也不接受舊master的消息;
b,只處理master位置相關消息,不處理session相關消息;
c,等待處理「危險狀態」的客戶端keep alive;
d,響應客戶端的keep alive,創建新的session,同時拒絕其餘session相關操做;同事向客戶端返回keep alive,警告客戶端master fail-over,客戶端必須更新handle和lock;
e,等待客戶端的session確認keep alive,或者讓session過時;
f,再次響應客戶端全部操做;
g,一段時間後,檢查是否有臨時文件,以及是否存在一些lock沒有handle;若是臨時文件或者lock沒有對應的handle,則清除臨時文件,釋放lock,固然這些操做都須要保持數據的一致性。
4.4服務器間的一致性操做
這塊考慮的問題是:當master收到客戶端請求時(主要是寫),如何將操做同步,以保證數據的一致性。
(1)節點數目
通常來講,服務器節點數爲5,若是臨時有節點被拿走,可預期不久的未來就會加進來。
(2)關於複製
服務器接受客戶端請求時,master會將請求複製到全部成員,並在消息中添加最新被提交的請求序號。member收到這個請求後,獲取master處被提交的請求序號,而後執行這個序列以前的全部請求,並把其記錄到內存的日誌裏。若是請求沒有被master接受,就不能執行。
各member會向master發送消息,master收到>=3個以上的消息,纔可以進行確認,發送commit給各member,執行請求,並返回客戶端。
若是某個member出現暫時的故障,沒有收到部分消息也無礙,在收到來自master的新請求後,主動從master處得到已執行的,本身卻尚未完成的日誌,並進行執行。
最終,全部成員都會得到一致性的數據,而且,在系統正常工做狀態中,至少有3個服務器保持一致而且是最新的數據狀態。
4.5Chubby系統鎖機制
客戶端和服務器除了要保存lease對象外,服務器和客戶端還須要保存另外一張表,用於描述已經加鎖的文件及相關信息。因爲Chubby系統所使用鎖是建議性而非強制性的,這表明着若是有多個鎖請求,後達的請求會進入鎖等待隊列,直到鎖被釋放。
5.Chubby使用例子(重點)
5.1選master
(1)每一個server都試圖建立/打開同一個文件,並在該文件中記錄本身的服務信息,任什麼時候刻都只有一個服務器可以得到該文件的控制權;
(2)首先建立該文件的server成爲主,並寫入本身的信息;
(3)後續打開該文件的server成爲從,並讀取主的信息;
5.2進程監控
(1)各個進程都把本身的狀態寫入指定目錄下的臨時文件裏;
(2)監控進程經過閱讀該目錄下的文件信息來得到進程狀態;
(3)各個進程隨時有可能死亡,所以指定目錄的數據狀態會發生變化;
(4)經過事件機制通知監控進程,讀取相關內容,獲取最新狀態,達到監控目的;
6.總結
Google Chubby提供粗粒度鎖服務,它的本質是一個鬆耦合分佈式文件系統;開發者不須要關注複雜的同步協議,直接調用庫來取得鎖服務,並保證了數據的一致性。
最後要說明的是,最終Chubby系統代碼共13700多行,其中ice自動生成6400行,手動編寫約8000行,這就是Google牛逼的地方:強大的工程能力,快速穩定的實現,而後用來解決各類業務問題。
==【完】==
若是沒有時間讀完,看看文末「選master」和「進程監控」兩個例子,對理解「利用一個互斥方可以訪問的公共資源來實現分佈式鎖」應該有幫助。本篇只是讀書筆記,瞭解更多細節可查閱Google的論文。
【轉自】58沈劍 架構師之路