宜立方商城架構總結

宜立方商城架構

分佈式+SOA

什麼是分佈式?

  將一個完整的系統按照功能點拆成若干個相互獨立的子系統,每一個子系統可稱之爲一個節點,每一個節點都可以單獨配置多臺服務器(集羣),各個子系統之間相互進行通信,進行協調合作,共同完成整個系統的業務流程,這就是分佈式。

 

什麼是集羣?

  多臺服務器做同一件事,這就是集羣。

 

集羣和分佈式的區別?(狹義理解)

答: 集羣就是一個工程部署到多臺服務器上,這麼多服務器都做同一件事。分佈式是把一個項目拆分成若干個工程,各個工程之間相互合作,共同完成整個業務流程。分佈式中每一個節點都可以搭集羣。

 

爲什麼要使用分佈式?

答:一個最簡單的web項目,它的架構可能就是表現層—>業務層—>持久層—>數據庫。整個項目部署到一臺Tomcat中。一臺Tomcat服務器理論支持500個併發,1000個併發需要2個Tomcat,10000併發需要20臺服務器做tomcat集羣。按理說,通過不斷的加Tomcat服務器也是可以解決高併發問題的。但是任何一個項目都應該有用戶登錄的功能,這裏面就必然會涉及到Session複製的問題,Tomcat本身具有Session共享的功能。因爲Seesion複製,就導致tomcat集羣中節點數量不斷增加,其服務能力會先增加後下降,一個集羣中一般有5個服務器,效果會最好。我們說,能用硬件解決的問題不用軟件解決,但是當硬件解決不了時,只能通過軟件來解決。

  所以我們使用了分佈式,將整個系統按功能點拆分成若干個獨立的子系統,再根據每個子系統的業務訪問量單獨配置集羣,比如首頁的併發量就高,訂單頁面的併發量就會相對較低。而且分佈式將Session複製問題單獨提到一個子系統中,即單點登錄系統,其它子系統沒有了Session複製問題,那麼其集羣中的服務器數量就沒有限制了。

 

項目分佈式後拆成的子系統有哪些?

答:前臺系統、後臺管理系統、購物車系統、訂單系統、搜索系統、單點登錄系統。

 

分佈式的優點有哪些?

1.將整個系統拆成不同的模塊,模塊之間使用接口通信,降低了模塊之間的耦合度

2.把一個項目拆成若干子項目,方便不同的團隊負責不同的子項目

3.當有新功能增加時,只需要再增加一個子項目,調用其它子項目的接口就可以

4.可以靈活的進行分佈式部署

 

分佈式的缺點有哪些?

答:1.系統之間交互需要使用遠程通信,接口開發增加工作量

  2.各個模塊有一些通用的業務邏輯無法共用

 

  比方說,前臺系統中的首頁需要展示商品,需要查詢商品信息,訂單也需要展示商品,也需要查詢商品信息,都是查商品信息,可能都是根據商品id查詢商品信息,是同一個業務邏輯。但現在這個業務邏輯能共用嗎?不能。在不同的工程裏面,你是不能共用的。你只能是這個工程裏複製一份,那個工程裏複製一份,兩份。那好了,我們想把這些業務邏輯共用。怎麼才能共用呢?

  所以我們在分佈式的基礎上引入soa的架構。

 

  SOA:Service Oriented Architecture面向服務的架構。也就是把工程拆分成服務層、表現層兩個工程。服務層中包含業務邏輯,只需要對外提供服務即可。表現層只需要處理和頁面的交互,業務邏輯都是調用服務層的服務來實現。

  基於SOA架構,面向服務的架構,這就要求表現層和服務層是分開的,表現層中沒有業務邏輯,要想查數據,需要調用服務層發佈的服務, 服務層工程沒有表現層,沒有頁面,只有業務邏輯。

  系統與系統之間的通信是通過dubbo實現的。

可以簡單介紹一下dubbo的架構?

答:

 

   Dubbo是一個服務治理工具。爲所有的服務提供了一個統一的入口。

   Dubbo架構中包含五個角色,分別是:Container、Provider、Registry、Consumer、Monitor。其中,Container是容器,可以是Spring;Provider是服務提供者,Registry是註冊中心、Consumer是服務調用者、Monitor是監控中心。

執行流程如下:

   1. 在初始化容器的時候,Provider發佈服務;

   2. Provider將發佈的服務在註冊中心Registry中進行註冊;

   3. Consumer預調用服務,先向註冊中心Registry詢問有沒有該服務被髮布,如果有,註冊中心向Consumer返回該服務的地址(ip+端口號)

   4. Consumer拿到預調用服務的地址,直接調用該服務

   5. 客戶端和服務端在內存中累計調用服務的次數和調用時間,並定時將統計數據發送到監控中心Monitor。

 

   Dubbo的實現只需要導入相應的jar包,服務端要發佈服務,客戶端要調用服務,都需要導入該jar包

   Zookeeper和Monitor都是獨立的服務軟件,需要在Linux系統下進行安裝。

 

如何解決分佈式系統中的分佈式緩存問題?

什麼是分佈式系統?

答:將一個完整的系統按照功能點拆成若干個相互獨立的子系統,每一個子系統可稱之爲一個節點,每一個節點都可以單獨配置多臺服務器(集羣),各個子系統之間相互進行通信,進行協調合作,共同完成整個系統的業務流程,這就是分佈式。

分佈式和集羣是不得不聯繫在一起的兩個概念,如果多臺服務器共同處理一件事情,叫集羣;如果多臺服務器各自處理不同的事情,彼此之間協調合作,共同完成整個系統的工作,就叫做分佈式系統。

 

Redis-Cluster是集羣,還是分佈式緩存系統?

答:既是集羣,也是分佈式系統。這要看從哪個角度來考慮。

  假如從存儲數據是否相同來看,Redis-Cluster中每個結點存儲的數據是不一樣的,它共有16384個槽(0~16383),假如Redis-Cluster中有3個結點,那麼這16384個槽是根據各個節點的性能分佈在不同節點上的。第一個節點負責0~5000個槽,第二個節點負責5001~10000個槽,第三個節點負責10001~16383個槽,三個節點分別負責不同範圍的數據存儲,最終完成對整個數據範圍的存儲。很顯然,這是一個分佈式系統。

  但如果從Redis-Cluster中所提供的緩存功能來看,每個結點都是用來做緩存的,各個節點提供的功能都是一樣的,當內存不夠用時,還可以不斷橫向添加結點來擴大內存容量,很顯然,這是一個集羣。

  所以,我們會說,Redis集羣是一個分佈式集羣系統。

   

(面試題)你知道哪些分佈式緩存,如果要你設計一個分佈式緩存,你會怎麼去設計?

答:主要有MemcachedRedis。我使用Redis來做分佈式緩存。

剛開始對Redis的操作都是單機版,雖然Redis的速度很快,但是在特別高的併發下,Redis也有性能瓶頸。Redis中的數據都放在內存裏面,內存能有多大呢?64G,已經很大了,64G都放滿了呢?還能放嗎?可以,內存放滿了會放在硬盤中的虛擬內存中,一旦用到虛擬內存了,性能就很低了,所以我們儘可能的不要超出內存的容量。如果存不下了,但是數據還是很多,還需要往緩存中放,那怎麼辦呢?通過搭Redis集羣來擴展內存空間。官方給出的Redis集羣名稱爲Redis-Cluster。

Redis-Cluster架構圖如下:

 

集羣一般都會有一個入口,有一個集羣管理工具,但Redis集羣沒有入口,即沒有代理層,集羣中的節點都是相互連接的,通過PING-PONG機制來實現各個節點之間的通信,以及判斷各個節點的狀態,客戶端想要連接集羣,只需要連接到集羣中的任意一個節點即可。

集羣中有那麼多個結點,結點中保存的數據一樣嗎?不一樣。如果是一樣的,那叫主備。既然是集羣,就應該是可以擴容的,如果存儲空間不足了,可以加結點,加一個服務器進行,存儲空間就會變大。所有結點的內存容量加起來纔是整個集羣內存的總容量,如果每個結點存儲的數據都一樣,那總容量就只是一個Redis的內存容量了。

Redis集羣中,每個結點保存的數據是不一樣的。如果不一樣,那麼當一個結點掛了,那整個集羣就不完整了,不完整了就不能用了,所以,要想保證Redis集羣的高可用(長時間可使用,而不會宕機),每一個節點都需要加一個備份機,如果這個結點掛了,必須要有備份結點頂上來,來保證集羣可以繼續提供服務。

Redis集羣中有一個投票:容錯機制,我們前面說集羣中一般都會有一個集羣管理工具,但在Redis集羣中並沒有,那麼,我們怎麼才能知道集羣中哪一個結點掛了呢?Redis集羣中有一個投票機制。大家都知道,在選舉的時候,是少數服從多數的原則,要判斷Redis集羣中的某個結點是否掛掉了,需要我們集羣中超過半數的節點進行投票,半數以上的節點認爲它掛了,它就掛了。假如集羣中有5個結點,有三個認爲某個結點已經掛了,那麼集羣就認爲這個結點真掛了。這個時候就要看有沒有備份結點,如果沒有備份結點頂上來,那麼集羣就會宕機。如果有備份結點,備份結點頂上來,繼續維持整個集羣的工作,然後管理人員就需要趕快把那個掛掉的節點修理好。那麼,集羣中最少有幾個結點呢?3個!3個結點就可以搭建起一個Redis集羣。而在實際開發中,爲了保證集羣的高可用,還要保證每個結點都有一個備份機,所以,實際中,最小的集羣會搭建6個結點。那如果面試官問你:

一個Redis集羣至少有幾個結點?爲什麼?

答:3個結點。這是由Redis集羣中的投票:容錯機制決定的。Redis集羣中,要判斷一個結點是否掛掉了,是通過集羣中的其他結點投票決定的。當集羣中有一半以上的節點都投票認爲該節點掛掉了,Redis集羣纔會認爲該節點掛掉了,這就導致一個Redis集羣中最少要有3個結點。

當我們使用單機版的Redis做緩存時,操作很簡單,當單機版的Redis變成Redis集羣后,操作是不是就會變得異常複雜?

答:並不會變多複雜。只需要連接上Redis集羣中的任意一個結點,就能連接上整個Redis集羣。只是在使用Jedis連接單機版Redis和連接Redis集羣時,會有所不同,但對Redis的操作都是一樣的。

Redis集羣中,每個結點保存的數據是不一樣的,那就會有一個問題,如何把數據分散到不同的節點進行存儲呢?爲解決這個問題,Redis集羣中引入了一個概念,叫slot(槽,哈希槽)。Redis集羣中一共有16384個槽(0~16383),這是固定的。這些槽有什麼作用呢?

當要在Redis集羣中放置一個key-value對時,先要對key使用crc16算法得出一個數,然後再用這個數對16384求餘數,肯定會得到一個0~16383之間的數,這樣每一個key值都會對應一個0~16383之間的哈希槽,然後將key-value鍵值對放在這個槽對應的Redis結點上就可以了。

槽如何進行分配呢?

答:這是可以由認爲設置的。一般情況下會平均分配。爲了保證Redis集羣的性能,要看Redis集羣中有幾個結點,還要看每個結點的性能怎麼樣。假如有3個結點,每個結點的性能都是完全一樣的,那麼我們就可以把這16384個槽平均分到3個結點上。

0~5000個槽分到第一個結點上

5001~10000個槽分到第二個結點上

10001~16383個槽分到第三個結點上(爲了好計算,這樣劃分)

 

Redis集羣中最多有多少個結點?爲什麼?

答:最多有16384個結點(這裏不考慮備份機的問題)。這是由Redis集羣中哈希槽的數量決定的,極限情況下,每個結點有一個哈希槽。

架構細節:

(1)所有的redis節點彼此互聯(PING-PONG機制),內部使用二進制協議優化傳輸速度和帶寬.

(2)節點的fail是通過集羣中超過半數的節點檢測失效時才生效.

(3)客戶端與redis節點直連,不需要中間proxy層.客戶端不需要連接集羣所有節點,連接集羣中任何一個可用節點即可

(4)redis-cluster把所有的物理節點映射到[0-16383]slot上,cluster負責維護node<->slot<->value

Redis集羣中內置了16384個哈希槽,當需要在Redis集羣中放置一個key-value時,redis先對key使用crc16算法算出一個結果,然後把結果對16384求餘數,這樣每個key都會對應一個編號在0-16383之間的哈希槽,redis會根據節點數量大致均等的將哈希槽映射到不同的節點。

數據庫架構設計

海量數據的存儲問題

如今隨着互聯網的發展,數據的量級也是呈指數的增長,從GBTB到PB。對數據的各種操作也是愈加的困難,傳統的關係型數據庫已經無法滿足快速查詢與插入數據的需求。這個時候NoSQL的出現暫時解決了這一危機。它通過降低數據的安全性,減少對事務的支持,減少對複雜查詢的支持,來獲取性能上的提升。

但是,在有些場合NoSQL一些折衷是無法滿足使用場景的,就比如有些使用場景是絕對要有事務與安全指標的。這個時候NoSQL肯定是無法滿足的,所以還是需要使用關係型數據庫。如何使用關係型數據庫解決海量存儲的問題呢?此時就需要做數據庫集羣,爲了提高查詢性能將一個數據庫的數據分散到不同的數據庫中存儲。

當數據太多,以至於數據庫存不下的時候,我們要做數據庫分片。

什麼是數據庫分片

簡單來說,就是指通過某種特定的條件,將我們存放在同一個數據庫中的數據分散存放到多個數據庫(主機)上面,以達到分散單臺設備負載的效果。

數據的切分根據其切分規則的類型,可以分爲兩種切分模式。

1)一種是按照不同的表(或者Schema)來切分到不同的數據庫(主機)之上,這種切可以稱之爲數據的垂直(縱向)切分。數據量比較大,是因爲有幾張表中存放的數據量比較大,比如商品表、訂單表、商品表等,我把不同的表放到不同的數據庫中,訂單庫、商品庫、用戶庫等。將不同的表放到不同的數據庫中,這就是所謂的垂直切割。這種思路是可行的,但是假如我只是單張表數據量特別大,即便是水平切分之後,存這張表的數據庫性能也會很低。

2)另外一種則是根據表中的數據的邏輯關係,將同一個表中的數據按照某種條件拆分到多臺數據庫(主機)上面,這種切分稱之爲數據的水平(橫向)切分。大多數的互聯網網站採取的是水平切割,因爲垂直切割解決不了單表數據量過大的問題。

當數據庫分片後(水平切割),數據由一個數據庫分散到多個數據庫中。此時系統要查詢時需要切換不同的數據庫進行查詢,那麼系統如何知道要查詢的數據在哪個數據庫中?當添加一條記錄時要向哪個數據庫中插入呢?這些問題處理起來都是非常的麻煩。

這種情況下可以使用一個數據庫中間件mycat來解決相關的問題。接下來了解一下什麼是mycat。

什麼是Mycat?

Mycat背後是阿里曾經開源的知名產品——Cobar。Cobar的核心功能和優勢是MySQL數據庫分片,此產品曾經廣爲流傳,據說最早的發起者對Mysql很精通,後來從阿里跳槽了,阿里隨後將Cobar開源,並維持到2013年年初。然後,就沒有然後了。

Cobar的思路和實現路徑的確不錯。基於Java開發的,實現了MySQL公開的二進制傳輸協議,巧妙地將自己僞裝成一個MySQL Server,目前市面上絕大多數MySQL客戶端工具和應用都能兼容。比自己實現一個新的數據庫協議要明智的多,因爲生態環境在那裏擺着。

Mycat是基於Cobar演變而來,對Cobar的代碼進行了徹底的重構,使用NIO重構了網絡模塊,並且優化了Buffer內核,增強了聚合、Join等基本特性,同時兼容絕大多數數據庫稱爲通用的數據庫中間件。

簡單的說,Mycat就是:一個新穎的數據庫中間件產品,支持mysql集羣,或者marisdb cluster,提供高可用性數據分片集羣。你可以像使用mysql一樣使用mycat。對於開發人員來說根本感覺不到mycat的存在。

 

4 Mycat具體是如何進行數據庫分片的呢?

答:這一塊不是我做的,我並不太清楚內部實現原理。

 

5 Mycat讀寫分離

數據庫讀寫分離對於大型系統或者訪問量很高的互聯網應用來說,是必不可少的一個重要功能。對於MySQL來說,標準的讀寫分離是主從模式,一個寫節點Master後面跟着多個讀節點,讀節點的數量取決於系統的壓力,通常是1-3個讀節點的配置

 

Mycat讀寫分離和自動切換機制,需要mysql的主從複製機制配合。主數據庫通過主從複製生成一個從數據庫,這兩個數據庫中存放的數據是一模一樣的,當對主數據庫修改之後,修改結果也會同步到從數據庫中。數據庫的讀寫分離要依賴於數據庫的主從複製,對主數據庫進行寫操作,對從數據庫進行讀操作。

6 Mysql的主從複製

 

主從複製需要注意的地方

1、主DB server和從DB server數據庫的版本一致

2、主DB server和從DB server數據庫數據名稱一致

3、主DB server開啓二進制日誌,主DB server和從DB server的server_id都必須唯一

 

注意:面試的時候經常會問,你是如何解決項目中的分佈式事務問題的?

答:我們項目中使用Mycat管理數據庫集羣,Mycat本身支持分佈式事務。

 

分佈式事務問題

在說分佈式事務之前,我們先從數據庫事務說起。 數據庫事務可能大家都很熟悉,在開發過程中也會經常使用到。但是即使如此,可能對於一些細節問題,很多人仍然不清楚。比如很多人都知道數據庫事務的幾個特性:原子性(Atomicity )、一致性( Consistency )、隔離性或獨立性( Isolation)和持久性(Durabilily),簡稱就是ACID。但是再往下,比如問到隔離性指的是什麼的時候可能就不知道了,或者是知道隔離性是什麼但是再問到數據庫實現隔離的都有哪些級別,或者是每個級別他們有什麼區別的時候可能就不知道了。

  本文並不打算介紹這些數據庫事務的這些東西,有興趣可以搜索一下相關資料。不過有一個知識點我們需要了解,就是假如數據庫在提交事務的時候突然斷電,那麼它是怎麼樣恢復的呢? 爲什麼要提到這個知識點呢? 因爲分佈式系統的核心就是處理各種異常情況,這也是分佈式系統複雜的地方,因爲分佈式的網絡環境很複雜,這種 “斷電”故障要比單機多很多,所以我們在做分佈式系統的時候,最先考慮的就是這種情況。這些異常可能有機器宕機、網絡異常、消息丟失、消息亂序、數據錯誤、不可靠的TCP、存儲數據丟失、其他異常等等...

  我們接着說本地事務數據庫斷電的這種情況,它是怎麼保證數據一致性的呢?我們使用SQL Server來舉例,我們知道我們在使用 SQL Server 數據庫是由兩個文件組成的,一個數據庫文件和一個日誌文件,通常情況下,日誌文件都要比數據庫文件大很多。數據庫進行任何寫入操作的時候都是要先寫日誌的。同樣的道理,我們在執行事務的時候數據庫首先會記錄下這個事務的redo操作日誌,然後纔開始真正操作數據庫,在操作之前首先會把日誌文件寫入磁盤,那麼當突然斷電的時候,即使操作沒有完成,在重新啓動數據庫時候,數據庫會根據當前數據的情況進行undo回滾或者是redo前滾,這樣就保證了數據的強一致性。

接着,我們就說一下分佈式事務。

當我們的單個數據庫的性能產生瓶頸的時候,我們可能會對數據庫進行分區,這裏所說的分區指的是物理分區,分區之後可能不同的庫就處於不同的服務器上了,這個時候單個數據庫的ACID已經不能適應這種情況了,而在這種ACID的集羣環境下,再想保證集羣的ACID幾乎是很難達到,或者即使能達到那麼效率和性能會大幅下降,最爲關鍵的是再很難擴展新的分區了,這個時候如果再追求集羣的ACID會導致我們的系統變得很差,這時我們就需要引入一個新的理論原則來適應這種集羣的情況,就是 CAP 原則或者叫CAP定理,那麼CAP定理指的是什麼呢?

CAP定理

  CAP定理是由加州大學伯克利分校Eric Brewer教授提出來的,他指出一個提供數據服務的存儲系統無法同時滿足數據一致性(Consistency)、數據可用性(Availibility)、分區耐受性(Partition tolerance) :

一致性(Consistency) : 在數據有多份副本的情況下,如果網絡、服務器或者軟件出現故障,會導致部分副本寫入成功,部分副本寫入失敗。這就會造成各個副本之間的數據不一致,數據內容衝突。實踐中,導致數據不一致的情形有很多種,表現形式也多種多樣,比如數據更新返回操作失敗,事實上數據在存儲服務器已經更新成功。

可用性(Availability) : 在多份數據副本分別存放在不同存儲設備的情況下,如果一個數據存儲設備損壞,就需要將數據訪問切換到另一個數據存儲設備上,如果這個過程不能很快完成(終端用戶幾乎沒有感知),或者在完成過程中需要停止終端用戶訪問數據,那麼這段時間數據是不可訪問的。

分區耐受性(Partition tolerance) : 簡單說就是可擴展性(伸縮性)。

  具體地講在分佈式系統中,在大型網站應用中,數據規模總是快速擴張的,因此可伸縮性即分區耐受性必不可少,規模變大以後,機器數量也會變得龐大,這時網絡和服務器故障會頻繁出現,要想保證應用可用,就必須保證分佈式處理系統的高可用性。所以在大型網站中。通常會選擇強化分佈式存儲系統的可用性(A)和伸縮性(P),而在某種程度上放棄一致性(C)。一般說來,數據不一致通常出現在系統高併發寫操作或者集羣狀態不穩(故障恢復、集羣擴容......)的情況下,應用系統需要對分佈式數據處理系統的數據不一致有所瞭解並進行某種意義上的補償和糾錯,以避免出現應用系統數據不正確。

  2012年淘寶“雙十一”活動期間,在活動第一分鐘就涌入了1000萬獨立用戶訪問,這種極端的高併發場景對數據處理系統造成了巨大壓力,存儲系統較弱的數據一致性導致出現部分商品超賣現象(交易成功的商品數超過了商品庫存數)。

  CAP原理對於可伸縮的分佈式系統設計具有重要意義,在系統設計開發過程中,不恰當地迎合各種需求,企圖打造一個完美的產品,可能會使設計進入兩難境地,難以爲繼。

  具體來說,數據一致性又可分爲如下幾點:

  數據強一致

  各個副本的數據在物理存儲中總是一致的;數據更新操作結果和操作響應總是一致的,即操作響應通知更新失敗,那麼數據一定沒有被更新,而不是出於不確定狀態。

  數據用戶一致

  即數據在物理存儲中的各個副本的數據可能是不一致的,但是終端用戶訪問時,通過糾錯和校驗機制,可以確定一個一致的且正確的數據返回給用戶。

  數據最終一致

  這是數據一致性中較弱的一種,即物理存儲的數據可能是不一致的,終端用戶訪問到的數據可能也是不一致的(同一用戶連續訪問,結果不同;或者不同用戶同時訪問,結果不同),但系統經過一段時間(通常是一個比較端的時間段)的自我恢復和修正,數據最終會達到一致。

  因爲難以滿足數據強一致性,網站通常會綜合成本、技術、業務場景等條件,結合應用服務和其他的數據監控與糾錯功能,使存儲系統達到用戶一致,保證最終用戶訪問數據的正確性。

  CAP定理在迄今爲止的分佈式系統中都是適用的! 爲什麼這麼說呢?

  這個時候有同學可能會把數據庫的2PC(兩階段提交)搬出來說話了。OK,我們就來看一下數據庫的兩階段提交。

  對數據庫分佈式事務有了解的同學一定知道數據庫支持的2PC,又叫做 XA Transactions。

MySQL從5.5版本開始支持,SQL Server 2005 開始支持,Oracle 7 開始支持。

其中,XA 是一個兩階段提交協議,該協議分爲以下兩個階段:

第一階段:事務協調器要求每個涉及到事務的數據庫預提交(precommit)此操作,並反映是否可以提交.

第二階段:事務協調器要求每個數據庫提交數據。

  其中,如果有任何一個數據庫否決此次提交,那麼所有數據庫都會被要求回滾它們在此事務中的那部分信息。這樣做的缺陷是什麼呢? 咋看之下我們可以在數據庫分區之間獲得一致性。

  如果CAP 定理是對的,那麼它一定會影響到可用性。

  如果說系統的可用性代表的是執行某項操作相關所有組件的可用性的和。那麼在兩階段提交的過程中,可用性就代表了涉及到的每一個數據庫中可用性的和。我們假設兩階段提交的過程中每一個數據庫都具有99.9%的可用性,那麼如果兩階段提交涉及到兩個數據庫,這個結果就是99.8%。根據系統可用性計算公式,假設每個月43200分鐘,99.9%的可用性就是43157分鐘, 99.8%的可用性就是43114分鐘,相當於每個月的宕機時間增加了43分鐘。

數據庫中的2PC追求的是強一致性,這就必然導致可用性很差,這是不合適的。

以上,可以驗證出來,CAP定理從理論上來講是正確的,CAP我們先看到這裏,等會再接着說。

BASE理論

  在分佈式系統中,我們往往追求的是可用性,它的重要程度比一致性要高,那麼如何實現高可用性呢? 前人已經給我們提出來了另外一個理論,就是BASE理論,它是用來對CAP定理進行進一步擴充的。BASE理論指的是:

· Basically Available(基本可用)

· Soft state(軟狀態)

· Eventually consistent(最終一致性)

  BASE理論是對CAP中的一致性和可用性進行一個權衡的結果,理論的核心思想就是:保證數據的基本可用和最終一致。但在有些業務場景下,需要高可用,需要強一致,這就需要開發人根據實際情況進行取捨了。

  有了以上理論之後,我們來看一下分佈式事務的問題。

 

分佈式事務就是用來解決數據一致性問題的。分佈式事務的理論基礎是CAP定理和BASE定理。所以,根據這兩大理論基礎,決定了在處理分佈式事務問題上,不能盲目追求數據的強一致性,因爲數據的一致性分爲三個層次:強一致性、用戶一致性和最終一致性。我們雖然竭力保證數據的一致性,但是如果過分追求數據一致性,就會犧牲掉數據的可用性,而可用性往往比一致性更重要。但有些情況下是需要保證數據的強一致性的,這個時候就算犧牲掉數據的可用性,也是沒辦法的。

分佈式事務經典應用場景

最經典的場景就是支付了,一筆支付,是對買家賬戶進行扣款,同時對賣家賬戶進行加錢,這些操作必須在一個事務裏執行,要麼全部成功,要麼全部失敗。而對於買家賬戶屬於買家中心,對應的是買家數據庫,而賣家賬戶屬於賣家中心,對應的是賣家數據庫,對不同數據庫的操作必然需要引入分佈式事務。

買家在電商平臺下單,往往會涉及到兩個動作,一個是扣庫存,第二個是更新訂單狀態,庫存和訂單一般屬於不同的數據庫,需要使用分佈式事務保證數據一致性。

處理分佈式事務的手段有哪些?

一、兩階段提交(2PC)

  和上一節中提到的數據庫XA事務一樣,兩階段提交就是使用XA協議的原理,我們可以從下面這個圖的流程來很容易的看出中間的一些比如commit和abort的細節。

 

  兩階段提交這種解決方案屬於犧牲了一部分可用性來換取的一致性。在實現方面,在 .NET 中,可以藉助 TransactionScop 提供的 API 來編程實現分佈式系統中的兩階段提交,比如WCF中就有實現這部分功能。不過在多服務器之間,需要依賴於DTC來完成事務一致性,Windows下微軟搞的有MSDTC服務,Linux下就比較悲劇了。

  另外說一句,TransactionScop 默認不能用於異步方法之間事務一致,因爲事務上下文是存儲於當前線程中的,所以如果是在異步方法,需要顯式的傳遞事務上下文。

  優點: 儘量保證了數據的強一致,適合對數據強一致要求很高的關鍵領域。(其實也不能100%保證強一致)

  缺點: 實現複雜,犧牲了可用性,對性能影響較大,不適合高併發高性能場景,如果分佈式系統跨接口調用,目前 .NET 界還沒有實現方案。

二、補償事務(TCC)

  TCC 其實就是採用的補償機制,其核心思想是:針對每個操作,都要註冊一個與其對應的確認和補償(撤銷)操作。它分爲三個階段:

Try 階段主要是對業務系統做檢測及資源預留

Confirm 階段主要是對業務系統做確認提交,Try階段執行成功並開始執行 Confirm階段時,默認 Confirm階段是不會出錯的。即:只要Try成功,Confirm一定成功。

Cancel 階段主要是在業務執行錯誤,需要回滾的狀態下執行的業務取消,預留資源釋放。

 舉個例子,假入 Bob 要向 Smith 轉賬,思路大概是:
   我們有一個本地方法,裏面依次調用
   1、首先在 Try 階段,要先調用遠程接口把 Smith 和 Bob 的錢給凍結起來。
   2、在 Confirm 階段,執行遠程調用的轉賬的操作,轉賬成功進行解凍。
   3、如果第2步執行成功,那麼轉賬成功,如果第二步執行失敗,則調用遠程凍結接口對應的解凍方法 (Cancel)。

   優點: 跟2PC比起來,實現以及流程相對簡單了一些,但數據的一致性比2PC也要差一些

   缺點: 缺點還是比較明顯的,在2,3步中都有可能失敗。TCC屬於應用層的一種補償方式,所以需要程序員在實現的時候多寫很多補償的代碼,在一些場景中,一些業務流程可能用TCC不太好定義及處理。

三、本地消息表(異步確保)

    本地消息表這種實現方式應該是業界使用最多的,其核心思想是將分佈式事務拆分成本地事務進行處理,這種思路是來源於ebay。我們可以從下面的流程圖中看出其中的一些細節:

 

基本思路就是:

  消息生產方,需要額外建一個消息表,並記錄消息發送狀態。消息表和業務數據要在一個事務裏提交,也就是說他們要在一個數據庫裏面。然後消息會經過MQ發送到消息的消費方。如果消息發送失敗,會進行重試發送。

  消息消費方,需要處理這個消息,並完成自己的業務邏輯。此時如果本地事務處理成功,表明已經處理成功了,如果處理失敗,那麼就會重試執行。如果是業務上面的失敗,可以給生產方發送一個業務補償消息,通知生產方進行回滾等操作。

  生產方和消費方定時掃描本地消息表,把還沒處理完成的消息或者失敗的消息再發送一遍。如果有靠譜的自動對賬補賬邏輯,這種方案還是非常實用的。

  這種方案遵循BASE理論,採用的是最終一致性,筆者認爲是這幾種方案裏面比較適合實際業務場景的,即不會出現像2PC那樣複雜的實現(當調用鏈很長的時候,2PC的可用性是非常低的),也不會像TCC那樣可能出現確認或者回滾不了的情況。

  優點:一種非常經典的實現,避免了分佈式事務,實現了最終一致性。在 .NET中有現成的解決方案。

  缺點:消息表會耦合到業務系統中,如果沒有封裝好的解決方案,會有很多雜活需要處理。

你們的項目中是如何保證分佈式事務問題的?

答:我們的項目用的是一個數據庫中間件Mycat來統一管理數據庫集羣。所以對開發人員而言,使用Mycat就和使用單個mysql數據庫一樣。Mycat中間件支持分佈式事務問題,它底層實現的原理是“弱XA事務”。

 

Mycat裏的事務包括以下幾種情況:

SQL不垮分片:事務中的單條SQL在單個節點上執行

SQL跨分片:事務中的單條SQL在多個節點上執行

事務內多個SQL,在不同的分片上執行

其中,第一種情況,單一SQL僅僅在一個dataNode上執行,此時Mycat事務模式跟標準的數據庫事務模式一樣,要麼提交要麼回滾;而對於第二種和第三種的事務,Mycat執行的一種”弱XA事務“模式,此模式的邏輯如下:

首先事務內的SQL在各自的分片上執行並返回狀態碼,若某個分片上的返回碼爲ERROR,則Mycat認爲事務失敗,應用端只能回滾(rollback)事務,Mycat收到回滾指令後,依次回滾事務中涉及到的所有分片;若事務中的所有SQL的執行都返回成功(OK)的返回碼,則應用程序提交事務的時候,Mycat會同時向事務中涉及到的節點發送提交事務的指令。

XA事務原理

分佈式事務處理( Distributed Transaction Processing,DTP )指一個程序或程序段,在一個或多個資源如數據庫或文件上爲完成某些功能的執行過程的集合,分佈式事務處理的關鍵是必須有一種方法可以知道事務在任何地方所做的所有動作,提交或回滾事務的決定必須產生統一的結果(全部提交或全部回滾)。X/Open 組織(即現在的 Open Group )定義了分佈式事務處理模型。 X/Open DTP 模型( 1994 )包括應用程序( AP )、事務管理器( TM )、資源管理器( RM )、通信資源管理器(CRM )四部分。一般,常見的事務管理器( TM )是交易中間件,常見的資源管理器( RM )是數據庫,常見的通信資源管理器( CRM )是消息中間件,下圖是X/Open DTP模型:

XA事務的問題和MySQL的侷限

XA事務的明顯問題是timeout問題,比如當一個RM出問題了,那麼整個事務只能處於等待狀態。這樣可以會連鎖反應,導致整個系統都很慢,最終不可用,另外2階段提交也大大增加了XA事務的時間,使得XA事務無法支持高併發請求。

避免使用XA事務的方法通常是最終一致性。

舉個例子,比如一個業務邏輯中,最後一步是用戶賬號增加300元,爲了減少DB的壓力,先把這個放到消息隊列裏,然後後端再從消息隊列裏取出消息,更新DB。那麼如何保證,這條消息不會被重複消費?或者重複消費後,仍能保證結果是正確的?在消息裏帶上用戶帳號在數據庫裏的版本,在更新時比較數據的版本,如果相同則加上300;比如用戶本來有500元,那麼消息是更新用戶的錢數爲800,而不是加上300;

另外一個方式是,建一個消息是否被消費的表,記錄消息ID,在事務裏,先判斷消息是否已經消息過,如果沒有,則更新數據庫,加上300,否則說明已經消費過了,丟棄。

前面兩種方法都必須從流程上保證是單方向的。

其實嚴格意義上,用消息隊列來實現最終一致性仍然有漏洞,因爲消息隊列跟當前操作的數據庫是兩個不同的資源,仍然存在消息隊列失敗導致這個賬號增加300元的消息沒有被存儲起來(當然複雜的高級的消息隊列產品可以避免這種現象,但仍然存在風險),而第二種方式則由於新的表跟之前的事務操作的表示在一個Database中,因此不存在上述的可能性。

MySQL的XA事務,長期以來都存在一個缺陷:

MySQL數據庫的主備數據庫的同步,通過Binlog的複製完成。而Binlog是MySQL數據庫內部XA事務的協調者,並且MySQL數據庫爲binlog做了優化——binlog不寫prepare日誌,只寫commit日誌。所有的參與節點prepare完成,在進行xa commit前crash。crash recover如果選擇commit此事務。由於binlog在prepare階段未寫,因此主庫中看來,此分佈式事務最終提交了,但是此事務的操作並未寫到binlog中,因此也就未能成功複製到備庫,從而導致主備庫數據不一致的情況出現。

 

總結:

我們項目中使用數據庫中間件Mycat來統一管理數據庫集羣,對開發人員來說,使用Mycat和使用單個Mysql數據庫是一樣的。Mycat中間件支持分佈式事務問題,它底層實現的原理是使用了Mysql數據庫中的XA事務模式,但是有有所不同,所以,可稱Mycat裏面是通過“弱XA事務”來實現分佈式事務管理的。這種實現分佈式事務的方式存在缺陷,就是它追求的是強一致性,根據CAP定理,就必然以犧牲可用性爲代價,而可用性往往比一致性更重要,所以不應該追求強一致性,而應該在保證可用性的提前下,實現數據的最終一致性就可以了。

解決這一問題的方式一般有兩種,一種是消息隊列,一種是本地消息表。

消息隊列:,比如一個業務邏輯中,最後一步是用戶賬號增加300元,爲了減少DB的壓力,先把這個放到消息隊列裏,然後後端再從消息隊列裏取出消息,更新DB。那麼如何保證,這條消息不會被重複消費?或者重複消費後,仍能保證結果是正確的?在消息裏帶上用戶帳號在數據庫裏的版本,在更新時比較數據的版本,如果相同則加上300;比如用戶本來有500元,那麼消息是更新用戶的錢數爲800,而不是加上300

本地消息表:建一個消息是否被消費的表,記錄消息ID,在事務裏,先判斷消息是否已經消息過,如果沒有,則更新數據庫,加上300,否則說明已經消費過了,丟棄。這兩種方法都必須從流程上保證是單方向的。

其實嚴格意義上,用消息隊列來實現最終一致性仍然有漏洞,因爲消息隊列跟當前操作的數據庫是兩個不同的資源,仍然存在消息隊列失敗導致這個賬號增加300元的消息沒有被存儲起來(當然複雜的高級的消息隊列產品可以避免這種現象,但仍然存在風險),而第二種方式則由於新的表跟之前的事務操作的表示在一個Database中,因此不存在上述的可能性。


發佈了90 篇原創文章 · 獲贊 86 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章