您好,登錄后才能下訂單哦!
什么是分布式系統?關于這點其實并沒有明確且統一的定義。在我看來,只要一個系統滿足以下幾點就可以稱之為分布式系統
要想更好的理解分布式系統,并正確使用甚至構建分布式系統,需要理解其中的兩個關鍵概念——分布式系統的數據一致性和分布式系統的冪等性。
對于分布式系統,數據可能存在于不同的物理節點上,節點之間只能通過網絡進行通信來協調彼此之間的狀態,而網絡通信需要時間并且其本身并不十分可靠,因而如何保持數據一致性成為了分布式系統的難題。對于不同的分布式系統,其一致性語義以及面對的一致性難題可能略有差別
1.1 分布式存儲系統中的一致性問題
在分布式存儲系統中,為了保持系統的高可用,同時增加讀操作的并發性,同一份數據會有多份副本,不同的副本存儲于不同的節點上,如下圖所示
在并發環境下,因為存在多個客戶端同時讀取同一數據在不同節點上的副本,因而如何維護數據的一致性視圖就非常重要,即對于使用該分布式系統的客戶端而言,對于多副本數據的讀寫其表現應該和單份數據一樣,通常系統是通過數據復制的方式來達到這一點的,
1.2 微服務應用的分布式一致性問題
微服務架構下,原有的單體應用按功能被拆分成一個個微服務應用,每個微服務應用被部署在不同的機器節點上,只完成原有單體應用的某一部分功能,操作屬于該業務功能的數據庫或表。彼此之前通過網絡通信的方式協調彼此之間的工作,作為整體共同對外提供服務,因而一個業務功能的實現,可能會涉及到多個微服務的調用,操作物理上不同的多個數據庫或表。比如對于下單并支付這個業務功能而言,需要調用下單微服務和支付微服務來共同完成。
對于下單并支付這一業務功能,應用先調用訂單微服務,在訂單數據庫中添加一條訂單記錄,成功后再調用支付微服務添加相應的支付記錄,只有這兩個微服務都調用成功,該業務功能才算執行成功。這個過程可能存在以下的問題:
1.3 對于一致性的正確理解
分布式存儲系統的一致性問題,主要在于如何維持多副本的一致性視圖上,即如何使多份數據對外表現的和一份數據一樣。而微服務架構下的分布式應用系統,其一致性問題主要在于如何使不同微服務的數據對同一業務狀態的描述保持一致,比如對于下單并支付這一業務操作而言,下單和支付要么同時成功,要么應該同時失敗,而不應該一個成功一個失敗,并且在這個過程中,某部分已經成功或失敗的數據是否應該對客戶端可見。在聯系一下本地事務ACID中的一致性,我們可能會產生一定的混亂:它們講的一致性是一個東西嗎?先說下我的個人理解:不管是ACID的一致性還是不同分布式系統中的一致性,它們本質上講的是一件事:數據的一致性,在于正確的反應現實世界,對發生于現實世界的事情的正確描述。這就要求,一致性的數據至少要滿足以下兩個條件:
從這個意義上,不管是單機數據庫還是分布式存儲系統還是微服務架構下的分布式應用,對一致性的追求本質上是一樣的:在滿足系統本身約束的前提下,對于發生的業務操作及其執行狀態的一致性描述。只不過由于分布式系統數據的分布式存儲以及網絡通信狀況的復雜,使得分布式系統要保持數據一致性相比單機應用要考慮更多復雜的因素,實現也要困難的多。很多文章把它們做了嚴格的區分,個人覺得很沒有必要,也不利于對于一致性的正確理解,從哲學的角度看,是割裂了事物共性和個性之間的聯系。
就好像單機數據庫中為事務的隔離性設置了不同的級別,分布式系統中對數據的一致性級別也有分類。總的來說可以分為強一致性和弱一致性兩大類,弱一致性中又可以繼續細分為最終一致性,因果一致性,會話一致性,單調讀一致性和單調寫一致性等多種,不過弱一致性中只有最終一致性比較重要,其他的可以暫時忽略。
嚴格意義上來講,真正的一致性模型只有一種——強一致性,這也是一種理想化的模型。它為分布式數據維護了完全一致的視圖,使得一旦修改了數據后,所有客戶端能夠馬上看到這個更新后的值并基于這個新值進行后續的操作,使得我們操作分布式數據和操作本地數據一樣。在分布式系統中要實現一致性需要考慮其他因素,比如可用性和分區容忍性,而這些因素相互有制約,這種制約關系在CAP定理中被很好的進行了描述。
CAP是"Consistency","Availabilty","Partition Tolerance"的簡稱,分別代表了:強一致性,可用性和分區容忍性,它們的含義分別如下:
CAP定理的內容:對于一個分布式系統,無法同時實現強一致性,可用性和分區容忍性,即CAP三要素不可兼得。
3.1 如何理解CAP三要素不可兼得
由于網絡的不可靠性,網絡分區的情況不可避免的會發生,當出現網絡分區時,不同分區的機器無法進行通信。分布式系統必須能夠在出現網絡分區的情況下繼續工作,因而對于分布式系統而言,P即分區容忍性是必須要具備的要素,那么問題就轉化為了,在系統滿足分區容忍性的前提下,為什么強一致性和可用性不可兼得。
假設數據項A的三個副本分別存儲在不同的物理節點,在某一時刻,系統狀態如下圖所示
當客戶端將節點1上的A修改為2后,系統出現了網絡分區,其中節點1和節點2在一個網絡分區中,而節點3在另一個分區中
當有客戶端嘗試讀取節點3上的A值時,系統將面臨兩難困境
因而,對于滿足分區容錯性的系統而言,強一致性和可用性的要求難以同時被滿足。其實這是很容易理解的,即使沒有網絡分區,因為不同節點上的數據需要經過網絡通信來保持一致性,這個過程本身就比較花時間,當需要在給定很短的時限內基于客戶端響應時,對于一致性的保證自然就比較弱。
3.2 如何正確理解CAP定理
由CAP定理可知,在分布式系統中過于追求數據的強一致性將導致可用性一定程度被犧牲,這意味著系統將不能很好的響應用戶的請求,這會一定程度影響用戶體驗。因而對于大部分布式系統而言,應當在保證系統高可用的前提下去追求數據的一致性,BASE原則正是對這一思想的描述。
BASE理論的核心思想是:把分布式系統的可用性放在首位,放棄CAP中對數據強一致性的追求,只要系統能保證數據最終一致。
4.1 CAP,BASE以及ACID的關系
CAP描述了對于一個分布式系統而言重要的三要素:數據一致性,可用性,分區容錯性之間的制約關系,當你選擇了其中的兩個時,就不得不對剩下的一個做一定程度的犧牲。BASE和ACID都可以看做是對CAP三要素進行取舍后的某種特殊情況
冪等的概念來自于抽象代數,比如對于一元函數來說,滿足以下條件
即可稱為滿足冪等性。在計算機科學中,一個操作如果多次執行產生的影響與一次執行的影響相同,這樣的操作即符合冪等性。在分布式系統中,服務消費方調用服務提供方的接口,多次調用的結果應該與一次調用的結果一樣,這正是分布式環境下冪等性的語義。為什么冪等性對分布式系統而言如此重要?因為在分布式環境下,服務的調用一般采用http協議或者rpc的方式,即雙方需要通過網絡進行通信,而因為網絡故障或者消息超時的存在,可能服務消費方已經成功調用了服務提供方的服務接口,但是消費方并沒有收到來自對方的成功響應,導致消費方以為服務調用失敗從而再次進行調用,也就是說網絡的不可靠性導致了服務接口被多次調用的可能。分布式系統必須保證在這種情況下,即使接口被多次調用,它對系統產生的影響應該與該接口只被調用一次的結果一樣。
6.1 微服務架構下的分布式一致性問題
微服務架構下,處理一個業務請求可能需要調用多個微服務進行處理,以前面的下單并支付場景為例,完成該業務請求需要先后調用訂單微服務的下單接口和支付微服務的支付接口,只有這兩個接口都調用成功,該業務操作才算執行成功。那么微服務架構中是如何保證同屬于一個業務單元的多個操作的原子性以及保證分布式數據一致性的?——答案是分布式事務。
分布式事務是指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位于不同的分布式系統的不同節點之上
并且根據遵循的一致性原則不同,可以分為剛性分布式事務和柔性分布式事務兩大類。
這當然導致了系統可用性的降低,加上剛性事務實現時會導致同步阻塞的問題,鎖定資源等問題,會極大的影響系統的吞吐量和設計彈性,所以實際上微服務架構不太會采用剛性事務。
在這個不一致窗口內,系統允許客戶端對不一致的數據進行訪問,因而系統的可用性相比而言會更好,加上其擴展性良好以及吞吐量的優勢,一般微服務架構下都會采用柔性事務。柔性事務有多種不同的實現方式,比如基于可靠事件的模式,基于補償的模式,基于Sagas長事務的模式等,具體的實現原理以及優缺點對比就放到下一篇在詳解解釋。
6.2 微服務架構下的冪等性問題
6.2.1 冪等性場景
在微服務架構下,不同微服務間會有大量的基于http,rpc或者mq消息的網絡通信,接口的重復調用以及消息的重復消費可能會經常發生,比如以下這些情況
微服務架構應該具有冪等性,當接口被重復調用時,消息被重復消費時,對系統的產生的影響應該和接口被調用一次,消息被消費一次時一樣。
6.2.2 CRUD操作的冪等性分析
總結:通常只需要對新增請求和更新請求作冪等性保證。
6.2.3 如何解決冪等性問題
適合對更新請求作冪等性控制,比如要更新商品的名字,這是就可以在更新的接口中增加一個版本號來做冪等性控制
boolean updateGoodsName(int id,String newName,int version);
數據庫更新的SQL語句如下
update goods set name=#{newName},version=#{version} where id=#{id} and version<${version}
適合在有狀態機流轉的情況下,比如訂單的創建和付款,訂單的創建肯定是在付款之前。這是可以添加一個int類型的字段來表示訂單狀態,創建為0,付款成功為100,付款失敗為99,則對訂單狀態的更新就可以這樣表示
update order set status=#{status} where id=#{id} and status<#{status}
insert into goods_category (goods_id,category_id,create_time,update_time)
values(#{goodsId},#{categoryId},now(),now())
on DUPLICATE KEY UPDATE
update_time=now()
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。