構建可擴充套件的有態服務

banq發表於2015-10-18
很長一段時間,無態服務被看成通向伸縮擴充套件的必由之路,無態架構容易水平擴充套件,只需要一個round-robin的負載平衡就可以在無態服務之間分發請求。

但是,無態服務也有一些問題,由於狀態都儲存在資料庫中,每次請求都要進入資料庫增加了延遲,當然可以增加一層快取,但是快取會帶來資料一致性等問題的新的複雜性。

那麼什麼是有態服務呢?有態服務是將資料運送給函式(shipping data to function),而無態服務是透過函式訪問資料庫資料,是將函式送給資料( shipping functions to data);這兩種服務哪種更好呢?目前來看,好像有關有態服務的資料不是很多。

Caitie McCaffrey在 Strange Loop演講中創新地進行了有態服務方面的探索。(PPT

他有以下觀點:
(1)無態服務是浪費的。
無態服務確實工作得很好,將資料放在資料庫中,透過增加新的無態服務例項實現水平擴充套件伸縮。

問題是,應用總是有狀態的,如果一個資料庫達到訪問極限怎麼辦?我們需要sharding切分關聯式資料庫或者使用NoSQL,這些都會放棄強一致性,從而導致資料庫部分的抽象洩漏到服務中。

Data Shipping資料運送正規化:客戶端發出一個服務請求,服務與資料庫互動,資料庫返回一些資料,服務再做一些計算,響應回覆給客戶端,然後資料就從服務中消失。下一個請求透過負載平衡球分發到不同的機器上,上面整個過程就重新再來一遍。

你已經注意到同一個客戶端在透過負載平衡器後到不同的服務後做同樣的事情,這是浪費的,同一個客戶端其實有一個session會話時間,我們為什麼不利用會話儲存上次請求的狀態。

(2)有態服務更容易程式設計。
資料本地化:伺服器儲存著同一客戶端的上次請求的狀態,這樣好處是,後面再次同樣請求服務時,可以不必再訪問資料庫再次進行計算,從而降低延時,這對於資料集中性應用很有用處,客戶端只需要一次性操作一系列大量資料,然後將結果儲存在服務中,下次再次請求將立即快速返回響應。

函式運送正規化:客戶端發出服務請求,啟動一個會話,第一次資料庫還是需要訪問,獲得資料,資料儲存在服務中(其實類似快取,不過快取的生命週期不是application級別,而是session會話級別,也就是這個快取跟隨客戶端的會話建立和銷燬)。一旦被處理好的資料儲存在服務中,下次同樣客戶端發出請求到同樣伺服器的這個服務時,它就會操作在記憶體中的上次請求處理好的資料。避免了到資料庫的額外開銷,降低了延遲,即使資料庫當機,請求也能被服務處理,因為服務不必再訪問資料庫了。

有態服務會導致更高的可用性和更強的一致性。在CAP理論中,當我們確定分割槽以後,我們需要在可用性和不同級別的一致性之間選擇,CP是選擇一致性超過可用性,而AP是選擇可用性超過一致性。

如果你必須需要有更高的可用性,你選擇AP,但是你必須有讀寫操作(read-your-write),單調的純讀或單獨的純寫。那麼有態服務就能幫助你實現。

如果你實現粘性sticky連線,也就是說,對於同一個客戶端,每次請求連線都會分發到同樣一個伺服器中,因為那個伺服器中儲存著這個客戶端的會話狀態,那麼你就能實現讀寫操作(read-your-write)的高一致性,透過管道化的隨機訪問記憶體。

在論文Werner Vogel 2007中,讀寫操作(read-your-write) 會話和單調monotonic一致效能夠透過客戶端粘到同一個伺服器的方式實現,在一個分散式環境中,如果同一客戶端訪問的總是相同伺服器,就能確保讀寫操作(read-your-write)和單調讀,這對於負載平衡和失敗恢復管理來說不是非常難,但是是一種簡單的解決方案,使用粘性會話stick session,顯式化提供一種符合客戶端邏輯的公開方式。

粘性連線給予客戶端一個更容易的可質疑方式,與其擔心資料會從資料庫被拉進許多不同伺服器從而導致的資料併發性,你讓每個客戶端只和相同的一個伺服器互動,會很容易幫助你思考業務實現,特別是在一個分散式的程式設計環境中時。

那麼問題來了,將某個客戶端與同一伺服器始終粘在一起,會導致某個伺服器負載特別大,還有如果這個伺服器負載特別大怎麼辦?因此提出了Cluster Membership解決方案,一個客戶端可以和一個叢集中任何一個伺服器互動,因為一個叢集中這些伺服器中狀態都是相同的,有三種Cluster Membership型別:Static靜態, Gossip協議, Consensus一致系統.

Static靜態是最簡單的方式,看上去最蠢也許正好符合你需要,一個配置檔案包含一個叢集中節點伺服器的所有IP地址,這個配置檔案分發到每個伺服器上,當然問題不能實時知道哪個伺服器正好空閒,也會導致某個伺服器負載過大,而且不能失敗恢復,如果伺服器失敗,名單中必須被替代,配置檔案必須被更新分發。難以擴充套件叢集本身規模,在一個叢集中增加新機器,整個機器所有伺服器必須重啟。

動態Cluster Membership:節點可以動態增加刪除,採取兩種方式來處理叢集中伺服器狀態複製:gossip protocol 和1 consensus system

gossip協議:帶來超強可用性,透過傳送訊息將狀態在叢集中擴散,訊息是有關他們和誰互動,誰還活著,哪個伺服器已經當機了,每個機器以自己的意願收集資料,有自己關於叢集的世界觀。在一個穩定的狀態中,叢集中所有機器會最終匯聚converge 到一個相同的認識觀,這個認識觀就是:大家最終會都知道哪個伺服器真的死了,哪個還活著。當網路出現錯誤,或者有新的伺服器增減,叢集中的不同伺服器就會有不同的有關叢集整體的世界觀了。有一點需要平衡,因為沒有了必需的協調工作,所以你獲得了高可用性,每個機器都能給予自己的世界觀做決定,但是你的程式碼得能夠處理在失敗問題出現時需要路由到不同節點的這種不確定性情況。

Consensus一致系統:更強調一致性,叢集中所有節點都有一致的世界觀,一致性系統控制著叢集中每個成員,當配置檔案改變,所有的節點都會基於一致性系統更新它們的世界觀,因為一致性系統如同專權者一樣牢牢把握著整個叢集的真實情況。

問題是:如果一致性系統自己不可用了,那麼所有節點也就狗屁了,因為它們誰都不相信誰。誰也不認識彼此。

還有問題:因為一致性系統屬於一種協調角色,協調工作加入分散式系統必然導致效能下降。

在這種情況下,儘量避免在一致性系統下尋求高可用性。

上面談了叢集內部狀態的複製問題,剩下問題是,如何將工作跨叢集分發,有三種型別:Random Placement, Consistent Hashing(一致性雜湊), Distributed Hash Tables(分散式雜湊表).

Random Placement:沉默意味著效率,當基於大量資料進行查詢時,需要許多資料基於叢集分發。適合採取這種方式,

一致性雜湊:這是一種確定性的Placement,也就是將客戶端請求基於一致性雜湊這種確定性方案傳送到某個伺服器,雜湊也許基於會話ID或使用者ID,依賴工作負載是如何被分割槽的。也就是說,如果你依據使用者ID的大小範圍對伺服器進行分割槽,那麼採取基於使用者ID的雜湊方式。類似Cassandra 採取的也是這種方式。這種方式的問題是hotspots,許多請求會被雜湊分發到相同節點,某個節點伺服器會過載變慢,一致性雜湊並不允許工作任務從熱點區域移除,這樣你必須為你的叢集準備足夠的多餘的空間,以防止它成為熱點區域,因為一旦成為熱點就無法動態移除不斷加入的工作任務了。


分散式雜湊(DHT):這是一種非確定性的Placement,在以惡搞分散式雜湊表中雜湊被用來定位請求工作傳送到哪個伺服器,DHT保有叢集中所有節點的指向,因為沒有強迫工作任務分發到哪個指定伺服器,因此是不確定的分發方案,如果某個節點不可用或變成熱點,可以容易重新分發客戶端請求工作到其他伺服器。

最後,該演講談了當前現實中三大系統採取了有態服務架構,這三個系統都是鼎鼎大名:Facebook的Scuba;Uber的Ringpop和微軟的Orlean。

有興趣者可見下文了解這三大系統如何在上述有態服務架構原理中進行平衡實踐的。

Making the Case for Building Scalable Stateful Ser



[該貼被banq於2015-10-18 16:04修改過]

相關文章