可擴展伸縮架構中的狀態

  • 2020 年 2 月 10 日
  • 筆記

提到狀態,我們總是伴隨着可變的、並發、隔離和作用域等詞語,精確定義如下:狀態是有關存儲信息的技術名詞,任何一個時間程序能夠立即訪問到。簡單地說,狀態是一種可能被行為操作改變的數據,是一種可變的純數據。

全局狀態類似於我們通常講的全局變量,為什麼我們需要全局變量?因為這個全局變量包含着全局狀態,可以全局共享,很顯然,如果所有程序都共用一個數據庫,那麼數據庫無疑是最常見的全局狀態。如果將全局狀態放在程序的全局變量中,那麼會使得我們的各個使用這個全局變量的程序部分會緊緊耦合在一起。

在面向對象編程中,一個對象可以看成是由一些數據組成的,包含一些訪問這些數據的操作方法。這些對象內部數據由於會被對象方法改變,這屬於對象內部的狀態,OOP強烈建議改變狀態的行為和狀態應該放在一起(放在一個對象中)。這樣才能保證,在前置條件和後置條件下使得代碼更容易測試。

使用繼承來共享代碼是一個壞主意,狀態的改變行為將位於不同的父子繼承文件中,即使他們最終是在運行時是單一對象,這也會影響代碼的可讀性。

函數式編程是通過避免可變狀態來解決這種複雜性,這種函數的輸出完全依賴其輸入,但是迴避可變狀態不只是簡單閉上眼睛,如同掩耳盜鈴,畢竟我們還要面對狀態,下面是Scala處理狀態代碼:

這是一個隨機數產生類,依賴於先前種子產生新的隨機數,OOP會將老的種子作為對象狀態,每次nextInt方法被調用時改變這個狀態,而FP函數編程則是封裝種子在結果元素中,這樣每件事都是不可變的,函數的結果是依賴其輸入,這稱為透明引用。

Akka是實現Actor模型的工具集,這個模型是混合了OOP和FP風格處理狀態,每個Actor管理自己的狀態,但是操作狀態的動作是按消息順序發生的,因此任何時刻不存在兩個行為同時改變狀態,從而避免了鎖。

在服務層中處理狀態的總結:

  • 隔離
  • 儘可能避免狀態
  • 狀態應該被指定軟件管理
  • 默認不可變
  • 狀態和行為要捆綁在一起

下面我們看看狀態如何在系統層的情況。

狀態是能夠瞬間訪問的數據,但是狀態生命周期?請求作用域?會話作用域?什麼時候能夠導入存儲到持久介質上?下面看看狀態在系統層面的幾個生命周期:

1. HTTP請求周期:在一個HTTP請求對象是持有一個有限狀態機,這個周期相當短,這樣我們只能讓狀態保留在內存中,大部分時間我們能通過失敗重試的方式簡化,而不是使用Akka持久層複雜技術。

2.會話周期:HTTP是一個無狀態協議,注意時間是我們狀態定義中的基礎,這就意味着HTTP並沒有內建機制跟蹤狀態,而會話則是針對同一個客戶端多個請求在服務器保有的狀態,但是會話狀態是無法擴展伸縮的,因為這導致有狀態服務,而無狀態服務可以根據負載平衡器分發請求到不同的無態服務,如果是有態服務,每次請求只能粘牢指定服務器,要麼將會話狀態在服務器之間複製,如果狀態比較多,複製會無故耗費服務器的處理性能。

3.流周期:這是接近實時分析,狀態存在一個時間窗口,用來進行分析比如計算平均值或計數或最大值。

4.持久周期:一些數據會比創建它的代碼壽命長,需要保存到磁盤。

數據庫作為狀態單一來源

我們認為儘可能避免狀態是一個好設計,無狀態服務雖然好,但不代表不會操作數據,不會和有狀態數據打交道,無態服務可以將狀態委託給數據存儲,或使用Servlerless架構,這不代表沒有服務器,但是你將狀態委託給專家專業平台處理。

委託我們的持久狀態到數據庫是一個好主意,當負載增加以後,系統會開始變得緩慢,我們這時會使用緩存,同樣,如果我們需要對數據庫進行全文本搜索,數據庫可能就不會很擅長,這樣我們會針對不同的查詢進行優化,同時要保持這些不同狀態查詢視圖的同步。

當多個應用同時修改同一個數據存儲時,會有各種情況:

1.競爭情況:如果兩個客戶端同時修改同一行記錄,如何避免同時爭奪呢?數據庫的ACID屬性幫助你處理並發問題。

2.衝突恢復,即使ACID幫助實現原子並發操作,如果第一個更新成功,但是第二個修改失敗怎麼辦?這可能需要2PC兩段事務提交機制。但是2PC事務難以橫向擴展伸縮,在分佈式系統中根據CAP定理,會有很差的性能。

日誌

Kafka這樣的消息系統能夠實現日誌的抽象,從而幫助同步狀態的不同視圖,以惡搞日誌是一種帶有順序消息的集合,這個順序對於分佈式系統非常重要,Kafka提供了publish-subsribe發佈-訂閱模型,包括topic主題,分區,broker或消費者,日誌成為狀態核心。下面看看Kafka是如何實現ACID?

1. Atomicity原子性:如果一個日誌消息消費者發生問題怎麼辦?比如從Kafka讀取消息後寫入緩存或數據庫出錯怎麼辦?每個消費者會保有一個指針,使用Kafka的專用名詞就是offset,這個指針或偏差會指向日誌中最後成功的一個消息,當消費者有可用時,能消費者這些消息並完成事務,我們可以認為其是最終原子性,這對於大多數分佈式系統已經足夠。

2.Isolation隔離性:競爭出現是因為沒有順序,沒有人排隊就會出現哄搶現象,而順序在Kafka是由日誌順序保證。

3.Durability持久性:Kafka有強的持久保證,消息會寫到磁盤在幾個broker之間複製,不要使用Kafka作為狀態長期保存,可以將消息備份到亞馬遜S3或Hadoop。

4.Consistency一致性:當消費者從日誌中讀取消息時是有採集率的,這實際解耦了生產者和消費者,使用日誌作為緩衝buffer,這就導致我們的系統狀態是最終一致性,這個過程是異步過程。