【51CTO.com原創(chuàng)稿件】對于開發(fā)或設(shè)計分布式系統(tǒng)的架構(gòu)師工程師來說,CAP 是必須要掌握的理論。
圖片來自 Pexels
But:這個文章的重點并不是討論 CAP 理論和細(xì)節(jié),重點是說說 CAP 在微服務(wù)中的開發(fā)怎么起到一個指引作用,會通過幾個微服務(wù)開發(fā)的例子說明,盡量的去貼近開發(fā)。
CAP 定理又被稱為布魯爾定理,是加州大學(xué)計算機科學(xué)家埃里克·布魯爾提出來的猜想,后來被證明成為分布式計算領(lǐng)域公認(rèn)的定理。
不過布魯爾在出來 CAP 的時候并沒有對 CAP 三者(Consistency,Availability,Partition tolerance)進(jìn)行詳細(xì)的定義,所以在網(wǎng)上也出現(xiàn)了不少對 CAP 不同解讀的聲音。
CAP 定理
CAP 定理在發(fā)展中存在過兩個版本,我們以第二個版本為準(zhǔn):
在一個分布式系統(tǒng)中(指互相連接并共享數(shù)據(jù)的節(jié)點集合)中,當(dāng)涉及到讀寫操作時,只能保證一致性(Consistence)、可用性(Availability)、分區(qū)容錯性(Partition Tolerance)三者中的兩個,另外一個必須被犧牲。
這個版本的 CAP 理論在探討分布式系統(tǒng),更加強調(diào)兩點是互聯(lián)和共享數(shù)據(jù),其實也是理清楚了第一個版本中三選二的一些缺陷。
分布式系統(tǒng)不一定都存在互聯(lián)和共享數(shù)據(jù),例如 Memcached 集群相互間就沒有存在連接和共享數(shù)據(jù)。
所以 Memcached 集群這類的分布式系統(tǒng)并不在 CAP 理論討論的范圍,而像 MySQL 集群就是互聯(lián)和數(shù)據(jù)共享復(fù)制,因此 MySQL 集群是屬于 CAP 理論討論的對象。
一致性(Consistency)
一致性意思就是寫操作之后進(jìn)行讀操作無論在哪個節(jié)點都需要返回寫操作的值。
可用性(Availability)
非故障的節(jié)點在合理的時間內(nèi)返回合理的響應(yīng)。
分區(qū)容錯性(Partition Tolerance)
當(dāng)網(wǎng)絡(luò)出現(xiàn)分區(qū)后,系統(tǒng)依然能夠繼續(xù)旅行社職責(zé)。
在分布式的環(huán)境下,網(wǎng)絡(luò)無法做到 100% 可靠,有可能出現(xiàn)故障,因此分區(qū)是一個必須的選項。
如果選擇了 CA 而放棄了 P,若發(fā)生分區(qū)現(xiàn)象,為了保證 C,系統(tǒng)需要禁止寫入,此時就與 A 發(fā)生沖突;如果是為了保證 A,則會出現(xiàn)正常的分區(qū)可以寫入數(shù)據(jù),有故障的分區(qū)不能寫入數(shù)據(jù),則與 C 就沖突了。
因此分布式系統(tǒng)理論上不可能選擇 CA 架構(gòu),而必須選擇 CP 或 AP 架構(gòu)。
分布式事務(wù) BASE 理論
BASE 理論是對 CAP 的延伸和補充,是對 CAP 中的 AP 方案的一個補充,即使在選擇 AP 方案的情況下,如何更好的最終達(dá)到 C。
BASE 是基本可用,柔性狀態(tài),最終一致性三個短語的縮寫,核心的思想是即使無法做到強一致性,但應(yīng)用可以采用適合的方式達(dá)到最終一致性。
CAP 在服務(wù)中實際的應(yīng)用例子
理解貌似講多了,項目的 CAP 可以參考下李運華的《從零開始學(xué)架構(gòu)》的書里面的 21,22 章,比較詳細(xì)的描繪了 CAP 的理論細(xì)節(jié)和 CAP 的版本演化過程。
這里著重講解的是神一樣的 CAP 在我們的微服務(wù)中怎么去指導(dǎo)和應(yīng)用起來,大概會舉幾個平時常見的例子。
服務(wù)注冊中心,是選擇 CA 還是選擇 CP?
服務(wù)注冊中心解決的問題
在討論 CAP 之前先明確下服務(wù)注冊中心主要是解決什么問題:
- 服務(wù)注冊:實例將自身服務(wù)信息注冊到注冊中心,這部分信息包括服務(wù)的主機 IP 和服務(wù)的 Port,以及暴露服務(wù)自身狀態(tài)和訪問協(xié)議信息等。
- 服務(wù)發(fā)現(xiàn):實例請求注冊中心所依賴的服務(wù)信息,服務(wù)實例通過注冊中心,獲取到注冊到其中的服務(wù)實例的信息,通過這些信息去請求它們提供的服務(wù)。
目前作為注冊中心的一些組件大致有:
- Dubbo 的 Zookeeper
- Spring Cloud 的 Eureka,Consul
- RocketMQ 的 nameServer
- HDFS 的 nameNode
目前微服務(wù)主流是 Dubbo 和 Spring Cloud,使用最多是 Zookeeper 和 Eureka,我們就來看看應(yīng)該根據(jù) CAP 理論怎么去選擇注冊中心。(Spring Cloud 也可以用 ZK,不過不是主流不討論)
Zookeeper 選擇 CP
Zookeeper 保證 CP,即任何時刻對 Zookeeper 的訪問請求能得到一致性的數(shù)據(jù)結(jié)果,同時系統(tǒng)對網(wǎng)絡(luò)分割具備容錯性,但是它不能保證每次服務(wù)的可用性。
從實際情況來分析,在使用 Zookeeper 獲取服務(wù)列表時,如果 ZK 正在選舉或者 ZK 集群中半數(shù)以上的機器不可用,那么將無法獲取數(shù)據(jù)。所以說,ZK 不能保證服務(wù)可用性。
Eureka 選擇 AP
Eureka 保證 AP,Eureka 在設(shè)計時優(yōu)先保證可用性,每一個節(jié)點都是平等的。
一部分節(jié)點掛掉不會影響到正常節(jié)點的工作,不會出現(xiàn)類似 ZK 的選舉 Leader 的過程,客戶端發(fā)現(xiàn)向某個節(jié)點注冊或連接失敗,會自動切換到其他的節(jié)點。
只要有一臺 Eureka 存在,就可以保證整個服務(wù)處在可用狀態(tài),只不過有可能這個服務(wù)上的信息并不是最新的信息。
ZK 和 Eureka 的數(shù)據(jù)一致性問題
先要明確一點,Eureka 的創(chuàng)建初心就是為一個注冊中心,但是 ZK 更多是作為分布式協(xié)調(diào)服務(wù)的存在。
只不過因為它的特性被 Dubbo 賦予了注冊中心,它的職責(zé)更多是保證數(shù)據(jù)(配置數(shù)據(jù),狀態(tài)數(shù)據(jù))在管轄下的所有服務(wù)之間保持一致。
所以這個就不難理解為何 ZK 被設(shè)計成 CP 而不是 AP,ZK 最核心的算法 ZAB,就是為了解決分布式系統(tǒng)下數(shù)據(jù)在多個服務(wù)之間一致同步的問題。
更深層的原因,ZK 是按照 CP 原則構(gòu)建,也就是說它必須保持每一個節(jié)點的數(shù)據(jù)都保持一致。
如果 ZK 下節(jié)點斷開或者集群中出現(xiàn)網(wǎng)絡(luò)分割(例如交換機的子網(wǎng)間不能互訪),那么 ZK 會將它們從自己的管理范圍中剔除,外界不能訪問這些節(jié)點,即使這些節(jié)點是健康的可以提供正常的服務(wù),所以導(dǎo)致這些節(jié)點請求都會丟失。
而 Eureka 則完全沒有這方面的顧慮,它的節(jié)點都是相對獨立,不需要考慮數(shù)據(jù)一致性的問題,這個應(yīng)該是 Eureka 的誕生就是為了注冊中心而設(shè)計。
相對 ZK 來說剔除了 Leader 節(jié)點選取和事務(wù)日志機制,這樣更有利于維護(hù)和保證 Eureka 在運行的健壯性。
再來看看,數(shù)據(jù)不一致性在注冊服務(wù)中會給 Eureka 帶來什么問題,無非就是某一個節(jié)點被注冊的服務(wù)多,某個節(jié)點注冊的服務(wù)少,在某一個瞬間可能導(dǎo)致某些 IP 節(jié)點被調(diào)用數(shù)多,某些 IP 節(jié)點調(diào)用數(shù)少的問題。
也有可能存在一些本應(yīng)該被刪除而沒被刪除的臟數(shù)據(jù)。
服務(wù)注冊應(yīng)該選擇 AP 還是 CP
對于服務(wù)注冊來說,針對同一個服務(wù),即使注冊中心的不同節(jié)點保存的服務(wù)注冊信息不相同,也并不會造成災(zāi)難性的后果。
對于服務(wù)消費者來說,能消費才是最重要的,就算拿到的數(shù)據(jù)不是最新的數(shù)據(jù),消費者本身也可以進(jìn)行嘗試失敗重試。總比為了追求數(shù)據(jù)的一致性而獲取不到實例信息整個服務(wù)不可用要好。
所以,對于服務(wù)注冊來說,可用性比數(shù)據(jù)一致性更加的重要,選擇 AP。
分布式鎖,是選擇 CA 還是選擇 CP?
這里實現(xiàn)分布式鎖的方式選取了三種:
- 基于數(shù)據(jù)庫實現(xiàn)分布式鎖
- 基于 redis 實現(xiàn)分布式鎖
- 基于 Zookeeper 實現(xiàn)分布式鎖
基于數(shù)據(jù)庫實現(xiàn)分布式鎖
構(gòu)建表結(jié)構(gòu):
利用表的 UNIQUE KEY idx_lock(method_lock)作為唯一主鍵,當(dāng)進(jìn)行上鎖時進(jìn)行 Insert 動作,數(shù)據(jù)庫成功錄入則以為上鎖成功,當(dāng)數(shù)據(jù)庫報出 Duplicate entry 則表示無法獲取該鎖。
不過這種方式對于單主卻無法自動切換主從的 MySQL 來說,基本就無法實現(xiàn) P 分區(qū)容錯性(MySQL 自動主從切換在目前并沒有十分完美的解決方案)。
可以說這種方式強依賴于數(shù)據(jù)庫的可用性,數(shù)據(jù)庫寫操作是一個單點,一旦數(shù)據(jù)庫掛掉,就導(dǎo)致鎖的不可用。這種方式基本不在 CAP 的一個討論范圍。
基于 Redis 實現(xiàn)分布式鎖
Redis 單線程串行處理天然就是解決串行化問題,用來解決分布式鎖是再適合不過。
實現(xiàn)方式:
setnx key value Expire_time 獲取到鎖 返回 1 , 獲取失敗 返回 0
為了解決數(shù)據(jù)庫鎖的無主從切換的問題,可以選擇 Redis 集群,或者是 Sentinel 哨兵模式,實現(xiàn)主從故障轉(zhuǎn)移,當(dāng) Master 節(jié)點出現(xiàn)故障,哨兵會從 Slave 中選取節(jié)點,重新變成新的 Master 節(jié)點。
哨兵模式故障轉(zhuǎn)移是由 Sentinel 集群進(jìn)行監(jiān)控判斷,當(dāng) Maser 出現(xiàn)異常即復(fù)制中止,重新推選新 Slave 成為 Master,Sentinel 在重新進(jìn)行選舉并不在意主從數(shù)據(jù)是否復(fù)制完畢具備一致性。
所以 Redis 的復(fù)制模式是屬于 AP 的模式。保證可用性,在主從復(fù)制中“主”有數(shù)據(jù),但是可能“從”還沒有數(shù)據(jù)。
這個時候,一旦主掛掉或者網(wǎng)絡(luò)抖動等各種原因,可能會切換到“從”節(jié)點,這個時候可能會導(dǎo)致兩個業(yè)務(wù)線程同時獲取得兩把鎖。
這個過程如下:
- 業(yè)務(wù)線程 -1 向主節(jié)點請求鎖
- 業(yè)務(wù)線程 -1 獲取鎖
- 業(yè)務(wù)線程 -1 獲取到鎖并開始執(zhí)行業(yè)務(wù)
- 這個時候 Redis 剛生成的鎖在主從之間還未進(jìn)行同步
- Redis 這時候主節(jié)點掛掉了
- Redis 的從節(jié)點升級為主節(jié)點
- 業(yè)務(wù)線程 -2 想新的主節(jié)點請求鎖
- 業(yè)務(wù)線程 -2 獲取到新的主節(jié)點返回的鎖
- 業(yè)務(wù)線程 -2 獲取到鎖開始執(zhí)行業(yè)務(wù)
- 這個時候業(yè)務(wù)線程 -1 和業(yè)務(wù)線程 -2 同時在執(zhí)行任務(wù)
上述的問題其實并不是 Redis 的缺陷,只是 Redis 采用了 AP 模型,它本身無法確保我們對一致性的要求。
Redis 官方推薦 Redlock 算法來保證,問題是 Redlock 至少需要三個 Redis 主從實例來實現(xiàn),維護(hù)成本比較高。
相當(dāng)于 Redlock 使用三個 Redis 集群實現(xiàn)了自己的另一套一致性算法,比較繁瑣,在業(yè)界也使用得比較少。
能不能使用 Redis 作為分布式鎖?這個本身就不是 Redis 的問題,還是取決于業(yè)務(wù)場景。
我們先要自己確認(rèn)我們的場景是適合 AP 還是 CP , 如果在社交發(fā)帖等場景下,我們并沒有非常強的事務(wù)一致性問題,Redis 提供給我們高性能的 AP 模型是非常適合的。
但如果是交易類型,對數(shù)據(jù)一致性非常敏感的場景,我們可能要尋找一種更加適合的 CP 模型。
基于 Zookeeper 實現(xiàn)分布式鎖
剛剛也分析過,Redis 其實無法確保數(shù)據(jù)的一致性,先來看 Zookeeper 是否適合作為我們需要的分布式鎖。
首先 ZK 的模式是 CP 模型,也就是說,當(dāng) ZK 鎖提供給我們進(jìn)行訪問的時候,在 ZK 集群中能確保這把鎖在 ZK 的每一個節(jié)點都存在。
這個實際上是 ZK 的 Leader 通過二階段提交寫請求來保證的,這個也是 ZK 的集群規(guī)模大了的一個瓶頸點。
①ZK 鎖實現(xiàn)的原理
說 ZK 的鎖問題之前先看看 Zookeeper 中幾個特性,這幾個特性構(gòu)建了 ZK 的一把分布式鎖。
ZK 的特性如下:
- 有序節(jié)點:當(dāng)在一個父目錄下如 /lock 下創(chuàng)建 有序節(jié)點,節(jié)點會按照嚴(yán)格的先后順序創(chuàng)建出自節(jié)點 lock000001,lock000002,lock0000003,以此類推,有序節(jié)點能嚴(yán)格保證各個自節(jié)點按照排序命名生成。
- 臨時節(jié)點:客戶端建立了一個臨時節(jié)點,在客戶端的會話結(jié)束或會話超時,Zookepper 會自動刪除該節(jié)點 ID。
- 事件監(jiān)聽:在讀取數(shù)據(jù)時,我們可以對節(jié)點設(shè)置監(jiān)聽,當(dāng)節(jié)點的數(shù)據(jù)發(fā)生變化(1 節(jié)點創(chuàng)建 2 節(jié)點刪除 3 節(jié)點數(shù)據(jù)變成 4 自節(jié)點變成)時,Zookeeper 會通知客戶端。
結(jié)合這幾個特點,來看下 ZK 是怎么組合分布式鎖:
- 業(yè)務(wù)線程 -1,業(yè)務(wù)線程 -2 分別向 ZK 的 /lock 目錄下,申請創(chuàng)建有序的臨時節(jié)點。
- 業(yè)務(wù)線程 -1 搶到 /lock0001 的文件,也就是在整個目錄下最小序的節(jié)點,也就是線程 -1 獲取到了鎖。
- 業(yè)務(wù)線程 -2 只能搶到 /lock0002 的文件,并不是最小序的節(jié)點,線程 2 未能獲取鎖。
- 業(yè)務(wù)線程 -1 與 lock0001 建立了連接,并維持了心跳,維持的心跳也就是這把鎖的租期。
- 當(dāng)業(yè)務(wù)線程 -1 完成了業(yè)務(wù),將釋放掉與 ZK 的連接,也就是釋放了這把鎖。
②ZK 分布式鎖的代碼實現(xiàn)
ZK 官方提供的客戶端并不支持分布式鎖的直接實現(xiàn),我們需要自己寫代碼去利用 ZK 的這幾個特性去進(jìn)行實現(xiàn)。
究竟該用 CP 還是 AP 的分布式鎖
首先得了解清楚我們使用分布式鎖的場景,為何使用分布式鎖,用它來幫我們解決什么問題,先聊場景后聊分布式鎖的技術(shù)選型。
無論是 Redis,ZK,例如 Redis 的 AP 模型會限制很多使用場景,但它卻擁有了幾者中最高的性能。
Zookeeper 的分布式鎖要比 Redis 可靠很多,但他繁瑣的實現(xiàn)機制導(dǎo)致了它的性能不如 Redis,而且 ZK 會隨著集群的擴大而性能更加下降。
簡單來說,先了解業(yè)務(wù)場景,后進(jìn)行技術(shù)選型。
分布式事務(wù),是怎么從 ACID 解脫,投身 CAP/BASE
如果說到事務(wù),ACID 是傳統(tǒng)數(shù)據(jù)庫常用的設(shè)計理念,追求強一致性模型,關(guān)系數(shù)據(jù)庫的 ACID 模型擁有高一致性+可用性,所以很難進(jìn)行分區(qū)。
在微服務(wù)中 ACID 已經(jīng)是無法支持,我們還是回到 CAP 去尋求解決方案,不過根據(jù)上面的討論,CAP 定理中,要么只能 CP,要么只能 AP。
如果我們追求數(shù)據(jù)的一致性而忽略可用性這個在微服務(wù)中肯定是行不通的,如果我們追求可用性而忽略一致性,那么在一些重要的數(shù)據(jù)(例如支付,金額)肯定出現(xiàn)漏洞百出,這個也是無法接受。所以我們既要一致性,也要可用性。
都要是無法實現(xiàn)的,但我們能不能在一致性上作出一些妥協(xié),不追求強一致性,轉(zhuǎn)而追求最終一致性,所以引入 BASE 理論。
在分布式事務(wù)中,BASE 最重要是為 CAP 提出了最終一致性的解決方案,BASE 強調(diào)犧牲高一致性,從而獲取可用性,數(shù)據(jù)允許在一段時間內(nèi)不一致,只要保證最終一致性就可以了。
實現(xiàn)最終一致性
弱一致性:系統(tǒng)不能保證后續(xù)訪問返回更新的值。需要在一些條件滿足之后,更新的值才能返回。
從更新操作開始,到系統(tǒng)保證任何觀察者總是看到更新的值的這期間被稱為不一致窗口。
最終一致性:這是弱一致性的特殊形式;存儲系統(tǒng)保證如果沒有對某個對象的新更新操作,最終所有的訪問將返回這個對象的最后更新的值。
BASE 模型
BASE 模型是傳統(tǒng) ACID 模型的反面,不同于 ACID,BASE 強調(diào)犧牲高一致性,從而獲得可用性,數(shù)據(jù)允許在一段時間內(nèi)的不一致,只要保證最終一致就可以了。
BASE 模型反 ACID 模型,完全不同 ACID 模型,犧牲高一致性,獲得可用性或可靠性:Basically Available 基本可用。
支持分區(qū)失敗(e.g. sharding碎片劃分?jǐn)?shù)據(jù)庫)Soft state 軟狀態(tài),狀態(tài)可以有一段時間不同步,異步。
Eventually consistent 最終一致,最終數(shù)據(jù)是一致的就可以了,而不是時時一致。
分布式事務(wù)
在分布式系統(tǒng)中,要實現(xiàn)分布式事務(wù),無外乎幾種解決方案。方案各有不同,不過其實都是遵循 BASE 理論,是最終一致性模型:
- 兩階段提交(2PC)
- 補償事務(wù)(TCC)
- 本地消息表
- MQ 事務(wù)消息
①兩階段提交(2PC)
還有一個數(shù)據(jù)庫的 XA 事務(wù),不過目前在真正的互聯(lián)網(wǎng)中實際的應(yīng)用基本很少,兩階段提交就是使用 XA 原理。
在 XA 協(xié)議中分為兩階段:
- 事務(wù)管理器要求每個涉及到事務(wù)的數(shù)據(jù)庫預(yù)提交(Precommit)此操作,并反映是否可以提交。
- 事務(wù)協(xié)調(diào)器要求每個數(shù)據(jù)庫提交數(shù)據(jù),或者回滾數(shù)據(jù)。
說一下,為何在互聯(lián)網(wǎng)的系統(tǒng)中沒被改造過的兩階段提交基本很少被業(yè)界應(yīng)用,最大的缺點就是同步阻塞問題。
在資源準(zhǔn)備就緒之后,資源管理器中的資源就一直處于阻塞,直到提交完成之后,才進(jìn)行資源釋放。
這個在互聯(lián)網(wǎng)高并發(fā)大數(shù)據(jù)的今天,兩階段的提交是不能滿足現(xiàn)在互聯(lián)網(wǎng)的發(fā)展。
還有就是兩階段提交協(xié)議雖然為分布式數(shù)據(jù)強一致性所設(shè)計,但仍然存在數(shù)據(jù)不一致性的可能。
例如:在第二階段中,假設(shè)協(xié)調(diào)者發(fā)出了事務(wù) Commit 的通知,但是因為網(wǎng)絡(luò)問題該通知僅被一部分參與者所收到并執(zhí)行了 Commit 操作,其余的參與者則因為沒有收到通知一直處于阻塞狀態(tài),這時候就產(chǎn)生了數(shù)據(jù)的不一致性。
②補償事務(wù)(TCC)
TCC 是服務(wù)化的兩階段編程模型,每個業(yè)務(wù)服務(wù)都必須實現(xiàn) Try,Confirm,Cancel 三個方法,這三個方式可以對應(yīng)到 SQL 事務(wù)中 Lock,Commit,Rollback。
相比兩階段提交,TCC 解決了幾個問題:同步阻塞,引入了超時機制,超時后進(jìn)行補償,并不會像兩階段提交鎖定了整個資源,將資源轉(zhuǎn)換為業(yè)務(wù)邏輯形式,粒度變小。
因為有了補償機制,可以由業(yè)務(wù)活動管理器進(jìn)行控制,保證數(shù)據(jù)一致性。
Try 階段:Try 只是一個初步的操作,進(jìn)行初步的確認(rèn),它的主要職責(zé)是完成所有業(yè)務(wù)的檢查,預(yù)留業(yè)務(wù)資源。
Confirm 階段:Confirm 是在 Try 階段檢查執(zhí)行完畢后,繼續(xù)執(zhí)行的確認(rèn)操作,必須滿足冪等性操作,如果 Confirm 中執(zhí)行失敗,會有事務(wù)協(xié)調(diào)器觸發(fā)不斷的執(zhí)行,直到滿足為止。
Cancel 是取消執(zhí)行:在 Try 沒通過并釋放掉 Try 階段預(yù)留的資源,也必須滿足冪等性,跟 Confirm 一樣有可能被不斷執(zhí)行。
一個下訂單,生成訂單扣庫存的例子:
接下來看看,我們的下單扣減庫存的流程怎么加入 TCC:
在 Try 的時候,會讓庫存服務(wù)預(yù)留 N 個庫存給這個訂單使用,讓訂單服務(wù)產(chǎn)生一個“未確認(rèn)”訂單,同時產(chǎn)生這兩個預(yù)留的資源。
在 Confirm 的時候,會使用在 Try 預(yù)留的資源,在 TCC 事務(wù)機制中認(rèn)為,如果在 Try 階段能正常預(yù)留的資源,那么在 Confirm 一定能完整的提交。
在 Try 的時候,有任務(wù)一方為執(zhí)行失敗,則會執(zhí)行 Cancel 的接口操作,將在 Try 階段預(yù)留的資源進(jìn)行釋放。
這個并不是重點要論 TCC 事務(wù)是怎么實現(xiàn),重點還是討論分布式事務(wù)在 CAP+BASE 理論的應(yīng)用。
實現(xiàn)可以參考:
https://github.com/changmingxie/tcc-transaction
③本地消息表
本地消息表這個方案最初是 eBay 提出的,eBay 的完整方案:
https://queue.acm.org/detail.cfm?id=1394128
本地消息表這種實現(xiàn)方式應(yīng)該是業(yè)界使用最多的,其核心思想是將分布式事務(wù)拆分成本地事務(wù)進(jìn)行處理。
對于本地消息隊列來說,核心就是將大事務(wù)轉(zhuǎn)變?yōu)樾∈聞?wù),還是用上面下訂單扣庫存的例子說明:
- 當(dāng)我們?nèi)?chuàng)建訂單的時候,我們新增一個本地消息表,把創(chuàng)建訂單和扣減庫存寫入到本地消息表,放在同一個事務(wù)(依靠數(shù)據(jù)庫本地事務(wù)保證一致性)。
- 配置一個定時任務(wù)去輪詢這個本地事務(wù)表,掃描這個本地事務(wù)表,把沒有發(fā)送出去的消息,發(fā)送給庫存服務(wù),當(dāng)庫存服務(wù)收到消息后,會進(jìn)行減庫存,并寫入服務(wù)器的事務(wù)表,更新事務(wù)表的狀態(tài)。
- 庫存服務(wù)器通過定時任務(wù)或直接通知訂單服務(wù),訂單服務(wù)在本地消息表更新狀態(tài)。
這里須注意的是,對于一些掃描發(fā)送未成功的任務(wù),會進(jìn)行重新發(fā)送,所以必須保證接口的冪等性。
本地消息隊列是 BASE 理論,是最終一致性模型,適用對一致性要求不高的情況。
④MQ 事務(wù)
RocketMQ 在 4.3 版本已經(jīng)正式宣布支持分布式事務(wù),在選擇 RokcetMQ 做分布式事務(wù)請務(wù)必選擇 4.3 以上的版本。
RocketMQ 中實現(xiàn)了分布式事務(wù),實際上是對本地消息表的一個封裝,將本地消息表移動到了 MQ 內(nèi)部。
事務(wù)消息作為一種異步確保型事務(wù), 將兩個事務(wù)分支通過 MQ 進(jìn)行異步解耦,RocketMQ 事務(wù)消息的設(shè)計流程同樣借鑒了兩階段提交理論。
整體交互流程如下圖所示:
MQ 事務(wù)是對本地消息表的一層封裝,將本地消息表移動到了 MQ 內(nèi)部,所以也是基于 BASE 理論,是最終一致性模式,對強一致性要求不那么高的事務(wù)適用,同時 MQ 事務(wù)將整個流程異步化了,也非常適合在高并發(fā)情況下使用。
RocketMQ 選擇同步/異步刷盤,同步/異步復(fù)制,背后的 CP 和 AP 思考
雖然同步刷盤/異步刷盤,同步/異步復(fù)制,并沒有對 CAP 直接的應(yīng)用,但在配置的過程中也一樣涉及到可用性和一致性的考慮。
同步刷盤/異步刷盤
RocketMQ 的消息是可以做到持久化的,數(shù)據(jù)會持久化到磁盤,RocketMQ 為了提高性能,盡可能保證磁盤的順序?qū)懭搿?/p>
消息在 Producer 寫入 RocketMQ 的時候,有兩種寫入磁盤方式:
- 異步刷盤:消息快速寫入到內(nèi)存的 Pagecache,就立馬返回寫成功狀態(tài),當(dāng)內(nèi)存的消息累計到一定程度的時候,會觸發(fā)統(tǒng)一的寫磁盤操作。這種方式可以保證大吞吐量,但也存在著消息可能未存入磁盤丟失的風(fēng)險。
- 同步刷盤:消息快速寫入內(nèi)存的 Pagecahe,立刻通知刷盤線程進(jìn)行刷盤,等待刷盤完成之后,喚醒等待的線程,返回消息寫成功的狀態(tài)。
同步復(fù)制/異步復(fù)制
一個 Broker 組有 Master 和 Slave,消息需要從 Master 復(fù)制到 Slave 上,所以有同步和異步兩種復(fù)制方式:
- 同步復(fù)制:是等 Master 和 Slave 均寫成功后才反饋給客戶端寫成功狀態(tài)。
- 異步復(fù)制:是只要 Master 寫成功即可反饋給客戶端寫成功狀態(tài)。
異步復(fù)制的優(yōu)點是可以提高響應(yīng)速度,但犧牲了一致性 ,一般實現(xiàn)該類協(xié)議的算法需要增加額外的補償機制。
同步復(fù)制的優(yōu)點是可以保證一致性(一般通過兩階段提交協(xié)議),但是開銷較大,可用性不好(參見 CAP 定理),帶來了更多的沖突和死鎖等問題。
值得一提的是 Lazy+Primary/Copy 的復(fù)制協(xié)議在實際生產(chǎn)環(huán)境中是非常實用的。
RocketMQ 的設(shè)置要結(jié)合業(yè)務(wù)場景,合理設(shè)置刷盤方式和主從復(fù)制方式,尤其是 SYNC_FLUSH 方式,由于頻繁的觸發(fā)寫磁盤動作,會明顯降低性能。
通常情況下,應(yīng)該把 Master 和 Slave 設(shè)置成 ASYNC_FLUSH 的刷盤方式,主從之間配置成 SYNC_MASTER 的復(fù)制方式,這樣即使有一臺機器出故障,仍然可以保證數(shù)據(jù)不丟。
總結(jié)
在微服務(wù)的構(gòu)建中,永遠(yuǎn)都逃離不了 CAP 理論,因為網(wǎng)絡(luò)永遠(yuǎn)不穩(wěn)定,硬件總會老化,軟件可能出現(xiàn) Bug,所以分區(qū)容錯性在微服務(wù)中是躲不過的命題。
可以這么說,只要是分布式,只要是集群都面臨著 AP 或者 CP 的選擇,但你很貪心的時候,既要一致性又要可用性,那只能對一致性作出一點妥協(xié),也就是引入了 BASE 理論,在業(yè)務(wù)允許的情況下實現(xiàn)最終一致性。
究竟是選 CA 還是選 CP,真的在于對業(yè)務(wù)的了解,例如金錢,庫存相關(guān)會優(yōu)先考慮 CP 模型,例如社區(qū)發(fā)帖相關(guān)可以優(yōu)先選擇 AP 模型,這個說白了基于對業(yè)務(wù)的了解是一個選擇和妥協(xié)的過程。
作者:陳于喆
簡介:十余年的開發(fā)和架構(gòu)經(jīng)驗,國內(nèi)較早一批微服務(wù)開發(fā)實施者。曾任職國內(nèi)互聯(lián)網(wǎng)公司網(wǎng)易和唯品會高級研發(fā)工程師,后在創(chuàng)業(yè)公司擔(dān)任技術(shù)總監(jiān)/架構(gòu)師,目前在洋蔥集團(tuán)任職技術(shù)研發(fā)副總監(jiān)。
【51CTO原創(chuàng)稿件,合作站點轉(zhuǎn)載請注明原文作者和出處為51CTO.com】






