分佈式系統關注點(12)——「無狀態」詳解

若是這是第二次看到個人文章,歡迎右側掃碼訂閱我喲~  👉html

本文長度爲2728字,建議閱讀8分鐘。前端

堅持原創,每一篇都是用心之做~算法

 

 

前面聊完的2個章節「數據一致性」和「高可用」其實本質是一個經過提高複雜度讓總體更完善的方式。數據庫

 

接下去咱們開始聊一些讓系統更簡單,更容易維護的東西——「易伸縮」,首當其衝的第一篇文章就是「stateless」,也叫「無狀態」。緩存

 

z哥帶你先來認識一下「狀態」是什麼。服務器

 

 

1、初識「狀態」

以前在「負載均衡」的第四篇(分佈式系統關注點——作了「負載均衡」就能夠隨便加機器了嗎?)中提到過一個例子,咱們再翻出來一下。微信

 

開發Z哥對運維Y弟喊:「Y弟,如今系統好卡,剛上了一波活動,趕忙幫我加幾臺機器上去頂一下。」網絡

 

Y弟回覆說:「沒問題,分分鐘搞定」。數據結構

 

而後就發現數據庫的壓力迅速上升,DBA就吼了:「Z哥,你丫的搞什麼呢?數據庫要被你弄垮了」。多線程

 

而後客服那邊接框也爆炸了,愈來愈多的用戶說剛登錄後沒多久,操做着就退出了,接着登錄,又退出了,到底還作不作生意了。

 

這個案例中的問題,產生的根本緣由是由於系統中存在着大量「有狀態」的業務處理過程。

 

 

2、「有狀態」和「無狀態」

 N.Wirth曾經在它1984年出版的書中將程序的定義經典的歸納爲:程序=數據結構+算法。(這個歸納也是這本書的書名)

 

這是一個頗有意思的啓發,受它的影響,z哥認爲程序作的事情本質就是「數據的移動和組合」,以此來達到咱們所指望的結果。而如何移動、如何組合是由「算法」來定的,因此z哥延伸出一個新的定義:數據+算法=成果

 

經過程序處理所獲得的「成果」其實和你平時生活中完成的任何事情所獲得的「成果」是同樣的。任何一個「成果」都是你經過一系列的「行動」將最開始的「原料」進行加工、轉化,最終獲得你所指望的「成果」。

 

 

好比,你將常溫的水,經過「倒入水壺」、「通電加熱」等工做後變成了100度的水,就是這樣一個過程。

 

正如燒水的例子,大多數時候獲得一個「成果」每每須要好幾道「行動」才能完成。

這個時候若是想下降這幾道「行動」總的成本(如:時間)該怎麼辦呢?

天然就是提煉出反覆要作的事情,讓其只作一次。而這個事情在程序中,就是將一部分「數據」放到一個「暫存區」(通常就是本地內存),以提供給相關的「行動」共用。 

可是如此一來,就致使了須要增長一道關係,以表示每個「行動」與哪個「暫存區」關聯。由於在程序裏,「行動」多是「多線程」的。

 

這時,這個「行動」就變成「有狀態」的了。

 

題外話:共用同一個「暫存區」的多個「行動」所處的環境常常被稱做「上下文」。

 

 

咱們再來深刻聊聊「有狀態」。

 

「暫存區」裏存的是「數據」,因此能夠理解爲「有數據」就等價於「有狀態」。

 

「數據」在程序中的做用範圍分爲「局部」和「全局」(對應局部變量和全局變量),所以「狀態」其實也能夠分爲兩種,一種是局部的「會話狀態」,一種是全局的「資源狀態」

 

題外話:由於有些服務端不僅僅負責運算,還會提供其自身範圍內的「數據」出去,這些「數據」屬於服務端完整的一部分,被稱做「資源」。因此,理論上「資源」能夠被每一個「會話」來使用,所以是全局的狀態。

 

本文聊的「有狀態」都指的是「會話狀態」。

 

 

與「有狀態」相反的是「無狀態」,「無狀態」意味着每次「加工」的所需的「原料」所有由外界提供,服務端內部不作任何的「暫存區」。而且請求能夠提交到服務端的任意副本節點上,處理結果都是徹底同樣的

 

有一類方法天生是「無狀態」,就是負責表達移動和組合的「算法」。由於它的本質就是:

  1. 接收「原料」(入參)

  2. 「加工」並返回「成果」(出參)

 

 

爲何網上主流的觀點都在說要將方法多作成「無狀態」的呢?

 

由於咱們更習慣於編寫「有狀態」的代碼,可是「有狀態」不利於系統的易伸縮性和可維護性。

 

在分佈式系統中,「有狀態」意味着一個用戶的請求必須被提交到保存有其相關狀態信息的服務器上,不然這些請求可能沒法被理解,致使服務器端沒法對用戶請求進行自由調度(例如雙11的時候臨時加再多的機器都沒用)。

 

同時也致使了容錯性很差,假若保有用戶信息的服務器宕機,那麼該用戶最近的全部交互操做將沒法被透明地移送至備用服務器上,除非該服務器時刻與主服務器同步所有用戶的狀態信息。

 

這兩個問題在負載均衡的第四篇(分佈式系統關注點——作了「負載均衡」就能夠隨便加機器了嗎?)中也有提到。

 

 

可是若是想得到更好的伸縮性,就須要儘可能將「有狀態」的處理機制改形成「無狀態」的處理機制。

 

 

3、「無狀態」化處理

將「有狀態」的處理過程改形成「無狀態」的,思路比較簡單,內容很少。

 

首先,狀態信息前置,豐富入參,將處理須要的數據儘量都經過上游的客戶端放到入參中傳過來。

固然,這個方案的弊端也很明顯:網絡數據包的大小會更大一些

 

 

另外,客戶端與服務端的交互中若是涉及到屢次交互,則須要來回傳遞後續服務端處理中所需的數據,以免須要在服務端暫存。

▲橙色請求,綠色響應

 

這些改造的目的都是爲了儘可能少出現相似下面的代碼。

 

func(){

    return i++;

}

 

而是變成:

 

func(i){

    return i+1;

}

 

 

要更好的作好這個「無狀態」化的工做,依賴於你在架構設計或者項目設計中的合理分層。

 

儘可能將會話狀態相關的處理上浮到最前面的層,由於只有最前面的層才與系統使用者接觸,如此一來,其它的下層就能夠將「無狀態」做爲一個廣泛性的標準去作。

 

與此同時,因爲會話狀態集中在最前面的層,因此哪怕真的狀態丟失了,重建狀態的成本相對也小不少。

 

好比三層架構的話,保證BLL和DAL都不要有狀態,代碼的可維護性大大提升。

 

若是是分佈式系統的話,保證那些被服務化的程序都不要有狀態。除了能提升可維護性,也大大有利於作灰度發佈、A/B測試。

 

題外話:在這裏,提到作分層的目的是爲了說明,只有將IO密集型程序和CPU密集型程序分離,纔是通往「無狀態」真正的出路。一旦分離後,CPU密集型的程序天然就是「無狀態」了。

 

如此也能更好的作「彈性擴容」。由於常見的須要「彈性擴容」的場景通常指的就是CPU負荷過大的時候。

 

 

最後,若是前面的都不合適,能夠將共享存儲做爲降級預案來運用,如遠程緩存、數據庫等。而後當狀態丟失的時候能夠從這些共享存儲中恢復。

 

因此,最理想的狀態存放點。要麼在最前端,要麼在最底層的存儲層。

 

 

4、總結

任何事物都是有兩面性的,正如前面提到的,咱們並非要全部的業務處理都改形成「無狀態」,而只是挑其中的一部分。最終仍是看「價值」,看「性價比」。

 

好比,將一個以「狀態」爲核心的即時聊天工具的全部處理過程都改形成「無狀態」的,就有點得不償失了。

 

 


 

相關文章:

 


 

做者:Zachary

出處:https://zacharyfan.com/archives/556.html

 

若是你喜歡這篇文章,能夠點一下右下角的「推薦」。

這樣能夠給我一點反饋。: )

謝謝你的舉手之勞。

 

▶關於做者:張帆(Zachary,我的微信號:Zachary-ZF)。堅持用心打磨每一篇高質量原創。歡迎掃描右側的二維碼~。

按期發表原創內容:架構設計丨分佈式系統丨產品丨運營丨一些思考。

相關文章
相關標籤/搜索