終于明白為什么要“分庫分表”了!
原創(chuàng)【51CTO.com原創(chuàng)稿件】 隨著互聯(lián)網(wǎng)產(chǎn)業(yè)的蓬勃發(fā)展,在互聯(lián)網(wǎng)應(yīng)用上產(chǎn)生的數(shù)據(jù)也是與日俱增。產(chǎn)生大量的交易記錄和行為記錄,它們的存放和分析是我們需要面對的問題。
圖片來自 Pexels
例如:單表中出現(xiàn)了,動輒百萬甚至千萬級別的數(shù)據(jù)。“分表分庫”就成為解決上述問題的有效工具。今天和大家一起看看,如何進(jìn)行分表分庫以及期間遇到的問題吧。
為什么會分表分庫
數(shù)據(jù)庫數(shù)據(jù)會隨著業(yè)務(wù)的發(fā)展而不斷增多,因此數(shù)據(jù)操作,如增刪改查的開銷也會越來越大。
再加上物理服務(wù)器的資源有限(CPU、磁盤、內(nèi)存、IO 等)。最終數(shù)據(jù)庫所能承載的數(shù)據(jù)量、數(shù)據(jù)處理能力都將遭遇瓶頸。
換句話說需要合理的數(shù)據(jù)庫架構(gòu)來存放不斷增長的數(shù)據(jù),這個(gè)就是分庫分表的設(shè)計(jì)初衷。目的就是為了緩解數(shù)據(jù)庫的壓力,最大限度提高數(shù)據(jù)操作的效率。
數(shù)據(jù)分表
如果單表的數(shù)據(jù)量過大,例如千萬級甚至更多,那么在操作表的時(shí)候就會加大系統(tǒng)的開銷。
每次查詢會消耗數(shù)據(jù)庫大量資源,如果需要多表的聯(lián)合查詢,這種劣勢就更加明顯了。
以 MySQL 為例,在插入數(shù)據(jù)的時(shí)候,會對表進(jìn)行加鎖,分為表鎖定和行鎖定。
無論是哪種鎖定方式,都意味著前面一條數(shù)據(jù)在操作表或者行的時(shí)候,后面的請求都在排隊(duì),當(dāng)訪問量增加的時(shí)候,都會影響數(shù)據(jù)庫的效率。
那么既然一定要分表,那么每張表分配多大的數(shù)據(jù)量比較合適呢?這里建議根據(jù)業(yè)務(wù)場景和實(shí)際情況具體分析。
一般來說 MySQL 數(shù)據(jù)庫單表記錄最好控制在 500 萬條(這是個(gè)經(jīng)驗(yàn)數(shù)字)。既然需要將數(shù)據(jù)從一個(gè)表分別存放到多個(gè)表中,那么來看看下面兩種分表方式吧。
垂直分表
根據(jù)業(yè)務(wù)把一個(gè)表中的字段(Field)分到不同的表中。這些被分出去的數(shù)據(jù)通常根據(jù)業(yè)務(wù)需要,例如分出去一些不是經(jīng)常使用的字段,一些長度較長的字段。
一般被拆分的表的字段數(shù)比較多。主要是避免查詢的時(shí)候出現(xiàn)因?yàn)閿?shù)據(jù)量大而造成的“跨頁”問題。
一般這種拆分在數(shù)據(jù)庫設(shè)計(jì)之初就會考慮,盡量在系統(tǒng)上線之前考慮調(diào)整。已經(jīng)上線的項(xiàng)目,做這種操作是要慎重考慮的。
水平分表
將一個(gè)表中的數(shù)據(jù),按照關(guān)鍵字(例如:ID)(或取 Hash 之后)對一個(gè)具體的數(shù)字取模,得到的余數(shù)就是需要存放到的新表的位置。
用 ID 取模的分表方式分配記錄
ID 分別為 01-04 的四條記錄,如果分配到 3 個(gè)表中,那么對 3 取模得到的余數(shù)分別是:
- ID:01 對 3 取模余數(shù)為 1 ,存到“表 1”。
- ID:02 對 3 取模余數(shù)為 2 ,存到“表 2”。
- ID:03 對 3 取模余數(shù)為 3 ,存到“表 3”。
- ID:04 對 3 取模余數(shù)為 1 ,存到“表 1”。
當(dāng)然這里只是一個(gè)例子,實(shí)際情況需要對 ID 做 Hash 之后再計(jì)算。同時(shí)還可以針對不同表所在的不同的數(shù)據(jù)庫的資源來設(shè)置存儲數(shù)據(jù)的多少。針對每個(gè)表所在的庫的資源設(shè)置權(quán)值。
用這種方式存放數(shù)據(jù)以后,在訪問具體數(shù)據(jù)的時(shí)候需要通過一個(gè) Mapping Table 獲取對應(yīng)要響應(yīng)的數(shù)據(jù)來自哪個(gè)數(shù)據(jù)表。目前比較流行的數(shù)據(jù)庫中間件已經(jīng)幫助我們實(shí)現(xiàn)了這部分的功能。
也就是說不用大家自己去建立這個(gè) Mapping Table,在做查詢的時(shí)候中間件幫助你實(shí)現(xiàn)了 Mapping Table 的功能。所以,我們這里只需要了解其實(shí)現(xiàn)原理就可以了。
Mapping Table 協(xié)助分表
水平拆分還有一種情況是根據(jù)數(shù)據(jù)產(chǎn)生的前后順序來拆分存放。例如,主表只存放最近 2 個(gè)月的信息,其他比較老舊的信息拆分到其他的表中。通過時(shí)間來做數(shù)據(jù)區(qū)分。更有甚者是通過服務(wù)的地域來做數(shù)據(jù)區(qū)分的。
按照時(shí)間做的數(shù)據(jù)分表
需要注意的是由于分表造成一系列記錄級別的問題,例如 Join 和 ID 生成,事務(wù)處理,同時(shí)存在這些表需要跨數(shù)據(jù)庫的可能性:
- Join:需要做兩次查詢,把兩次查詢的結(jié)果在應(yīng)用層做合并。這種做法是最簡單的,在應(yīng)用層設(shè)計(jì)的時(shí)候需要考慮。
- ID:可以使用 UUID,或者用一張表來存放生成的 Sequence,不過效率都不算高。UUID 實(shí)現(xiàn)起來比較方便,但是占用的空間比較大。
Sequence 表的方式節(jié)省了空間,但是所有的 ID 都依賴于單表。這里介紹一個(gè)大廠用的 Snowflake 的方式。
Snowflake 是 Twitter 開源的分布式 ID 生成算法,結(jié)果是一個(gè) long 型的 ID。
其核心思想是:使用 41bit 作為毫秒數(shù),10bit 作為機(jī)器的 ID(5 個(gè) bit 是數(shù)據(jù)中心,5 個(gè) bit 的機(jī)器 ID),12bit 作為毫秒內(nèi)的流水號(意味著每個(gè)節(jié)點(diǎn)在每毫秒可以產(chǎn)生 4096 個(gè) ID),最后還有一個(gè)符號位,永遠(yuǎn)是 0。
Snowflake 示意圖
排序/分頁:數(shù)據(jù)分配到水平的幾個(gè)表中的時(shí)候,做排序和分頁或者一些集合操作是不容易的。
這里根據(jù)經(jīng)驗(yàn)介紹兩種方法。對分表的數(shù)據(jù)先進(jìn)行排序/分頁/聚合,再進(jìn)行合并。對分表的數(shù)據(jù)先進(jìn)行合并再做排序/分頁/聚合。
事務(wù):存在分布式事務(wù)的可能,需要考慮補(bǔ)償事務(wù)或者用 TCC(Try Confirm Cancel)協(xié)助完成,這部分的內(nèi)容我們下面會為大家介紹。
數(shù)據(jù)分庫
說完了分表,再來談?wù)劮謳?。每個(gè)物理數(shù)據(jù)庫支持?jǐn)?shù)據(jù)都是有限的,每一次的數(shù)據(jù)庫請求都會產(chǎn)生一次數(shù)據(jù)庫鏈接,當(dāng)一個(gè)庫無法支持更多訪問的時(shí)候,我們會把原來的單個(gè)數(shù)據(jù)庫分成多個(gè),幫助分擔(dān)壓力。
這里有幾類分庫的原則,可以根據(jù)具體場景進(jìn)行選擇:
- 根據(jù)業(yè)務(wù)不同分庫,這種情況都會把主營業(yè)務(wù)和其他功能分開。例如可以分為訂單數(shù)據(jù)庫,核算數(shù)據(jù)庫,評論數(shù)據(jù)庫。
- 根據(jù)冷熱數(shù)據(jù)進(jìn)行分庫,用數(shù)據(jù)訪問頻率來劃分,例如:近一個(gè)月的交易數(shù)據(jù)屬于高頻數(shù)據(jù),2-6 個(gè)月的交易數(shù)據(jù)屬于中頻數(shù)據(jù),大于 6 個(gè)月的數(shù)據(jù)屬于低頻數(shù)據(jù)。
- 根據(jù)訪問數(shù)據(jù)的地域/時(shí)間范圍進(jìn)行分庫。
單個(gè)表會分到不同的數(shù)據(jù)庫中
通常數(shù)據(jù)分庫之后,每一個(gè)數(shù)據(jù)庫包含多個(gè)數(shù)據(jù)表,多個(gè)數(shù)據(jù)庫會組成一個(gè) Cluster/Group,提高了數(shù)據(jù)庫的可用性,并且可以把讀寫做分離。
Master 庫主要負(fù)責(zé)寫操作,Slave 庫主要負(fù)責(zé)讀操作。在應(yīng)用訪問數(shù)據(jù)庫的時(shí)候會通過一個(gè)負(fù)載均衡代理,通過判斷讀寫操作把請求路由到對應(yīng)的數(shù)據(jù)庫。
如果是讀操作,也會根據(jù)數(shù)據(jù)庫設(shè)置的權(quán)重或者平均分配請求。另外,還有數(shù)據(jù)庫健康監(jiān)控機(jī)制,定時(shí)發(fā)送心跳檢測數(shù)據(jù)庫的健康狀況。
如果 Slave 出現(xiàn)問題,會啟動熔斷機(jī)制停止對其的訪問;如果 Master 出現(xiàn)問題,通過選舉機(jī)制選擇新的 Master 代替。
主從數(shù)據(jù)庫簡圖
數(shù)據(jù)庫擴(kuò)容
分庫之后的數(shù)據(jù)庫會遇到數(shù)據(jù)擴(kuò)容或者數(shù)據(jù)遷移的情況。這里推薦兩種數(shù)據(jù)庫擴(kuò)容的方案。
主從數(shù)據(jù)庫擴(kuò)容
我們這里假設(shè)有兩個(gè)數(shù)據(jù)庫集群,每個(gè)集群分別有 M1 S1 和 M2 S2 互為主備。
兩個(gè)數(shù)據(jù)庫集群示意圖
由于 M1 和 S1 互為主備所以數(shù)據(jù)是一樣的,M2 和 S2 同樣。把原有的 ID %2 模式切換成 ID %4 模式,也就是把兩個(gè)數(shù)據(jù)集群擴(kuò)充到 4 個(gè)數(shù)據(jù)庫集群。
負(fù)載均衡器直接把數(shù)據(jù)路由到原來兩個(gè) S1 和 S2 上面,同時(shí) S1 和 S2 會停止與 M1 和 M2 的數(shù)據(jù)同步,單獨(dú)作為主庫(寫操作)存在。
這些修改不需要重啟數(shù)據(jù)庫服務(wù),只需要修改代理配置就可以完成。由于 M1 M2 S1 S2 中會存在一些冗余的數(shù)據(jù),可以后臺起服務(wù)將這些冗余數(shù)據(jù)刪除,不會影響數(shù)據(jù)使用。
兩個(gè)集群中的兩個(gè)主從,分別擴(kuò)展成四個(gè)集群中的四個(gè)主機(jī)
此時(shí),再考慮數(shù)據(jù)庫可用性,將擴(kuò)展后的 4 個(gè)主庫進(jìn)行主備操作,針對每個(gè)主庫都建立對應(yīng)的從庫,前者負(fù)責(zé)寫操作,后者負(fù)責(zé)讀操作。下次如果需要擴(kuò)容也可以按照類似的操作進(jìn)行。
從兩個(gè)集群擴(kuò)展成四個(gè)集群
雙寫數(shù)據(jù)庫擴(kuò)容
在沒有數(shù)據(jù)庫主從配置的情況下的擴(kuò)容,假設(shè)有數(shù)據(jù)庫 M1 M2 如下圖:
擴(kuò)展前的兩個(gè)主庫
需要對目前的兩個(gè)數(shù)據(jù)庫做擴(kuò)容,擴(kuò)容之后是 4 個(gè)庫如下圖。新增的庫是 M3,M4 路由的方式分別是 ID%2=0 和 ID%2=1。
新增兩個(gè)主庫
這個(gè)時(shí)候新的數(shù)據(jù)會同時(shí)進(jìn)入 M1 M2 M3 M4 四個(gè)庫中,而老數(shù)據(jù)的使用依舊從 M1 M2 中獲取。
與此同時(shí),后臺服務(wù)對 M1 M3,M2 M4 做數(shù)據(jù)同步,建議先做全量同步再做數(shù)據(jù)校驗(yàn)。
老庫給新庫做數(shù)據(jù)同步
當(dāng)完成數(shù)據(jù)同步之后,四個(gè)庫的數(shù)據(jù)保持一致了,修改負(fù)載均衡代理的配置為 ID%4 的模式。此時(shí)擴(kuò)容就完成了,從原來的 2 個(gè)數(shù)據(jù)庫擴(kuò)展成 4 個(gè)數(shù)據(jù)庫。
當(dāng)然會存在部分的數(shù)據(jù)冗余,需要像上面一個(gè)方案一樣通過后臺服務(wù)刪除這些冗余數(shù)據(jù),刪除的過程不會影響業(yè)務(wù)。
數(shù)據(jù)同步以后做 Hash 切分
分布式事務(wù)原理
架構(gòu)設(shè)計(jì)的分表分庫帶來的結(jié)果是我們不得不考慮分布式事務(wù),今天我們來看看分布式事務(wù)需要記住哪兩個(gè)原理。
CAP
互聯(lián)網(wǎng)應(yīng)用大多會使用分表分庫的操作,這個(gè)時(shí)候業(yè)務(wù)代碼很可能會同時(shí)訪問兩個(gè)不同的數(shù)據(jù)庫,做不同的操作。同時(shí)這兩個(gè)操作有可能放在同一個(gè)事務(wù)中處理。
這里引出分布式系統(tǒng)的 CAP 理論,他包括以下三個(gè)屬性:
一致性(Consistency):分布式系統(tǒng)中的所有數(shù)據(jù),同一時(shí)刻有同樣的值。
業(yè)務(wù)代碼往數(shù)據(jù)庫 01 這個(gè)節(jié)點(diǎn)寫入記錄 A,數(shù)據(jù)庫 01 把 A 記錄同步到數(shù)據(jù)庫 02,業(yè)務(wù)代碼再從數(shù)據(jù)庫 02 中讀出的記錄也是 A。那么兩個(gè)數(shù)據(jù)庫存放的數(shù)據(jù)就是一致的。
一致性簡圖
可用性(Availability):分布式系統(tǒng)中一部分節(jié)點(diǎn)出現(xiàn)故障,分布式系統(tǒng)仍舊可以響應(yīng)用戶的請求。
假設(shè)數(shù)據(jù)庫 01 和 02 同時(shí)存放記錄 A,由于數(shù)據(jù)庫 01 掛掉了,業(yè)務(wù)代碼不能從中獲取數(shù)據(jù)。
那么業(yè)務(wù)代碼可以從數(shù)據(jù)庫 02 中獲取記錄 A。也就是在節(jié)點(diǎn)出現(xiàn)問題的時(shí)候,還保證數(shù)據(jù)的可用性。
可用性簡圖
分區(qū)容錯(cuò)性(Partition tolerance):假設(shè)兩個(gè)數(shù)據(jù)庫節(jié)點(diǎn)分別在兩個(gè)區(qū),而兩個(gè)區(qū)的通訊發(fā)生了問題。就不能達(dá)成數(shù)據(jù)一致,這就是分區(qū)的情況,我就需要從 C 和 A 之間做出選擇。
是選擇可用性(A),獲取其中一個(gè)區(qū)的數(shù)據(jù)。還是選擇一致性(C),等待兩個(gè)區(qū)的數(shù)據(jù)同步了再去獲取數(shù)據(jù)。
這種情況的前提是兩個(gè)節(jié)點(diǎn)的通訊失敗了,寫入數(shù)據(jù)庫 01 記錄的時(shí)候,需要鎖住數(shù)據(jù)庫 02 記錄不讓其他的業(yè)務(wù)代碼修改,直到數(shù)據(jù)庫 01 記錄完成修改。因此 C 和 A 在此刻是矛盾的。兩者不能兼得。
分區(qū)容錯(cuò)簡圖
BASE
Base 原理廣泛應(yīng)用在數(shù)據(jù)量大,高并發(fā)的互聯(lián)網(wǎng)場景。一起來看看都包含哪些:
基本可用(Basically Available): 不會因?yàn)槟硞€(gè)節(jié)點(diǎn)出現(xiàn)問題就影響用戶的請求。
即使在流量激增的情況下,也會考慮通過限流降級的辦法保證用戶的請求是可用的。
比如,電商系統(tǒng)在流量激增的時(shí)候,資源會向核心業(yè)務(wù)傾斜,其他的業(yè)務(wù)降級處理。
軟狀態(tài)( Soft State):一條數(shù)據(jù)如果存在多個(gè)副本,允許副本之間同步的延遲,在較短時(shí)間內(nèi)能夠容忍不一致。這個(gè)正在同步并且還沒有完成同步的狀態(tài)稱為軟狀態(tài)。
最終一致性( Eventual Consistency):最終一致性是相對于強(qiáng)一致性來說的,強(qiáng)一致性是要保證所有的數(shù)據(jù)都是一致的,是實(shí)時(shí)同步。
而最終一致性會容忍一小段時(shí)間數(shù)據(jù)的不一致,但過了這段時(shí)間以后數(shù)據(jù)會保證一致。其包含以下幾種“一致性”:
①因果一致性(Causal Consistency)
如果有兩個(gè)進(jìn)程 1 和 2 都對變量 X 進(jìn)行操作,“進(jìn)程 1” 寫入變量 X,“進(jìn)程 2”需要讀取變量 X,然后用這個(gè) X 來計(jì)算 X+2。
這里“進(jìn)程 1”和“進(jìn)程 2” 的操作就存在因果關(guān)系。“進(jìn)程 2” 的計(jì)算依賴于進(jìn)程 1 寫入的 X,如果沒有 X 的值,“進(jìn)程 2”無法計(jì)算。
兩個(gè)進(jìn)程對同一變量進(jìn)行操作
②讀己之所寫(Read Your Writes)
“進(jìn)程 1”寫入變量 X 之后,該進(jìn)程可以獲取自己寫入的這個(gè)值。
進(jìn)程寫入的值的同時(shí)獲取值
③會話一致性(Session Consistency)
如果一個(gè)會話中實(shí)現(xiàn)來讀己之所寫。一旦數(shù)據(jù)更新,客戶端只要在同一個(gè)會話中就可以看到這個(gè)更新的值。
多進(jìn)程在同一會話需要看到相同的值
④單調(diào)寫一致性(Monotonic Write Consistency)
“進(jìn)程 1”如果有三個(gè)操作分別是 1,2,3。“進(jìn)程 2”有兩個(gè)操作分別是 1,2。當(dāng)進(jìn)程請求系統(tǒng)時(shí),系統(tǒng)會保證按照進(jìn)程中操作的先后順序來執(zhí)行。
多進(jìn)程多操作通過隊(duì)列方式執(zhí)行
分布式事務(wù)方案
說完了分布式的原理,再來提一下分布式的方案。由于所處場景不一樣,所以方案也各有不同,這里介紹兩種比較流行的方案,兩段式和 TCC(Try,Confirm,Cancel)。
兩階段提交
顧名思義,事務(wù)會進(jìn)行兩次提交。這里需要介紹兩個(gè)概念,一個(gè)是事務(wù)協(xié)調(diào)者,也叫事物管理器。
它是用來協(xié)調(diào)事務(wù)的,所有事務(wù)什么時(shí)候準(zhǔn)備好了,什么時(shí)候可以提交了,都由它來協(xié)調(diào)和管理。
另一個(gè)是參與者,也叫資源管理器。它主要是負(fù)責(zé)處理具體事務(wù)的,管理者需要處理的資源。例如:訂票業(yè)務(wù),扣款業(yè)務(wù)。
第一階段(準(zhǔn)備階段):事務(wù)協(xié)調(diào)者(事務(wù)管理器)給每個(gè)參與者(資源管理器)發(fā)送 Prepare 消息,發(fā)這個(gè)消息的目的是問“大家是不是都準(zhǔn)備好了,我們馬上就要執(zhí)行事務(wù)了”。
參與者會根據(jù)自身業(yè)務(wù)和資源情況進(jìn)行檢查,然后給出反饋。這個(gè)檢查過程根據(jù)業(yè)務(wù)內(nèi)容不同而不同。
例如:訂票業(yè)務(wù),就要檢查是否有剩余票??劭顦I(yè)務(wù)就要檢查,余額是否足夠。一旦檢查通過了才能返回就緒(Ready)信息。
否則,事務(wù)將終止,并且等待下次詢問。由于這些檢查需要做一些操作,這些操作可能再之后回滾時(shí)用到,所以需要寫 redo 和 undo 日志,當(dāng)事務(wù)失敗重試,或者事務(wù)失敗回滾的時(shí)候使用。
第二階段(提交階段):如果協(xié)調(diào)者收到了參與者失敗或者超時(shí)的消息,會給參與者發(fā)送回滾(rollback)消息;否則,發(fā)送提交(commit)消息。兩種情況處理如下:
情況 1,當(dāng)所有參與者均反饋 yes,提交事務(wù):
- 協(xié)調(diào)者向所有參與者發(fā)出正式提交事務(wù)的請求(即 commit 請求)。
- 參與者執(zhí)行 commit 請求,并釋放整個(gè)事務(wù)期間占用的資源。
- 各參與者向協(xié)調(diào)者反饋 ack(應(yīng)答)完成的消息。
- 協(xié)調(diào)者收到所有參與者反饋的 ack 消息后,即完成事務(wù)提交。
情況 2,當(dāng)有一個(gè)參與者反饋 no,回滾事務(wù):
- 協(xié)調(diào)者向所有參與者發(fā)出回滾請求(即 rollback 請求)。
- 參與者使用第一階段中的 undo 信息執(zhí)行回滾操作,并釋放整個(gè)事務(wù)期間占用的資源。
- 各參與者向協(xié)調(diào)者反饋 ack 完成的消息。
- 協(xié)調(diào)者收到所有參與者反饋的 ack 消息后,即完成事務(wù)。
兩個(gè)階段提交事務(wù)示意圖
TCC(Try,Confirm,Cancel)
對于一些要求高一致性的分布式事務(wù),例如:支付系統(tǒng),交易系統(tǒng),我們會采用 TCC。
它包括,Try 嘗試,Confirm 確認(rèn),Cancel 取消??聪旅嬉粋€(gè)例子能否幫助大家理解。
假設(shè)我們有一個(gè)轉(zhuǎn)賬服務(wù),需要把“A 銀行”“A 賬戶”中的錢分別轉(zhuǎn)到“B銀行”“B 賬戶”和“C 銀行”“C 賬戶”中去。
假設(shè)這三個(gè)銀行都有各自的轉(zhuǎn)賬服務(wù),那么這次轉(zhuǎn)賬事務(wù)就形成了一次分布式事務(wù)。
我們來看看用 TCC 的方式如何解決:
轉(zhuǎn)賬業(yè)務(wù)示意圖
首先是 Try 階段,主要檢測資源是否可用,例如檢查賬戶余額是否足夠,緩存,數(shù)據(jù)庫,隊(duì)列是否可用等等。
并不執(zhí)行具體的邏輯。如上圖,這里從“A 賬戶”轉(zhuǎn)出之前要檢查,賬戶的總金額是否大于 100,并且記錄轉(zhuǎn)出金額和剩余金額。
對于“B 賬戶”和“C 賬戶”來說需要知道賬戶原有總金額和轉(zhuǎn)入的金額,從而可以計(jì)算轉(zhuǎn)入后的金額。
這里的交易數(shù)據(jù)庫設(shè)計(jì)除了有金額字段,還要有轉(zhuǎn)出金額或者轉(zhuǎn)入金額的字段,在 Cancel 回滾的時(shí)候使用。
Try 階段示意圖
如果 Try 階段成功,那么就進(jìn)入 Confirm 階段,也就是執(zhí)行具體的業(yè)務(wù)邏輯。
這里從“A 賬戶”轉(zhuǎn)出 100 元成功,剩余總金額=220-100=120,把這個(gè)剩余金額寫入到總金額中保存,并且把交易的狀態(tài)設(shè)置為“轉(zhuǎn)賬成功”。
“B 賬戶”和“C 賬戶”分別設(shè)置總金額為 80=50+30 和 130=60+70,也把交易狀態(tài)設(shè)置為“轉(zhuǎn)賬成功”。則整個(gè)事務(wù)完成。
Confirm 階段示意圖
如果 Try 階段沒有成功,那么服務(wù) A B C 都要做回滾的操作。對于“A賬戶”來說需要把扣除的 100 元加回,所以總金額 220=120+100。
那么“B 服務(wù)”和“C 服務(wù)”需要把入賬的金額從總金額里面減去,也就是 50=80-30 和 60=130-70。
Cancel 階段示意圖
TCC 接口實(shí)現(xiàn)
這里需要注意的是,需要針對每個(gè)服務(wù)去實(shí)現(xiàn) Try,Confirm,Cancel 三個(gè)階段的代碼。
例如上面所說的檢查資源,執(zhí)行業(yè)務(wù),回滾業(yè)務(wù)等操作。目前有很多開源的架構(gòu)例如:ByteTCC、TCC-transaction 可以借鑒。
TCC 實(shí)現(xiàn)接口示意圖
TCC 可靠性
TCC 通過記錄事務(wù)處理日志來保證可靠性。一旦 Try,Confirm,Cancel 操作的時(shí)候服務(wù)掛掉或者出現(xiàn)異常,TCC 會提供重試機(jī)制。另外如果服務(wù)存在異步的情況可以采用消息隊(duì)列的方式通信保持事務(wù)一致。
重試機(jī)制示意圖
分庫表中間件介紹
如果覺得分表分庫之后,需要考慮的問題很多,可以使用市面上的現(xiàn)成的中間件幫我們實(shí)現(xiàn)。
這里介紹幾個(gè)比較常用的中間件:
- 基于代理方式的有 MySQL Proxy 和 Amoeba。
- 基于 Hibernate 框架的有 Hibernate Shards。
- 基于 JDBC 的有當(dāng)當(dāng) Sharding-JDBC。
- 基于 MyBatis 的類似 Maven 插件式的蘑菇街 TSharding。
另外著重介紹 Sharding-JDBC 的架構(gòu),它的構(gòu)成和“服務(wù)注冊中心”很像。
Sharding-JDBC 會提供一個(gè) Sharding-Proxy 做代理,他會連接一個(gè)注冊中心(registry center),一旦數(shù)據(jù)庫的節(jié)點(diǎn)掛接到系統(tǒng)中,會在這個(gè)中心注冊,同時(shí)也會監(jiān)控?cái)?shù)據(jù)庫的健康狀況做心跳檢測。
而 Sharding-Proxy 本身在業(yè)務(wù)代碼(Business Code)請求數(shù)據(jù)庫的時(shí)候可以協(xié)助做負(fù)載均衡和路由。
同時(shí) Sharding-Proxy 本身也可以支持被 MySQL Cli 和 MySQL Workbench 查看。
實(shí)際上如果我們理解了分表分庫的原理之后,實(shí)現(xiàn)并不難,很多大廠都提供了產(chǎn)品。
Sharding-Proxy 實(shí)現(xiàn)原理圖
總結(jié)
因?yàn)閿?shù)據(jù)量的上升,為了提高性能會對系統(tǒng)進(jìn)行分表分庫。從分表來說,有水平分表和垂直分表兩種方式。
可以根據(jù)業(yè)務(wù),冷熱數(shù)據(jù)等來進(jìn)行分庫,分庫以后通過主從庫來實(shí)現(xiàn)讀寫分離。
如果對分庫之后數(shù)據(jù)庫做擴(kuò)容,有兩種方式,主從數(shù)據(jù)庫擴(kuò)容和雙寫數(shù)據(jù)庫擴(kuò)容。
分表分庫會帶來分布式事務(wù),我們需要掌握 CAP 和 BASE 原理,同時(shí)介紹了兩階段提交和 TCC 兩個(gè)分布式事務(wù)方案。最后,介紹了流行的分表分庫中間件,以及其實(shí)現(xiàn)原理。
作者:崔皓
簡介:十六年開發(fā)和架構(gòu)經(jīng)驗(yàn),曾擔(dān)任過惠普武漢交付中心技術(shù)專家,需求分析師,項(xiàng)目經(jīng)理,后在創(chuàng)業(yè)公司擔(dān)任技術(shù)/產(chǎn)品經(jīng)理。善于學(xué)習(xí),樂于分享。目前專注于技術(shù)架構(gòu)與研發(fā)管理。
【51CTO原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請注明原文作者和出處為51CTO.com】