首頁>技術>

作為一名程式設計師,你是不是經常在很多場景,例如看部落格、聊天吹水等等時候聽到這樣一個詞"系統資料一致性",是不是有時候感覺到了迷糊,不知道這個"系統資料一致性"到底是在說什麼?其實,你可能只是不明白這個詞,但是你肯定在實際工作中發現、解決過這樣的問題。

單體架構下系統資料一致性問題

在傳統的系統應用中,一般都是使用單體架構來構建系統的。即所有的功能模組都放在一起實現,打成一個WAR包部署在Tomcat中,資料一般存放在關係型資料庫中,如MySQL資料庫。

前面我說過即使這種單體架構的系統也是資料一致性的問題的,舉一個電商下單的例子,使用者提交完訂單,系統,系統在訂單表order表中寫入訂單金額、使用者等相關資料,在訂單明細order_item表中寫入商品價格、購買的數量等資料,最後更新商品的庫存sku資訊。使用者下單成功之後,系統操作了order、order_item、sku這三個資料表,對於這三個表的操作無論成功與失敗,都應該是原子的,操作成功則都要成功,失敗則都要一起失敗。不然就會出現髒資料,資料一致性被破壞。

1、 如果操作order和order_item表成功,操作sku表失敗,則會導致本應該扣減的庫存沒有扣減,則商品有可能出現超賣。

2、如果操作order和order_item表失敗,操作sku表成功,則會導致本不應該扣減的庫存扣減了,則商品有可能出現少賣。

3、如果操作order和sku表成功,order_item操作失敗,則這個訂單資料丟失,訂單後續的操作肯定也是操作不了了。

上面只是簡單的舉了三種可能出現的情況,也可能會有其他的情況發生。那我們怎麼避免這些情況的發生呢?其實這種問題稍微有的開發經驗的同學都會想到解決方案,那就是使用資料庫的事務,事務的原子性保證上述的步驟成功則一起成功,失敗則一起失敗。

BEGIN;INSERT INTO order;INSERT INTO order_item;UPDATE sku;COMMIT; # ROLLBACK

在單體架構的系統下解決內部模組的資料一致性的問題,用資料庫的ACID特性就能保證。

單體架構的優點就是相對分散式來說開發簡單,功能可以集中管理,模組之間通訊沒有損耗。但隨著業務越來越複雜、需求越來越龐大,人們對系統響應時間、吞吐量和出現故障的時候的系統可用性的要求也越來越高!傳統的單體架構系統在這種情況下暴露的缺點也越來越多,人們開始尋求轉變。既然部署在一個伺服器上的單體架構系統搞不定,那就多部署幾臺,即用多臺單機節點組成叢集,再用負載均衡向外提供服務。

但是這樣做還是解決不了單體架構存在的一些問題:

只能使用同種語言開發,不能針對不同業務場景利用不同語言的優勢開發對應的模組。系統模組耦合性太強,系統中某一個模組出現問題,例如高併發、大資料場景或者出現bug,整個系統都會受到牽連。某個模組釋出,整個系統都要停機發布,系統所有模組都不能對外提供服務,這樣無法快速響應市場需求。叢集負擔大,如果想要叢集,只能對整個系統進行叢集,即使只有一個模組有壓力。

叢集(Cluster): 系統單機部署對外服務能力出現瓶頸,則將系統進行多機部署,這些系統對外提供相同的服務,每個單機系統我們稱之為節點,多個節點統一起來則可以稱之為叢集。

分散式架構下系統資料一致性問題

天下大事分久必合、合久必分!既然單體架構解決不了問題,那我們就嘗試拆分系統,讓專業的人做專業的事,那如何進行拆分呢?拆分一般分為水平拆分和垂直拆分。這裡說的拆分並不單指資料庫拆分,而是所有模組都進行拆分,每個模組都有自己的快取、資料庫等等。

水平拆分指的是單一的節點無法滿足效能的需求,需要進行數量上的擴充套件。每一個節點都具有相同的功能,每一個節點都負責一部分請求,節點們組成一個叢集,對外進行提供服務。垂直拆分指的是按照功能進行拆分,秉著"專業的人幹專業的事",把複雜的系統拆分成各個模組。模組之間透過RPC進行通訊,可以做到高內聚、低耦合,每個模組獨立部署和維護,可以快速迭代響應市場需求。

因此,分散式架構在這種背景下應運而生。

分散式(Distributed)架構:分散式系統是由集中式系統逐漸演變而來。所謂的集中式系統,就是把系統中所用的功能都集中到一起,從而向外提供服務的單體應用。

軟體行業是沒有銀彈的,每一個被髮明出來的新技術,都是一把雙刃劍,都是在特定的領域解決了某些老問題,但是同時也會帶來新的問題。那麼微服務這種分散式架構解決了什麼老問題?同時它又帶來了哪些新問題呢?

解決了老問題

微服務這種分散式架構主要解決了單體架構存在的一些問題。

各個服務可以使用不同的語言開發,可以利用不同語言的優勢開發不同模組。服務之間可以做到高內聚、低耦合。每個服務可以獨立維護、部署,可以快速響應市場需求。可以單獨對某個有高併發、大流量的服務單獨進行最佳化,不浪費資源。

帶來了新問題

系統的監控難度加大。資料的一致性成為問題。系統的複雜度提高,系統的維護、設計成本增加,除錯、糾錯難度加大。

新問題中的 資料一致性問題 才是本文接下來的重點。

為啥會有這個資料一致性問題呢?

單體架構按照文中的說法,是一種不太時髦的架構方式,都能輕鬆解決資料一致性問題,新發明的分散式架構卻又成了一個棘手的問題,這個到底是技術的進步還是技術在退步呢?哈哈(我的一點點吐槽)!!接下來我來解釋一下為啥分散式系統會有這樣的問題。 分散式系統每個功能大都部署在不同的伺服器上,部署在不同國家和地區的伺服器中,部署在不同的網路中,部署在不同國家和地區的網路中。這樣一個需要大量的伺服器共同協作,向外提供服務的系統,面臨著諸多的挑戰:

良莠不齊的伺服器和系統能力

分散式系統中的伺服器,可能配置不一樣,其上部署的系統可能也是由不同的程式語言、架構實現,因此處理請求的能力也就不一樣。

不可靠的網路

如上文所說,系統中各個服務可能部署在不同國家和地區,各個服務透過網路進行通訊,但是網路是不可靠的。網路經常會出現抖動、延時、分割、丟包等問題。 網路通訊中最讓人頭痛的是因為網路抖動、延時等問題導致系統之間的通訊出現超時:A服務向B服務發出請求,A服務沒有在約定的時間內接受到B服務的響應,你不能確定B服務到底有沒有處理完A服務的請求,這樣的不確定性就需要我們進行重試處理,那麼B服務就要解決請求冪等性問題。

伺服器的機房發生火災、斷電等事故。 支付寶出現過伺服器的電纜被挖斷的問題。

普遍存在的單點故障

分散式系統為了保證故障發生的時候,系統仍然保證可用,每個模組都採用叢集部署。單個節點的故障機率較低,但是節點數量達到一定規模時,系統中的節點出現故障的機率可能就變高了。

分散式系統就是這樣一些處在不同區域、有著不同能力和擁有單一功能的服務組成,他們通力合作才能向外提供服務,那如何保證他們的狀態、資訊一致並且協調有序就成了一個難題。

分散式系統就是要解決解決集中式的單體架構系統的各種缺陷,實現整個系統的 高效能 、高可用、可擴充套件,但是要實現這三個目標並不容易,將系統進行拆分的過程中會出現上文中說到的問題,為了解決這些問題,誕生了很多關於分散式的基本理論,比如CAP、BASE等等。

分散式架構有很多相關的理論和演算法,這裡我只說了CAP、BASE理論,其他諸如Paxos演算法、Raft演算法、ZAB協議等等,這些大家自己找資料看看吧!

我們先來說說CAP理論

這個CAP理論相信很多人都聽說過,下面請允許我寫下教科書般的理論內容:

CAP原則又稱CAP定理,指的是在一個分散式系統中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分割槽容錯性),三者不可得兼。

一致性(C):在分散式系統中的所有資料備份,在同一時刻是否同樣的值,也就是等同於所有節點訪問同一份最新的資料副本。 可用性(A):保證每個請求不管成功或者失敗都有響應,即使資料不是最新的。 分割槽容忍性(P):系統中的某個節點或者網路分割槽出現了故障的時候,整個系統仍然能對外提供的服務。

什麼情況?這個CAP理論上來就給出三個概念或者說是指標,還說分散式系統只能滿足上面兩個指標。大家是不是經常聽到CAP理論,但是卻又不是很理解為什麼CAP三個指標只能滿足其中的兩個,那麼接下來我給大家解釋一下:

就如前面的“分散式架構圖”展示的一樣,系統一個對外的服務涉及到多個節點通訊和互動,節點所處的網路發生分割槽故障的問題又無法避免,所以分散式系統中分割槽容錯性必須要考慮,那麼系統自然也不可能同時滿足上面說的三個指標。

分散式系統中CAP如何抉擇?

在分散式系統內,各種因素導致分割槽是必然的會發生的,不考慮分割槽容忍性(P),一旦發生分割槽錯誤,整個分散式系統就完全無法使用了,這其實和最開始的單體應用一樣有單點問題,這樣的系統是和分散式架構理論是相違背的,同時也是不符合實際需要的。所以,對於分散式系統,我們只能能考慮當發生分割槽錯誤時,如何選擇一致性(C)和可用性(A)。

根據一致性和可用性的選擇不同,開源的分散式系統往往又被分為 CP 系統和 AP 系統。 當系統在發生分割槽故障後,客戶端的任何請求都被阻塞或者超時,但是,系統的每個節點總是會返回一致的資料,則這樣的系統就是 CP 系統,經典的比如 Zookeeper。 當系統發生分割槽故障後,客戶端依然可以訪問系統,但是獲取的資料是不一致的,有的是新的資料,有的還是老資料,那麼這樣系統就是 AP 系統,經典的比如 Eureka。

前面說分散式系統不考慮分割槽容忍性(P)為啥分割槽錯誤發生,系統就不能用了,這裡我再解釋一下: 不考慮分割槽容忍性(P),那就是選擇CA。假設發生了分割槽錯誤,系統由於可用性(A)的要求,即使系統發生分割槽故障也要提供服務,那系統就仍然向外提供服務,因為服務肯定的包含對資料的讀取、寫入、更新、刪除,可是由於一致性(C)的要求,系統中所有的節點資料都要保持一致,因為分割槽錯誤發生,節點的資料同步肯定無法進行,資料副本的一致性就無法保證,那就不能像對外提供服務。那這樣CA就相互矛盾,系統無法保證可用性(A)和一致性(C),系統自然是不能使用了,也就是說沒有選擇CA的分散式系統,而且這分割槽容忍性(P)必須要考慮!而且,不是一個系統選擇了可用性(A)或者一致性(C),可以是其中的模組選擇了可用性(A)和一致性(C)

Zookeeper常常有人用它作為dubbo的註冊中心,Eureka作為Spring Cloud體系中的註冊中心,其實對於註冊中心角色來說,我覺得Eureka比Zookeeper更適合!

還有一點這裡我說一下,其實大部分情況下分散式系統是沒有問題的,C和A兩個指標都是同時滿足的,只是在分割槽問題發生的情況下,才需要我們考慮到底是選擇C還是A。

前文說到解決單點故障的問題,我們引入了叢集。在分散式系統中我們為了提高系統的可用性,也是不可避免的使用副本的機制,引入了副本則就需要同步資料到不同的副本,從而引發了副本一致性的問題。就如前面展示的“分散式架構圖”中,會員、訂單和產品服務都是獨立部署且分別使用不同的資料庫,每個服務內部又是使用資料庫叢集,資料在服務與服務之間、在某個服務的資料庫叢集中間等等的流轉、同步,這些過程都是有網路、時間消耗的,一個數據從最開始的產生到它應該到的地方不會瞬時完成,而CAP理論是基於瞬時,在同一時刻任意節點都保持著最新的資料副本,它是忽略網路延遲、節點處理資料的速度的,這個在目前的技術下是不可能做到的,從這個角度來看,CAP理論實在是樂觀主義了。

CAP理論的缺點是什麼?

CAP理論其實是有缺點的,前文也提到一些,具體的缺點如下:

理論忽略網路延遲、節點處理資料的速度

CAP的理論的作者布魯爾在定義一致性時,並沒有將上述的問題考慮進去。即當事務提交之後,資料能夠瞬間複製到所有節點。但實際情況下,資料從產生到複製到各個服務、各個節點,總是需要花費一定時間的。如果在相同機房可能是幾毫秒,如果跨地域、跨機房,可能是幾十毫秒甚至是一百多毫秒。這也就是說,CAP理論中的C在實踐中是不可能完美實現的,在資料副本的同步的過程中,節點之間的資料在一個短時間內並不一致。

理論中的一致性是強一致性

CAP理論中的一致性的概念是,在分散式系統中的所有資料備份,在同一時刻是否同樣的值,也就是等同於所有節點訪問同一份最新的資料副本。在某些場景下這種強一致性要求並不是那麼高。在一個日誌蒐集系統,在高併發、大資料的情況下,一條日誌寫入需要稍後一會才能在ELK中展示出來,這樣是沒有問題的。透過犧牲強一致性獲得可用性,在一定時間之後最終資料達成一致性即可。

理論中的指標的選擇和放棄並不是三選二的關係

CAP理論告訴我們三者只能取兩個,需要放棄另外一個,這裡的放棄是有一定誤導作用的,因為“放棄”讓很多人理解成什麼也不做。實際上,CAP理論的“放棄”只是說在系統分割槽錯誤過程中,我們無法同時保證C和A,但並不意味著什麼都不做。分割槽期間放棄C或者A,並不意味著永遠放棄C和A,我們可以在分割槽期間進行一些操作,從而讓分割槽故障解決後,系統能夠重新達到CA的狀態。最典型的就是主從資料庫中主資料掛了,後面進行修復,使得重新達到CA狀態。

CAP理論的改進版BASE理論

由於CAP理論在定義時過於的樂觀,導致他有些缺陷,於是又有大神改進了CAP理論,從而引申出理論改進版本:BASE理論。eBay的架構師Dan Pritchett根據他自身在大規模分散式系統的實踐經驗,提出了BASE理論。BASE理論是對CAP理論的延伸和補充,它滿足CAP理論,透過犧牲強一致性獲得可用性,在一定的時間視窗內,達到資料的最終一致性。

BASE理論模型包含如下三個元素:

BA:Basically Available,基本可用。S:Soft State,軟狀態,狀態可以在一定時間內不同步。E:Eventually Consistent,最終一致性,在一定的時間視窗內,最終資料達成一致即可。

Basically Available 基本可用

BASE理論中的Basically Available 基本可用,就是系統在出現問題的時候,犧牲一部分的功能,來保障核心功能正常。這其實就是一種妥協,相當於壁虎斷臂求生。 就像前幾年的雙十一淘寶,訂單支付、退款直接崩掉了,後面就進行改進限流需要你多試幾次才能付款、退款,再後來雙十一那幾天是不能申請退款的,直接就把你這個功能給關閉了,相當於服務熔斷了。這就是犧牲非核心的功能,將所有的資源都用來保障核心的支付功能。

Soft State,軟狀態

允許系統在一定時間內的狀態不同步,允許系統處於軟狀態,這個軟狀態其實就是中間狀態。比如採用分散式架構的電商系統,使用者下單完成並付款,是否支付成功,是支付系統完成的,訂單系統不會等支付系統返回是否支付成功再把結果返回給客戶的,而是先把訂單狀態設定為付款中,返回給客戶,然後支付系統收到非同步通知確定支付成功成功,再把狀態設定為付款完成,再把付款完成資訊推送給訂單系統。這樣,就可以提高系統的響應速度。即使這支付系統出現故障宕機了,系統重啟之後可以透過定時任務補償處理未完成的資料,然後根據資料所處的狀態進行補償處理,最終完成資料處理。付款中這個狀態,就是軟狀態即中間狀態。

Eventually Consistent,最終一致性

資料不會一直處在中間狀態,就如上面的例子所說,處於中間狀態的資料會有采用類似定時任務一樣的補償處理,將資料修復成正確的狀態,最終資料達成一致。

重說“帶來了新問題”

前文說到分散式架構解決了單體架構的一些問題,但是同時也帶來了一些新的問題,這裡我們著重說一下,本來不是大問題的“資料一致性”問題。前面舉了一個電商系統中的經典案例:下訂單與扣庫存。單體架構的應用我們直接用資料庫事務的ACID特性就可解決,但是採用分散式架構的系統就沒有那麼好解決了,我們先說一下在分散式架構下的系統是如何完成“下訂單與扣庫存”的,這裡就不畫圖了直接用虛擬碼來展示:

public void buildOrder(OrderDto orderDto) {    // 1.儲存訂單  orderService.saveOrder(orderDto);  // 2.扣除產品庫存  inventoryService.deductInventory(orderDto);}

這步驟一儲存訂單是在訂單系統中執行,步驟二扣除產品庫存是在庫存系統執行,這個下訂單與扣庫存兩個步驟分別涉及到了兩個系統,使用RPC的方式和兩個系統進行互動。由於這兩個步驟不是原子的,不能保持一致的話會導致很多的問題:

比如先儲存訂單成功,然後扣除產品庫存失敗,那訂單就要回滾處理;如果先扣除產品庫存成功,然後儲存訂單失敗,那庫存就要回滾;或者說先儲存訂單然後扣除產品庫存時請求超時,其實庫存已經扣除成功等等問題。

這些問題你不解決,就有可能導致產品多賣或者是產品出現少賣,不管出現哪個都會造成資損或者客訴,任何一種情況都不是我們想發生的。

由於庫存系統和訂單系統分別使用各自的資料庫,那原先使用資料庫事務的ACID特性保證資料的一致性就不能奏效了,分散式架構的系統就產生了資料一致性的問題,這種跨多個數據庫的事務問題,其實就是分散式事務問題。要解決分散式架構的系統的資料一致性問題,其實就是解決分散式事務的問題。

目前業界也出現了很多分散式事務的解決方案,例如兩階段提交2PC、三階段提交3PC、TCC還有基於可靠訊息等方案,他們用不同的方案實現分散式事務,解決資料一致性的問題,這裡就不再詳述。

基於可靠訊息解決分散式事務,解決資料一致性的問題,其中一種方案叫做本地訊息表,這種方案名稱大家可能不知道,但是你很有可能這樣做過,這個後面的文章再細說。

8
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 配置群暉Docker使用阿里雲映象加速