一致性是一個抽象的、具備多重含義的計算機術語,在不一樣應用場景下,有不一樣的定義和含義。在傳統的IT時代,一致性一般指強一致性,強一致性一般體如今你中有我、我中有你、渾然一體;而在互聯網時代,一致性的含義遠遠超出了它原有的含義,在咱們討論互聯網時代的一致性以前,咱們先了解一下互聯網時代的特色,互聯網時代信息量巨大、須要計算能力巨大,不但對用戶響應速度要求快,並且吞吐量指標也要向外擴展(既:水平伸縮),因而單節點的服務器沒法知足需求,服務節點開始池化,想一想那個經典的故事,一隻筷子一折就斷,一把筷子怎麼都折不斷,可見人多力量大的思想是多麼的重要,可是人多也不必定能解決全部事情,還得進行有序、合理的分配任務,進行有效的管理,因而互聯網時代談論最多的話題就是拆分,拆分通常分爲「水平拆分」和「垂直拆分」(你們不要對應到數據庫或者緩存拆分,這裏主要表達一種邏輯)。這裏,「水平拆分」指的是同一個功能因爲單機節點沒法知足性能需求,須要擴展成爲多節點,多個節點具備一致的功能,組成一個服務池,一個節點服務一部分的請求量,團結起來共同處理大規模高併發的請求量。「垂直拆分」指的是按照功能拆分,秉着「專業的人幹專業的事兒」的原則,把一個複雜的功能拆分到多個單一的簡單的元功能,不一樣的元功能組合在一塊兒,和未拆分前完成的功能是一致的,因爲每一個元功能職責單1、功能簡單,讓維護和變動都變得更簡單、安全,更易於產品版本的迭代,在這樣的一個互聯網的時代和環境,一致性指分佈式服務化系統之間的弱一致性,包括應用系統一致性和數據一致性。html
不管是水平拆分仍是垂直拆分,都解決了特定場景下的特定問題,凡事有好的一面,都會有壞的一面,拆分後的系統或者服務化的系統最大的問題就是一致性問題,這麼多個具備元功能的模塊,或者同一個功能池中的多個節點之間,如何保證他們的信息是一致的、工做步伐是一致的、狀態是一致的、互相協調有序的工做呢?git
本節列舉不一致會致使的種種問題,這也包括一例生活中的問題。github
假如你想要享受生活的隨意,只想買個兩居,不想讓房貸有太大壓力,而你媳婦卻想要買個三居,還得帶花園的,那麼大家就不一致了,不一致致使生活不愉快、不協調,嚴重狀況下還會吵架,可見生活中的不一致問題影響很大。web
轉帳是經典的不一致案例,設想一下銀行爲你處理一筆轉帳,扣減你帳戶上的餘額,而後增長別人帳戶的餘額;若是扣減你的帳戶餘額成功,增長別人帳戶餘額失敗,那麼你就會損失這筆資金。反過來,若是扣減你的帳戶餘額失敗,增長別人帳戶餘額成功,那麼銀行就會損失這筆資金,銀行須要賠付。對於資金處理系統來講,上面任何一種場景都是不容許發生的,一旦發生就會有資金損失,後果是不堪設想的,嚴重狀況會讓一個公司瞬間倒閉,可參考案例。算法
電商系統中也有一個經典的案例,下訂單和扣庫存如何保持一致,若是先下訂單,扣庫存失敗,那麼將會致使超賣;若是下訂單沒有成功,扣庫存成功,那麼會致使少賣。兩種狀況都會致使運營成本的增長,嚴重狀況下須要賠付。sql
服務化的系統間調用經常由於網絡問題致使系統間調用超時,即便是網絡很好的機房,在億次流量的基數下,同步調用超時也是屢見不鮮。系統A同步調用系統B超時,系統A能夠明確獲得超時反饋,可是沒法肯定系統B是否已經完成了預約的功能或者沒有完成預約的功能。因而,系統A就迷茫了,不知道應該繼續作什麼,如何反饋給使用方。(曾經的一個B2B產品的客戶要求接口超時從新通知他們,這個在技術上是難以實現的,由於服務器自己可能並不知道本身超時,可能會繼續正常的返回數據,只是客戶端並無接受到結果罷了,所以這不是一個合理的解決方案)。數據庫
此案例和上一個同步超時案例相似,不過這個場景使用了異步回調,系統A同步調用系統B發起指令,系統B採用受理模式,受理後則返回受理成功,而後系統B異步通知系統A。在這個過程當中,若是系統A因爲某種緣由遲遲沒有收到回調結果,那麼兩個系統間的狀態就不一致,互相認知不一樣會致使系統間發生錯誤,嚴重狀況下會影響核心事務,甚至會致使資金損失。緩存
分佈式系統中,兩個系統協做處理一個流程,分別爲對方的上下游,若是一個系統中存在一個請求,一般指訂單,另一個系統不存在,則致使掉單,掉單的後果很嚴重,有時候也會致使資金損失。安全
這個案例與上面掉單案例相似,不一樣的是兩個系統間都存在請求,可是請求的狀態不一致。服務器
交易相關係統基本離不開關係型數據庫,依賴關係型數據庫提供的ACID特性(後面介紹),可是在大規模高併發的互聯網系統裏,一些特殊的場景對讀的性能要求極高,服務於交易的數據庫難以抗住大規模的讀流量,一般須要在數據庫前墊緩存,那麼緩存和數據庫之間的數據如何保持一致性?是要保持強一致呢仍是弱一致性呢?
一個服務池上的多個節點爲了知足較高的性能需求,須要使用本地緩存,使用了本地緩存,每一個節點都會有一份緩存數據的拷貝,若是這些數據是靜態的、不變的,那永遠都不會有問題,可是若是這些數據是半靜態的或者常被更新的,當被更新的時候,各個節點更新是有前後順序的,在更新的瞬間,各個節點的數據是不一致的,若是這些數據是爲某一個開關服務的,想象一下重複的請求走進了不一樣的節點(在failover或者補償致使的場景下,重複請求是必定會發生的,也是服務化系統必須處理的),一個請求走了開關打開的邏輯,同時另一個請求走了開關關閉的邏輯,這致使請求被處理兩次,最壞的狀況下會致使災難性的後果,就是資金損失。
這個案例會時有發生,某系統須要種某一數據結構的緩存,這一數據結構有多個數據元素組成,其中,某個數據元素都須要從數據庫中或者服務中獲取,若是一部分數據元素獲取失敗,因爲程序處理不正確,仍然將不徹底的數據結構存入緩存,那麼緩存的消費者消費的時候頗有可能由於沒有合理處理異常狀況而出錯。
你們回顧一下上一節列舉的生活中的案例1-買房,若是置身事外來看,解決這種不一致的辦法有兩個,一個是避免不一致的發生,若是已是媳婦了就很差辦了:),還有一種方法就是慢慢的補償,先買個兩居,而後慢慢的等資金充裕了再換三居,買比特幣賺了再換帶花園的房子,因而問題最終被解決了,最終你們處於一致的狀態,都開心了。這樣能夠解決案例1的問題,很天然因爲有了過渡的方法,問題在不經意間就消失了,可見「過渡」也是解決一致性問題的一個模式。
從案例1的解決方案來看,咱們要解決一致性問題,一個最直接最簡單的方法就是保持強一致性,對於案例1的狀況,儘可能避免在結婚前兩我的可以互相瞭解達成一致,避免不一致問題的發生;不過有些事情事已至此,發生了就是發生了,出現了不一致的問題,咱們應該考慮去補償,盡最大的努力從不一致狀態修復到一致狀態,避免損失所有或者一部分,也不失爲一個好方法。
所以,避免不一致是上策,出現了不一致及時發現及時修復是中策,有問題不積極解決留給他人解決是下策。
ACID在英文中的意思是「酸」,BASE的意識是「鹼」,這一段講的是「酸鹼平衡」的故事。
如何保證強一致性呢?計算機專業的童鞋在學習關係型數據庫的時候都學習了ACID原理,這裏對ACID作個簡單的介紹。若是想全面的學習ACID原理,請參考ACID。
關係型數據庫天生就是解決具備復瑣事務場景的問題,關係型數據庫徹底知足ACID的特性。
ACID指的是:
A: Atomicity,原子性
C: Consistency,一致性
I: Isolation,隔離性
D: Durability,持久性
具備ACID的特性的數據庫支持強一致性,強一致性表明數據庫自己不會出現不一致,每一個事務是原子的,或者成功或者失敗,事物間是隔離的,互相徹底不影響,並且最終狀態是持久落盤的,所以,數據庫會從一個明確的狀態到另一個明確的狀態,中間的臨時狀態是不會出現的,若是出現也會及時的自動的修復,所以是強一致的。
3個典型的關係型數據庫Oracle、Mysql、Db2都能保證強一致性,Oracle和Mysql使用多版本控制協議實現,而DB2使用改進的兩階段提交協議來實現。
若是你在爲交易相關係統作技術選型,交易的存儲應該只考慮關係型數據庫,對於核心系統,若是須要較好的性能,能夠考慮使用更強悍的硬件,這種向上擴展(升級硬件)雖然成本較高,可是是最簡單粗暴有效的方式,另外,Nosql徹底不適合交易場景,Nosql主要用來作數據分析、ETL、報表、數據挖掘、推薦、日誌處理等非交易場景。
前面提到的案例2-轉帳和案例3-下訂單和扣庫存均可以利用關係型數據庫的強一致性解決。
然而,前面提到,互聯網項目多數具備大規模高併發的特性,必須應用拆分的理念,對高併發的壓力採起「大而化小、小而化了」的方法,不然難以知足動輒億級流量的需求,即便使用關係型數據庫,單機也難以知足存儲和TPS上的需求。爲了保證案例2-轉帳能夠利用關係型數據庫的強一致性,在拆分的時候儘可能的把轉帳相關的帳戶放入一個數據庫分片,對於案例3,儘可能的保證把訂單和庫存放入同一個數據庫分片,這樣經過關係型數據庫天然就解決了不一致的問題。
然而,有些時候事與願違,因爲業務規則的限制,沒法將相關的數據分到同一個數據庫分片,這個時候咱們就須要實現最終一致性。
對於案例2-轉帳場景,假設帳戶數量巨大,對帳戶存儲進行了拆分,關係型數據庫一共分了8個實例,每一個實例8個庫,每一個庫8個表,共512張表,假如要轉帳的兩個帳戶正好落在了一個庫裏,那麼能夠依賴關係型數據庫的事務保持強一致性。
若是要轉帳的兩個帳戶正好落在了不一樣的庫裏,轉帳操做是沒法封裝在同一個數據庫事務中的,這個時候會發生一個庫的帳戶扣減餘額成功,另一個庫的帳戶增長餘額失敗的狀況。
對於這種狀況,咱們須要繼續探討解決之道,CAP原理和BASE原理,BASE原理經過記錄事務的中間的臨時狀態,實現最終一致性。
若是想深刻的學習CAP理論,請參考CAP。
因爲對系統或者數據進行了拆分,咱們的系統再也不是單機系統,而是分佈式系統,針對分佈式系的帽子理論包含三個元素:
C:Consistency,一致性, 數據一致更新,全部數據變更都是同步的
A:Availability,可用性, 好的響應性能,徹底的可用性指的是在任何故障模型下,服務都會在有限的時間處理響應
P:Partition tolerance,分區容錯性,可靠性
帽子理論證實,任何分佈式系統只可同時知足二點,無法三者兼顧。關係型數據庫因爲關係型數據庫是單節點的,所以,不具備分區容錯性,可是具備一致性和可用性,而分佈式的服務化系統都須要知足分區容錯性,那麼咱們必須在一致性和可用性中進行權衡,具體表如今服務化系統處理的異常請求在某一個時間段內多是不徹底的,可是通過自動的或者手工的補償後,達到了最終的一致性。
BASE理論解決CAP理論提出了分佈式系統的一致性和可用性不能兼得的問題,若是想全面的學習BASE原理,請參考Eventual consistency。
BASE在英文中有「鹼」的意思,對應本節開頭的ACID在英文中「酸」的意思,基於這兩個名詞提出了酸鹼平衡的結論,簡單來講是在不一樣的場景下,能夠分別利用ACID和BASE來解決分佈式服務化系統的一致性問題。
BASE模型與ACID模型大相徑庭,知足CAP理論,經過犧牲強一致性,得到可用性,通常應用在服務化系統的應用層或者大數據處理系統,經過達到最終一致性來儘可能知足業務的絕大部分需求。
BASE模型包含個三個元素:
BA:Basically Available,基本可用
S:Soft State,軟狀態,狀態能夠有一段時間不一樣步
E:Eventually Consistent,最終一致,最終數據是一致的就能夠了,而不是時時保持強一致
BASE模型的軟狀態是實現BASE理論的方法,基本可用和最終一致是目標。按照BASE模型實現的系統,因爲不保證強一致性,系統在處理請求的過程當中,能夠存在短暫的不一致,在短暫的不一致窗口請求處理處在臨時狀態中,系統在作每步操做的時候,經過記錄每個臨時狀態,在系統出現故障的時候,能夠從這些中間狀態繼續未完成的請求處理或者退回到原始狀態,最後達到一致的狀態。
以案例1-轉帳爲例,咱們把用戶A給用戶B轉帳分紅四個階段,第一個階段用戶A準備轉帳,第二個階段從用戶A帳戶扣減餘額,第三個階段對用戶B增長餘額,第四個階段完成轉帳。系統須要記錄操做過程當中每一步驟的狀態,一旦系統出現故障,系統可以自動發現沒有完成的任務,而後,根據任務所處的狀態,繼續執行任務,最終完成任務,達到一致的最終狀態。
在實際應用中,上面這個過程一般是經過持久化執行任務的狀態和環境信息,一旦出現問題,定時任務會撈取未執行完的任務,繼續未執行完的任務,直到執行完成爲止,或者取消已經完成的部分操做回到原始狀態。這種方法在任務完成每一個階段的時候,都要更新數據庫中任務的狀態,這在大規模高併發系統中不會有太好的性能,一個更好的辦法是用Write-Ahead Log(寫前日誌),這和數據庫的Bin Log(操做日誌)類似,在作每個操做步驟,都先寫入日誌,若是操做遇到問題而中止的時候,能夠讀取日誌按照步驟進行恢復,而且繼續執行未完成的工做,最後達到一致。寫前日誌能夠利用機械硬盤的追加寫而達到較好性能,所以,這是一種專業化的實現方式,多數業務繫系統仍是使用數據庫記錄的字段來記錄任務的執行狀態,也就是記錄中間的「軟狀態」,一個任務的狀態流轉通常能夠經過數據庫的行級鎖來實現,這比使用Write-Ahead Log實現更簡單、更快速。
有了BASE理論做爲基礎,咱們對複雜的分佈式事務進行拆解,對其中的每一步驟都記錄其狀態,有問題的時候能夠根據記錄的狀態來繼續執行任務,達到最終的一致,經過這個方法咱們能夠解決案例2-轉帳和案例3-下訂單和扣庫存中遇到的問題。
國際開放標準組織Open Group定義了DTS(分佈式事務處理模型),模型中包含4個角色:應用程序、事務管理器、資源管理器、通訊資源管理器四部分。事務處理器是統管全局的管理者,資源處理器和通訊資源處理器是事務的參與者。
J2EE規範也包含此分佈式事務處理模型的規範,並在全部的AppServer中進行實現,J2EE規範中定義了TX協議和XA協議,TX協議定義應用程序與事務管理器之間的接口,而XA協議定義了事務管理器與資源處理器之間的接口,在過去,你們使用AppServer,例如:Websphere、Weblogic、Jboss等配置數據源的時候會看見相似XADatasource的數據源,這就是實現了DTS的關係型數據庫的數據源。企業級開發JEE中,關係型數據庫、JMS服務扮演資源管理器的角色,而EJB容器則扮演事務管理器的角色。
下面咱們就介紹兩階段提交協議、三階段提交協議以及阿里巴巴提出的TCC,它們都是根據DTS這一思想演變出來的。
上面描述的JEE的XA協議就是根據兩階段提交來保證事務的完整性,並實現分佈式服務化的強一致性。
兩階段提交協議把分佈式事務分紅兩個過程,一個是準備階段,一個是提交階段,準備階段和提交階段都是由事務管理器發起的,爲了接下來說解方便,咱們把事務管理器稱爲協調者,把資管管理器稱爲參與者。
兩階段以下:
兩階段提交協議成功場景示意圖以下:
兩階段提交協議
咱們看到兩階段提交協議在準備階段鎖定資源,是一個重量級的操做,並能保證強一致性,可是實現起來複雜、成本較高,不夠靈活,更重要的是它有以下致命的問題:
上面全部的這些問題,都是須要人工干預處理,沒有自動化的解決方案,所以兩階段提交協議在正常狀況下能保證系統的強一致性,可是在出現異常狀況下,當前處理的操做處於錯誤狀態,須要管理員人工干預解決,所以可用性不夠好,這也符合CAP協議的一致性和可用性不能兼得的原理。
三階段提交協議是兩階段提交協議的改進版本。它經過超時機制解決了阻塞的問題,而且把兩個階段增長爲三個階段:
三階段提交協議成功場景示意圖以下:
三階段提交協議
然而,這裏與兩階段提交協議有兩個主要的不一樣:
三階段提交協議與兩階段提交協議相比,具備如上的優勢,可是一旦發生超時,系統仍然會發生不一致,只不過這種狀況不多見罷了,好處就是至少不會阻塞和永遠鎖定資源。
上面兩節講解了兩階段提交協議和三階段提交協議,實際上他們能解決案例2-轉帳和案例3-下訂單和扣庫存中的分佈式事務的問題,可是遇到極端狀況,系統會發生阻塞或者不一致的問題,須要運營或者技術人工解決。不管兩階段仍是三階段方案中都包含多個參與者、多個階段實現一個事務,實現複雜,性能也是一個很大的問題,所以,在互聯網高併發系統中,鮮有使用兩階段提交和三階段提交協議的場景。
阿里巴巴提出了新的TCC協議,TCC協議將一個任務拆分紅Try、Confirm、Cancel,正常的流程會先執行Try,若是執行沒有問題,再執行Confirm,若是執行過程當中出了問題,則執行操做的逆操Cancel,從正常的流程上講,這仍然是一個兩階段的提交協議,可是,在執行出現問題的時候,有必定的自我修復能力,若是任何一個參與者出現了問題,協調者經過執行操做的逆操做來取消以前的操做,達到最終的一致狀態。
能夠看出,從時序上,若是遇到極端狀況下TCC會有不少問題的,例如,若是在Cancel的時候一些參與者收到指令,而一些參與者沒有收到指令,整個系統仍然是不一致的,這種複雜的狀況,系統首先會經過補償的方式,嘗試自動修復的,若是系統沒法修復,必須由人工參與解決。
從TCC的邏輯上看,能夠說TCC是簡化版的三階段提交協議,解決了兩階段提交協議的阻塞問題,可是沒有解決極端狀況下會出現不一致和腦裂的問題。然而,TCC經過自動化補償手段,會把須要人工處理的不一致狀況降到到最少,也是一種很是有用的解決方案,根據線人,阿里在內部的一些中間件上實現了TCC模式。
咱們給出一個使用TCC的實際案例,在秒殺的場景,用戶發起下單請求,應用層先查詢庫存,確認商品庫存還有餘量,則鎖定庫存,此時訂單狀態爲待支付,而後指引用戶去支付,因爲某種緣由用戶支付失敗,或者支付超時,系統會自動將鎖定的庫存解鎖供其餘用戶秒殺。
TCC協議使用場景示意圖以下:
TCC
總結一下,兩階段提交協議、三階段提交協議、TCC協議都能保證分佈式事務的一致性,他們保證的分佈式系統的一致性從強到弱,TCC達到的目標是最終一致性,其中任何一種方法均可以不一樣程度的解決案例2:轉帳、案例3:下訂單和扣庫存的問題,只是實現的一致性的級別不同而已,對於案例4:同步超時能夠經過TCC的理念解決,若是同步調用超時,調用方可使用fastfail策略,返回調用方的使用方失敗的結果,同時調用服務的逆向cancel操做,保證服務的最終一致性。
在大規模高併發服務化系統中,一個功能被拆分紅多個具備單一功能的元功能,一個流程會有多個系統的多個元功能組合實現,若是使用兩階段提交協議和三階段提交協議,確實能解決系統間一致性問題,除了這兩個協議帶來的自身的問題,這些協議的實現比較複雜、成本比較高,最重要的是性能並很差,相比來看,TCC協議更簡單、容易實現,可是TCC協議因爲每一個事務都須要執行Try,再執行Confirm,略微顯得臃腫,所以,在現實的系統中,底線要求僅僅須要能達到最終一致性,而不須要實現專業的、複雜的一致性協議,實現最終一致性有一些很是有效的、簡單粗暴的模式,下面就介紹這些模式及其應用場景。
任何一個服務操做都須要提供一個查詢接口,用來向外部輸出操做執行的狀態。服務操做的使用方能夠經過查詢接口,得知服務操做執行的狀態,而後根據不一樣狀態來作不一樣的處理操做。
爲了可以實現查詢,每一個服務操做都須要有惟一的流水號標識,也可以使用這次服務操做對應的資源ID來標誌,例如:請求流水號、訂單號等。
首先,單筆查詢操做是必須提供的,咱們也鼓勵使用單筆訂單查詢,這是由於每次調用須要佔用的負載是可控的,批量查詢則根據須要來提供,若是使用了批量查詢,須要有合理的分頁機制,而且必須限制分頁的大小,以及對批量查詢的QPS須要有容量評估和流控等。
查詢模式的示意圖以下:
查詢模式
對於案例4:同步超時、案例5:異步回調超時、案例6:掉單、案例7:系統間狀態不一致,咱們都須要使用查詢模式來了解被調用服務的處理狀況,來決定下一步作什麼:補償未完成的操做仍是回滾已經完成的操做。
有了上面的查詢模式,在任何狀況下,咱們都能得知具體的操做所處的狀態,若是整個操做處於不正常的狀態,咱們須要修正操做中有問題的子操做,這可能須要從新執行未完成的子操做,後者取消已經完成的子操做,經過修復使整個分佈式系統達到一致,爲了讓系統最終一致而作的努力都叫作補償。
對於服務化系統中同步調用的操做,業務操做發起的主動方在尚未獲得業務操做執行方的明確返回或者調用超時,場景可參考案例4:同步超時,這個時候業務發起的主動方須要及時的調用業務執行方得到操做執行的狀態,這裏使用查詢模式,得到業務操做的執行方的狀態後,若是業務執行方已經完預設的工做,則業務發起方給業務的使用方返回成功,若是業務操做的執行方的狀態爲失敗或者未知,則會當即告訴業務的使用方失敗,而後調用業務操做的逆向操做,保證操做不被執行或者回滾已經執行的操做,讓業務的使用方、業務發起的主動方、業務的操做方最終達成一致的狀態。
補償模式的示意圖以下:
補償模式
補償操做根據發起形式分爲:
異步確保模式是補償模式的一個典型案例,常常應用到使用方對響應時間要求並不過高,咱們一般把這類操做從主流程中摘除,經過異步的方式進行處理,處理後把結果經過通知系統通知給使用方,這個方案最大的好處可以對高併發流量進行消峯,例如:電商系統中的物流、配送,以及支付系統中的計費、入帳等。
實踐中,將要執行的異步操做封裝後持久入庫,而後經過定時撈取未完成的任務進行補償操做來實現異步確保模式,只要定時系統足夠健壯,任何一個任務最終會被成功執行。
異步確保模式的示意圖以下:
異步確保模式
對於案例5:異步回調超時,使用的就是異步確保模式,這種狀況下對於某個操做,若是遲遲沒有收到響應,咱們經過查詢模式和補償模式來繼續未完成的操做。
既然咱們在系統中實現最終一致性,系統在沒有達到一致以前,系統間的狀態是不一致的,甚至是混亂的,須要補償操做來達到一致的目的,可是咱們如何來發現須要補償的操做呢?
在操做的主流程中的系統間執行校對操做,咱們能夠過後異步的批量校對操做的狀態,若是發現不一致的操做,則進行補償,補償操做與補償模式中的補償操做是一致的。
另外,實現按期校對的一個關鍵就是分佈式系統中須要有一個自始至終惟一的ID,ID的生成請參考SnowFlake。
在分佈式系統中,全局惟一ID的示意圖以下:
惟一ID
通常狀況下,生成全局惟一ID有兩種方法:
實踐中,爲了能在分佈式系統中迅速的定位問題,通常的分佈式系統都有技術支持系統,它可以跟蹤一個請求的調用鏈,調用鏈是在二維的維度跟蹤一個調用請求,最後造成一個調用樹,原理可參考谷歌的論文Dapper, a Large-Scale Distributed Systems Tracing Infrastructure,一個開源的參考實現爲pinpoint。
在分佈式系統中,調用鏈的示意圖以下:
調用鏈
全局的惟一流水ID能夠把一個請求在分佈式系統中的流轉的路徑聚合,而調用鏈中的spanid能夠把聚合的請求路徑經過樹形結構進行展現,讓技術支持人員輕鬆的發現系統出現的問題,可以快速定位出現問題的服務節點,提升應急效率。
關於訂單跟蹤、調用鏈跟蹤、業務鏈跟蹤,咱們會在後續文章中詳細介紹。
在分佈式系統中構建了惟一ID,調用鏈等基礎設施,咱們很容易對系統間的不一致進行覈對,一般咱們須要構建第三方的按期覈對系統,以第三方的角度來監控服務執行的健康程度。
按期覈對系統示意圖以下:
按期覈對模式
對於案例6:掉單、案例7:系統間狀態不一致一般經過按期校對模式發現問題,並經過補償模式來修復,最後完成系統間的最終一致性。
按期校對模式多應用在金融系統,金融系統因爲涉及到資金安全,須要保證百分之百的準確性,因此,須要多重的一致性保證機制,包括:系統間的一致性對帳、現金對帳、帳務對帳、手續費對帳等等,這些都屬於按期校對模式,順便說一下,金融系統與社交應用在技術上本質的區別在於社交應用在於量大,而金融系統在於數據的準確性。
到如今爲止,咱們看到經過查詢模式、補償模式、按期覈對模式能夠解決案例4到案例7的全部問題,對於案例4:同步超時,若是同步超時,咱們須要查詢狀態進行補償,對於案例5:異步回調超時,若是遲遲沒有收到回調響應,咱們也會經過查詢狀態進行補償,對於案例6:掉單、案例7:系統間狀態不一致,咱們經過按期覈對模式能夠保證系統間操做的一致性,避免掉單和狀態不一致致使問題。
在分佈式系統中,對於主流程中優先級比較低的操做,大多采用異步的方式執行,也就是前面提到的異步確保型,爲了讓異步操做的調用方和被調用方充分的解耦,也因爲專業的消息隊列自己具備可伸縮、可分片、可持久等功能,咱們一般經過消息隊列實現異步化,對於消息隊列,咱們須要創建特殊的設施保證可靠的消息發送以及處理機的冪等等。
消息的可靠發送
消息的可靠發送能夠認爲是盡最大努力發送消息通知,有兩種實現方法:
第一種,發送消息以前,把消息持久到數據庫,狀態標記爲待發送,而後發送消息,若是發送成功,將消息改成發送成功。定時任務定時從數據庫撈取必定時間內未發送的消息,將消息發送。
消息發送模式1
第二種,實現方式與第一種相似,不一樣的是持久消息的數據庫是獨立的,並不耦合在業務系統中。發送消息以前,先發送一個預消息給某一個第三方的消息管理器,消息管理器將其持久到數據庫,並標記狀態爲待發送,發送成功後,標記消息爲發送成功。定時任務定時從數據庫撈取必定時間內未發送的消息,回查業務系統是否要繼續發送,根據查詢結果來肯定消息的狀態。
消息發送模式2
一些公司把消息的可靠發送實如今了中間件裏,經過Spring的注入,在消息發送的時候自動持久消息記錄,若是有消息記錄沒有發送成功,定時會補償發送。
消息處理器的冪等性
若是咱們要保證消息可靠的發送,簡單來講,要保證消息必定要發送出去,那麼就須要有重試機制,有了重試機制,消息必定會重複,那麼咱們須要對重複作處理。
處理重複的最佳方式爲保證操做的冪等性,冪等性的數學公式爲:
f(f(x)) = f(x)
保證操做的冪等性經常使用的幾個方法:
大規模高併發系統中一個常見的核心需求就是億級的讀需求,顯然,關係型數據庫並非解決高併發讀需求的最佳方案,互聯網的經典作法就是使用緩存抗讀需求,下面有一些使用緩存的保證一致性的最佳實踐:
這裏的最佳實踐可以解決案例8:緩存和數據庫不一致、案例9:本地緩存節點間不一致、案例10:緩存數據結構不一致的問題,對於數據存儲層、緩存與數據庫、Nosql等的一致性是更深刻的存儲一致性技術,將會在後續文章單獨介紹,這裏的數據一致性主要是處理應用層與緩存、應用層與數據庫、一部分的緩存與數據庫的一致性。
這一節介紹特殊場景下的一致性問題和解決方案。
在大多數企業裏,新項目和老項目通常會共存,你們都在努力的下掉老項目,可是因爲種種緣由老是下不掉,若是要完全的下掉老項目,就必需要有很是完善的遷移方案,遷移是一項很是複雜而艱鉅的任務,我會在未來的文章中詳細探討遷移方案、流程和技術,這裏咱們只對遷移中使用的開關進行描述。
遷移過程必須使用開關,開關通常都會基於多個維度來設計,例如:全局的、用戶的、角色的、商戶的、產品的等等,若是遷移過程當中遇到問題,咱們須要關閉開關,遷移回老的系統,這須要咱們的新系統兼容老的數據,老的系統也兼容新的數據,從某種意義上來說,遷移比實現新系統更加困難。
曾經看過不少簡單的開關設計,有的開關設計在應用層次,經過一個curl語句調用,沒有權限控制,這樣的開關在服務池的每一個節點都是不一樣步的、不一致的;還有的系統把開關配置放在中心化的配置系統、數據庫或者緩存等,處理的每一個請求都經過統一的開關來判斷是否遷移等等,這樣的開關有一個致命的缺點,服務請求在處理過程當中,開關可能會變化,各個節點之間開關可能不一樣步、不一致,致使重複的請求可能走到新的邏輯又走了老的邏輯,若是新的邏輯和老的邏輯沒有保證冪等性,這個請求就被重複處理了,若是是金融行業的應用,可能會致使資金損失,電商系統可能會致使發貨並退款等問題。
這裏面咱們推薦使用訂單開關,無論咱們在什麼維度上設計了開關,接收到服務請求後,咱們在請求建立的關聯實體(例如:訂單)上標記開關,之後的任何處理流程,包括同步的和異步的處理流程,都經過訂單上的開關來判斷,而不是經過全局的或者基於配置的開關,這樣在訂單建立的時候,開關已經肯定,再也不變動,一旦一份數據再也不發生變化,那麼它永遠是線程安全的,而且不會有不一致的問題。
這個模式在生產中使用比較頻繁,建議每一個企業都把這個模式做爲設計評審的一項,若是不檢查這一項,不少開發童鞋都會偷懶,直接在配置中或者數據庫中作個開關就上線了。
本文從一致性問題的實踐出發,從大規模高併發服務化系統的實踐經驗中進行總結,列舉致使不一致的具體問題,圍繞着具體問題,總結出解決不一致的方法,而且抽象成模式,供你們在開發服務化系統的過程當中參考。
另外,因爲篇幅有限,還有一些關於分佈式一致性的技術沒法在一篇文章中與你們分享,包括:paxos算法、raft算法、zab算法、nwr算法、一致性哈希等,我會在後續文章中詳細介紹。