輕松構(gòu)建微服務(wù)之分布式事物

微信公眾號:內(nèi)核小王子
關(guān)注可了解更多關(guān)于數(shù)據(jù)庫,JVM內(nèi)核相關(guān)的知識;
如果你有任何疑問也可以加我pigpdong[1]
此為輕松構(gòu)建微服務(wù)系列的第三篇文章

拜占庭將軍問題

在存在消息丟失的不可信信道上通過消息傳遞的方式得到一致性是不可能的

由于當(dāng)時拜占庭帝國國土遼闊,為了防御目的,每個軍隊都分隔很遠(yuǎn),將軍和將軍之間只能靠信差來通信,但是軍隊內(nèi)可能存在間諜和叛徒,那么如何在已知有叛徒的情況下,在其他忠誠的將軍之間達(dá)成一致性協(xié)定。映射到網(wǎng)絡(luò)問題,例如網(wǎng)絡(luò)被劫持,網(wǎng)絡(luò)不可達(dá)等

CAP

一個分布式系統(tǒng)中,最多只能同時滿足一致性,可用性,分區(qū)容忍性

image
  • 一致性(Consistance)

一致性是指,all nodes see the same data at same time,怎么理解這句話? 假設(shè)在一個有N個節(jié)點的集群環(huán)境下,同一時刻訪問不同的節(jié)點的同一個數(shù)據(jù),返回的值應(yīng)該一樣.

例如一個寫請求發(fā)送到節(jié)點A將值從5改為6,在寫操作之前假設(shè)所有節(jié)點上的數(shù)據(jù)都是5,那么當(dāng)節(jié)點A把值從5改為6之后,還需要把這個操作同步到其他的節(jié)點,直到其他節(jié)點都同步成功后,才能返回客戶端寫成功,并且在這個過程中節(jié)點如果已經(jīng)修改為6的值不能被外界看到.

  • 可用性 (Availablity)

可用性是指 read and write always succeed.是指服務(wù)永遠(yuǎn)可以響應(yīng)客戶端的請求,這個也是在集群環(huán)境下,當(dāng)一個節(jié)點不可用的時候,可以將請求發(fā)到其他節(jié)點上,一個節(jié)點不可用不一定是因為宕機,網(wǎng)絡(luò)不通等原因,也有可能是這個節(jié)點的數(shù)據(jù)沒有復(fù)制全.當(dāng)然狹義上的可用性也可以指代請求響應(yīng)時間,在現(xiàn)在高并發(fā)的環(huán)境下,如果一個請求的響應(yīng)時間太長,一方面影響用戶體驗倒置不可用,另一方面由于響應(yīng)時間太長倒置QPS太低,當(dāng)客戶端大量請求打過來的時候會將服務(wù)端打垮倒置機器不可用.
在一個分布式系統(tǒng)中,上下游系統(tǒng)任何一個節(jié)點出故障都可能倒置整個系統(tǒng)不可用,像數(shù)據(jù)庫,負(fù)載均衡,緩存,微服務(wù)中的某一個服務(wù),所以分布式系統(tǒng)在滿足高可用的需求上需要進行彈性設(shè)計,即可以支持服務(wù)降級,限流,以及監(jiān)控等.

  • 分區(qū)容忍性

分區(qū)容忍性是指 the system continue to operate despite arbitrary message loss or failure of part of the system, 也就是說當(dāng)網(wǎng)絡(luò)存在丟包和某幾個節(jié)點網(wǎng)絡(luò)不可達(dá)的情況下,整個服務(wù)集群可以繼續(xù)保持可用,operate是經(jīng)營,管理的意思,despite是盡管的意思,arbitrary是隨意的意思.

在分布式系統(tǒng)中,只有多個節(jié)點才能保證一個節(jié)點掛了另外一個節(jié)點可以頂上來實現(xiàn)高可用,不能存在單點,而為了滿足高可用必須保證每個節(jié)點上都有完整的數(shù)據(jù),也就是需要各個節(jié)點之間需要進行數(shù)據(jù)復(fù)制,這個復(fù)制操作只能通過網(wǎng)絡(luò),而網(wǎng)絡(luò)環(huán)境是不穩(wěn)定的,光纖被挖,機房斷電,操作員失誤等是機器無法預(yù)料的,當(dāng)網(wǎng)絡(luò)的不可靠就可能引起數(shù)據(jù)不同步,數(shù)據(jù)不同步就會導(dǎo)致節(jié)點不可用,所以在現(xiàn)代軟件架構(gòu)中,會在三者之間做一個權(quán)衡,例如對一致性,可以放低要求做到弱一致性,就是運行中間短暫的數(shù)據(jù)不一致,而最終數(shù)據(jù)還是一致的,所以出來了一個變種就是BASE.
basic avalilablity,基本可用意味著系統(tǒng)可以出現(xiàn)短暫的不可用,后面會恢復(fù),Soft-state 軟狀態(tài),是在有狀態(tài)和無狀態(tài)之間的一種中間態(tài),運行應(yīng)用短暫的保存一小部分?jǐn)?shù)據(jù)和狀態(tài).Eventual Consistency 最終一致性,系統(tǒng)在一個短暫的時間段內(nèi)是不一致的,但最終會恢復(fù)一致.

2PC

兩階段提交Two Phase Commit,一階段,協(xié)調(diào)者發(fā)起prepare操作,參與者將操作結(jié)果同步給協(xié)調(diào)者,二階段 由協(xié)調(diào)者根據(jù)上一階段操作的結(jié)果,決定提交還是回滾.也就是所有參與者都返回成功就提交,有一個參與者返回失敗就回滾.

該協(xié)議比較簡單,我們對照下圖進行理解,就不在具體分析過程了.

image

兩階段提交有以下缺點

  • 1.單點故障 一旦協(xié)調(diào)者發(fā)生故障,那么事物的其他參與者會一直阻塞下去,尤其在第二階段,由于一階段已經(jīng)將資源鎖定,協(xié)調(diào)者可以設(shè)計為分布式集群部署,當(dāng)master節(jié)點宕機,可以重新選舉一個master,但是協(xié)調(diào)者就需要將參與者的返回結(jié)果狀態(tài)進行持久化,并且和其他協(xié)調(diào)者進行同步,這個過程又會涉及新的一致性問題.

  • 2.同步阻塞問題,阻塞周期長,當(dāng)?shù)谝粋€參與者鎖定資源后需要等到階段2提交后才會釋放資源

  • 3.可能出現(xiàn)數(shù)據(jù)不一致,當(dāng)協(xié)調(diào)者發(fā)出commit后,某一個參與者由于網(wǎng)絡(luò)原因沒有收到,而其他參與者已經(jīng)commit,于是出現(xiàn)數(shù)據(jù)不一致

基于兩階段提交的缺陷,后來提出了三階段提交的方案,就是在2pc的基礎(chǔ)上增加了一個CanCommit的詢問動作,這一步可以保證后續(xù)的commit操作大概率會成功,例如從用戶賬戶扣50塊錢,而canCommit詢問的時候發(fā)現(xiàn)賬戶只有40,就會直接返回不可以提交,提高成功率,同時減少了鎖定資源的時間,但是3PC并沒有解決2PC的問題
當(dāng)然三階段也設(shè)置了超時時間,二階段只有協(xié)調(diào)中的請求會有超時時間.

XA

X/OPEN 組織定義了一套分布式事物處理模型,也就是Distributed Trasaction Processing Reference Model 簡稱DTP模型,并定義了兩套協(xié)議,分別是XA,TX

image

從上圖可以看出,DTP使用了兩階段提交協(xié)議,并提出了RM,AP,TM的概念,目前mysql的innodb有對XA的支持,spring也封裝了XA相關(guān)的接口,但是XA存在兩階段提交相關(guān)問題,所以目前在互聯(lián)網(wǎng)公司高并發(fā)的場景下并沒有太普及.

業(yè)務(wù)補償

在分布式環(huán)境下,由于在大多數(shù)情況下我們無法做到強一致性的ACID,特別是我們的系統(tǒng)跨越多個系統(tǒng),而且有些系統(tǒng)可能還需要調(diào)用其他公司的服務(wù),我們要保證一個事物全部執(zhí)行成功,要么全部回滾,在哪些場景需要回滾,哪些場景不需要回滾可能會和業(yè)務(wù)強相關(guān).

業(yè)務(wù)補償?shù)脑O(shè)計,就是業(yè)務(wù)正常執(zhí)行,當(dāng)遇到某個分支場景不可用的時候就啟用回滾流程,將之前的流程進行逆向操作.我們用客戶購買理財產(chǎn)品的例子,第一步 下訂單,第二步,扣產(chǎn)品份額,第三步,支付.第四部給用戶加持倉,第五步將訂單狀態(tài)改為成功.

image
  • 1.扣份額失敗則直接修改訂單狀態(tài)為失敗

  • 2.如果支付失敗,則回滾產(chǎn)品份額,修改訂單狀態(tài)為失敗

  • 3.如果加持倉失敗就一直重試

  • 4.如果修改訂單狀態(tài)失敗就一直重試

  • 5.如果支付返回處理中就不停輪詢支付狀態(tài)直到有確定結(jié)果

根據(jù)以上流程我們可以總結(jié)下采用補償方案的設(shè)計重點

  • 1.可以采用工作量引擎進行業(yè)務(wù)補償操作

  • 2.業(yè)務(wù)服務(wù)需要支持重試

  • 3.業(yè)務(wù)服務(wù)要冪等

  • 4.業(yè)務(wù)服務(wù)要提高回滾接口,如上面的回滾標(biāo)的份額

  • 5.有些業(yè)務(wù)失敗可能不需要進行回滾,所以業(yè)務(wù)補償和業(yè)務(wù)關(guān)聯(lián)緊密,很難用中間件解決

  • 6.有些業(yè)務(wù)可能沒有明確結(jié)果,需要采用JOB進行狀態(tài)查詢

  • 7.業(yè)務(wù)服務(wù)要支持狀態(tài)查詢的接口

  • 8.補償業(yè)務(wù)不一定是強相關(guān)或依賴的,有些服務(wù)可以并行執(zhí)行可以提高效率

TCC

TCC是(Try Commit Cancel)的簡稱,源于國外的一篇論文,最早由阿里的程立博士在infoQ的一篇介紹中引入國內(nèi),目前國內(nèi)大多數(shù)互聯(lián)網(wǎng)公司都在采用這種方案.

我們用一個賬戶A轉(zhuǎn)賬給賬戶B100元為例子

  • Try 預(yù)留資源,完成一致性檢查 (賬戶A可用余額減少100,凍結(jié)金額增加100元,賬戶B凍結(jié)金額增加100)

  • Commit 執(zhí)行業(yè)務(wù),不做任何一致性檢查,而且只能使用try中預(yù)留的資源 (賬戶A凍結(jié)金額減少100,賬戶B可用余額增加100,凍結(jié)金額減少100)

  • Cancel 釋放TRY階段預(yù)留的資源 (賬戶A可用余額增加100,凍結(jié)金額減少100,賬戶B凍結(jié)金額減少100)

image

TCC模式本質(zhì)上也是兩階段提交,上圖對比了TCC和XA方案,兩者一個是業(yè)務(wù)操作一個是針對數(shù)據(jù)庫,所以TCC方案不會采用數(shù)據(jù)庫本地事物去鎖定資源,所以使用TCC也需要各個接口能夠支持冪等,并且能夠重試,而且需要提供狀態(tài)查詢接口,不然在網(wǎng)絡(luò)超時后,發(fā)起方不確定分支事物是執(zhí)行成功還是失敗.

SAGA

saga提供了一種根據(jù)工作流引擎進行管理事物的提交和回滾,流程上類似于上面的事物補償機制

可靠消息最終一致性

大多時候我們希望多個業(yè)務(wù)能夠并行處理,這個時候我們可以借助消息隊列來異步通知其他應(yīng)用進行相應(yīng)的操作,那么怎么保證本地事物和接受消息的應(yīng)用上處理的服務(wù)要么全部成功,要么全部失敗呢,我們根據(jù)之前的最終一致性方案,允許兩個分支事物可以先后執(zhí)行,但是最終肯定會執(zhí)行,不會不執(zhí)行.

這種方案其實也是業(yè)務(wù)補償?shù)囊环N,只是借助消息隊列進行解耦和異步通知,下面我們分析下這種方案的兩個難點.

  • 如何保證消息投遞和本地事物要么全部成功,要么全部失敗
    首先,事物管理器先發(fā)送一條記錄給消息服務(wù),消息服務(wù)將這條消息存儲,狀態(tài)記錄為待確認(rèn),然后執(zhí)行本地數(shù)據(jù)庫操作,然后發(fā)送確認(rèn)消息給消息服務(wù),這兩個動作可以放在一個本地事物內(nèi),如果本地數(shù)據(jù)庫操作成功,消息確認(rèn)失敗,就將本地數(shù)據(jù)庫操作回滾,如果本地數(shù)據(jù)庫操作失敗就發(fā)消息給消息服務(wù)請求刪除消息.
    如果發(fā)送給消息服務(wù)的確認(rèn)和刪除由于網(wǎng)絡(luò)沒有回應(yīng),那么就需要把在消息服務(wù)里定時輪詢事物的狀態(tài),也就是消息服務(wù)去反查服務(wù)發(fā)送方,然后決定提交消息或者刪除消息,所以消息服務(wù)和服務(wù)發(fā)送方應(yīng)該維護一個雙向通道,rocketMQ的做法是將PID和對應(yīng)的channel緩存起來

  • 如何保證消息百分百會被消費
    消費端消費完消息后,給個確認(rèn)消息給消息服務(wù),消息服務(wù)不停輪詢當(dāng)前消息列表中,查看是否存在沒有消費完成的消息,如果存在就讓消息隊列重新通知一次.

  • 如何保證消息不會重復(fù)消費

原則上我們希望分支事物自己能夠支持冪等,如果一定要讓中間件去重,實際上消息隊列去重的代價是很大的,會犧牲掉高可用性,我們可以在應(yīng)用層維護一張表去存儲已經(jīng)處理的消息,這樣可以根據(jù)消息ID去重.

FESCAR

FESCAR (Fast easy commit and rollback),是阿里GTS的社區(qū)開源版本,基于現(xiàn)有的分布式事物解決方案,要么像XA這種會有嚴(yán)重的性能問題,要么像業(yè)務(wù)補償,TCC,SAGA,可靠消息保證等,這種需要根據(jù)業(yè)務(wù)場景在應(yīng)用中進行定制,我們不希望引入微服務(wù)后給業(yè)務(wù)層帶來額外的研發(fā)負(fù)擔(dān),另外一方面不希望引入分布式事物后拖慢業(yè)務(wù),所以FesCar的初衷就是對業(yè)務(wù)0侵入,并且高性能.

下面先通過官方上的介紹,看下fescar的思想,后面在結(jié)合代碼看下fescar的具體實現(xiàn)細(xì)節(jié)

我們先簡單回顧下XA里面分布式事物的三個角色,TM事物管理器.負(fù)責(zé)發(fā)起一個全局事物,TC事物協(xié)調(diào)器,獨立在應(yīng)用層外,負(fù)責(zé)維護全局事物和分支事物的狀態(tài),來決策提交還是回滾,RM:資源管理器,存儲實際狀態(tài)的容器,像硬盤,數(shù)據(jù)庫,消息中間件,內(nèi)存等都可以稱為RM,Fescar將RM作為一個二方包的形式遷入到了應(yīng)用層,作為應(yīng)用層和TC進行通訊的代理,并且協(xié)助本地事物處理.

  • 1.TM向TC申請開啟一個全局事物,全局事物創(chuàng)建成功,并返回一個唯一的XID

  • 2.XID在微服務(wù)調(diào)用鏈路中進行傳遞,dubbo可以通過filter方式,spring cloud也很方便,RPC服務(wù)端收到XID后存放在本地線程局部變量threadlocal中

  • 3.分支事物RM向TC注冊分支事物,將其納入XID對應(yīng)的全局事物管理中

  • 4.TM向TC發(fā)起針對XID的全局事物的提交或者回滾操作

  • 5.TC調(diào)用XID管轄下的分支事物完成提交和回滾
    image

FESCAR取消了數(shù)據(jù)庫層的prepare操作,而是直接進行commit操作,這樣就不會帶來昂貴的數(shù)據(jù)庫鎖開銷,而每一個commit操作對應(yīng)的都是數(shù)據(jù)庫的本地事物,這個改變是Fescar性能高的主要原因,同時使他犧牲了隔離性,導(dǎo)致目前Fescar只能支持讀未提交的隔離級別,如果要實現(xiàn)讀已提交需要應(yīng)用層做一些定制.

image
image

Fescar的RM插件,重新實現(xiàn)了一遍JDBC,從而攔截掉數(shù)據(jù)庫的sql進行解析,并生成undolog,以及事物提交后的增強處理,這種設(shè)計使應(yīng)用方完全無感,只需要開啟一個全局事物

image

undolog是存儲在業(yè)務(wù)方本地的數(shù)據(jù)庫實例里,這樣業(yè)務(wù)更新和插入undolog在一個本地事物內(nèi),可以保證事物回滾的時候一定有undolog.

image

目前fescar剛開源,在可靠性上還需要驗證,目前社區(qū)也在計劃完善一些新功能.

下面我們分析下Fescar的一些核心功能

事物管理器

我們先看下全局事物的三個核心接口,begin,commit,rollback,我在代碼中都加了注釋

public interface GlobalTransaction {    /**     * Begin a new global transaction with default timeout and name.     *     * 開啟一個全局事物,像TC發(fā)起請求,返回一個XID,并將這個XID進行緩存到ThreadLocal     *     * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown     * out.     */    void begin() throws TransactionException;    /**     * Commit the global transaction.     *     * 提交一個全局事物,將XID發(fā)給TC,并清除threadlocal里的緩存     *     * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown     * out.     */    void commit() throws TransactionException;    /**     * Rollback the global transaction.     *     * 回滾一個全局事物,將XID發(fā)給TC,并清除threadlocal里的緩存     *     * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown     * out.     */    void rollback() throws TransactionException;}

然后我們在看下事物處理模板,也就是我們使用的入口,也是接入fescar唯一要關(guān)心的一個地方

public class TransactionalTemplate {    /**     * Execute object.     *     * @param business the business 只需要傳人一個TransactionalExecutor就可以了,業(yè)務(wù)實現(xiàn)放在execute里面就可以了     * @return the object     * @throws ExecutionException the execution exception     */    public Object execute(TransactionalExecutor business) throws TransactionalExecutor.ExecutionException {        // 1. get or create a transaction        GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();        // 2. begin transaction        try {            tx.begin(business.timeout(), business.name());        } catch (TransactionException txe) {            throw new TransactionalExecutor.ExecutionException(tx, txe,                TransactionalExecutor.Code.BeginFailure);        }        Object rs = null;        try {            // Do Your Business            rs = business.execute();        } catch (Throwable ex) {            // 3. any business exception, rollback.            try {                tx.rollback();                // 3.1 Successfully rolled back                throw new TransactionalExecutor.ExecutionException(tx, TransactionalExecutor.Code.RollbackDone, ex);            } catch (TransactionException txe) {                // 3.2 Failed to rollback                throw new TransactionalExecutor.ExecutionException(tx, txe,                    TransactionalExecutor.Code.RollbackFailure, ex);            }        }        // 4. everything is fine, commit.        try {            tx.commit();        } catch (TransactionException txe) {            // 4.1 Failed to commit            throw new TransactionalExecutor.ExecutionException(tx, txe,                TransactionalExecutor.Code.CommitFailure);        }        return rs;    }}

有沒有發(fā)現(xiàn)跟我們平時使用的JTA用法一樣,由于分支事物是RPC調(diào)用,所以存在網(wǎng)絡(luò)超時的情況,所以分支事物如果超時了,即使分支事物的本地執(zhí)行成功了,全局事物一樣會進行回滾,因為這里會捕獲這個超時異常,后面我們在分析為什么要這樣設(shè)計.

xid怎么在rpc中傳遞

我們在做分布式鏈路監(jiān)控的時候,也需要在rpc之間傳遞一個traceid,方法類似,如果是dubbo,我們可以寫一個filter

@Activate(group = { Constants.PROVIDER, Constants.CONSUMER }, order = 100)public class TransactionPropagationFilter implements Filter {    private static final Logger LOGGER = LoggerFactory.getLogger(TransactionPropagationFilter.class);    @Override    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {        String xid = RootContext.getXID();        String rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID);        if (LOGGER.isDebugEnabled()) {            LOGGER.debug("xid in RootContext[" + xid + "] xid in RpcContext[" + rpcXid + "]");        }        boolean bind = false;        if (xid != null) {            RpcContext.getContext().setAttachment(RootContext.KEY_XID, xid);        } else {            if (rpcXid != null) {                RootContext.bind(rpcXid);                bind = true;                if (LOGGER.isDebugEnabled()) {                    LOGGER.debug("bind[" + rpcXid + "] to RootContext");                }            }        }        try {            return invoker.invoke(invocation);        } finally {            if (bind) {                String unbindXid = RootContext.unbind();                if (LOGGER.isDebugEnabled()) {                    LOGGER.debug("unbind[" + unbindXid + "] from RootContext");                }                if (!rpcXid.equalsIgnoreCase(unbindXid)) {                    LOGGER.warn("xid in change during RPC from " + rpcXid + " to " + unbindXid);                    if (unbindXid != null) {                        RootContext.bind(unbindXid);                        LOGGER.warn("bind [" + unbindXid + "] back to RootContext");                    }                }            }        }    }}

RootContext是fescar的threadlocal容器,RpcContext是dubbo得threadcontext容器,Attachment可以讓dubbo在遠(yuǎn)程調(diào)用過程中攜帶更多地參數(shù),服務(wù)調(diào)用方傳遞xid,服務(wù)提供方接收xid并保存,服務(wù)調(diào)用結(jié)束記得清空threadlocal以防止內(nèi)存泄露.

分支在一階段的處理流程

PreparedStatementProxy -> ExecuteTemplate -> UpdateExecutor ->ConnectionProxy

  • 0.sql進入jdbc的PreparedStatement中,然后這個jdbc對象被PreparedStatementProxy代理,進入他的execute方法

  • 1.查看自己是否在一個Fescar的全局事物中,根據(jù)線程本地變量threadlocal中是否存在全局事物id進行判斷,具體代碼在ExecuteTemplate中

  • 2.如果存在,就進入jdbc代理中,解析sql,根據(jù)類型選擇不同的執(zhí)行器

image

3.使用 select for update 查詢獲取原始當(dāng)前update的記錄當(dāng)前值,就是修改前的值

  • 4.執(zhí)行原本的update修改記錄的值

  • 5.使用 select for update 查詢獲取update后的值,就是修改后的值

  • 6.根據(jù)修改前和修改后的值,生成undolog,并根據(jù)主鍵的值生成lockkey放入context中

  • 5.向TC請求,注冊分支事物并將lockkey傳給TC加上全局鎖

  • 6.如果注冊并且獲取鎖成功,將把undolog插入數(shù)據(jù)庫中

  • 7.提交本地事物,并向TC反饋執(zhí)行結(jié)果 (本地事物中包含原本的執(zhí)行語句和插入undolog)

image

我們發(fā)現(xiàn)分支事物上的RM操作是基于statement和connection,在原來的connection上做了增強,用的時同一個物理connection,所以分支應(yīng)用上的分支的定義為一個本地事物,所以
在一個RPC實現(xiàn)中,一個方法中如果存在多個sql語句,那么將會注冊多個分支,向TC注冊多次,如果這個方法在一個本地事物中,那么即使多個sql最終一起提交,并且只會向TC注冊一次,undolog也是一起插入數(shù)據(jù)庫的,這個地方注意下,如果這個connection是自動提交的,為了讓update語句和插入undolog放在一個本地事物中,所以會將connection改為非自動提交,開啟一個事物,在用完只會在改為自動提交,不要影響應(yīng)用程序.

image

我們回顧下分支一階段的處理流程
image

二階段的處理流程

  • 1.如果所有的分支都返回執(zhí)行成功,TC將立即釋放全局鎖,并且TC異步通知分支刪除undolog

  • 2.如果有一個分支事物返回執(zhí)行失敗,則TC發(fā)起請求執(zhí)行undolog,所有undolog執(zhí)行成功了,釋放鎖,異步刪除undolog

如何實現(xiàn)讀已提交

目前官方給出的方案,在需要進行讀取全局事物已經(jīng)提交的記錄的話,需要將select 語句后面加上for update,fescar發(fā)現(xiàn)加上排他鎖后,會去TC獲取對應(yīng)的鎖,如果沒有鎖上就執(zhí)行,鎖上就自旋等待,為了避免讀取未提交的記錄,后面全局事物回滾了,就導(dǎo)致臟讀,當(dāng)然目前可能大部分應(yīng)用都可以接受這種情況,例如在扣商品份額的時候都會最終在校驗一次,
當(dāng)然我們也可以借助undolog實現(xiàn)一個read-view,讓這條sql語句讀取到這個全局事物還沒有執(zhí)行之前的數(shù)據(jù).

目前fescar的實現(xiàn)方式是在sql解析后如果發(fā)現(xiàn)是select for update語句,將會進入SelectForUpdateExecutor執(zhí)行器,不管是不是在一個全局事物中,都需要去TC看下是否被鎖住,這里將不會獲取鎖,只是校驗鎖是否會釋放.

如果分支超時,實際已經(jīng)執(zhí)行成功,那么肯定是已經(jīng)向TC注冊成功的,那么如果TM發(fā)起回滾,分支可以正?;貪L,沒有毛病,如果超時后,分支本地事物還沒有提交,那么回滾請求已經(jīng)到達(dá)分支,那么將會回滾失敗,但是TC會重試不停進行回滾.

實現(xiàn)HA的挑戰(zhàn)

TC目前是一個單點,如果需要集群部署,則需要一個服務(wù)發(fā)現(xiàn)的系統(tǒng),讓TC可以自動擴展,應(yīng)用不需要關(guān)心TC具體節(jié)點,而TC的全局鎖就不能直接放內(nèi)存了,可能需要借助第三方存儲系統(tǒng),mysql或者etcd

實現(xiàn)XA的方案

可能需要在分支事物中,當(dāng)解析到在一個全局事物中,不會進行commit,等到所有分支都返回成功了,事物管理器發(fā)起commit請求給TC,然后TC在通知各個分支進行提交,和rollback流程差不多

一致性協(xié)議 raft

由于paxos算法太復(fù)雜,我們分析下raft協(xié)議是如何保證分布式集群下數(shù)據(jù)復(fù)制的一致性的.

  • 1.通過選舉保證集群中只有一個leader,只有l(wèi)eader對外提供寫服務(wù),leader將日志廣播給follower,每一條日志都按順序維護在一個隊列里,所有節(jié)點的隊列里有一個index來控制前面的是已經(jīng)提交的,后面的是沒提交的,提交代表已經(jīng)有超過半數(shù)的節(jié)點應(yīng)答,leader先把日志復(fù)制給所有follower,在收到半數(shù)節(jié)點應(yīng)答后在通知follower,index位置來控制那些日志是已經(jīng)提交的,只有提交過的日志,follower才會提供給應(yīng)用方使用

  • 2、選舉過程,當(dāng)一個leader長時間沒有應(yīng)答時,所有的follower都可以成為candidate,向其他follower節(jié)點發(fā)送投票請求,如果超過半數(shù)follower節(jié)點應(yīng)答后這個candidate就會升級為leader,為了避免所有的follower節(jié)點已經(jīng)作為candidate發(fā)起投票,引入隨機超時機制,每個follower和leader的超時時間在一定范圍內(nèi)隨機,當(dāng)candidate發(fā)起投票沒有結(jié)果時,隨機等待一定時間。

  • 3.candidate的日志長度要大于等于半數(shù)follower節(jié)點的日志才能成為leader,所以發(fā)起投票的時候如果follower發(fā)現(xiàn)自己的日志長度大于后選擇的就會投反對票

  • 4.日志補齊,當(dāng)leader發(fā)生故障的時候,各個follower上的狀態(tài)不一樣,所以新leader產(chǎn)生后需要補齊所有follow的日志,而且新leander的日志也不一定是最長的,但是foller日志上面沒有的日志肯定是未提交的,這個時候補齊就可以

  • 5.老leader復(fù)活,每一次選舉到下一次選舉的時間稱為一個term任期,每一個任期內(nèi)都會維護一個數(shù)字并自增,當(dāng)leader發(fā)送復(fù)制請求的時候會帶上term編號,如果follower發(fā)現(xiàn)term比自己小就拒絕,

image

raft設(shè)計中只有兩個rpc請求,一個選舉,一個復(fù)制日志,復(fù)制日志順便做了心跳檢測,當(dāng)沒有日志復(fù)制的時候發(fā)送空日志,觸發(fā)選舉的唯一條件是 election timeout到期,每一個節(jié)點的 election timeout都會將自己設(shè)置為candidate然后發(fā)起投票,每個節(jié)點的election timeout都會存在一個隨機值,所以不同,當(dāng)一個節(jié)點被選為leader后會定期向所有的follower發(fā)送心跳包,follower收到心跳包后會延長election timeout的值。節(jié)點選舉的時候term值大的會優(yōu)先于term值小的,每一輪選舉term值都會加1.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.分布式事務(wù) 在去年的時候我寫過一篇關(guān)于分布式事務(wù)的文章[再有人問你分布式事務(wù),把這篇扔給他](https://...
    Java機械師閱讀 811評論 0 11
  • 在這個滿世界被霧霾污染的季節(jié)我們手牽手找個理由去逛街用平時十分之一的速度卻感覺很快走完了長長的旅程 因為有彼此在身...
    瑜伽散人閱讀 198評論 0 3
  • 喏~這么會玩的穿越小說就是這本啦↓↓↓ 主角也是夠慘,穿越到不知名的朝代,瞧瞧碰上的都是什么人!父親愚孝、叔嬸厲害...
    酷聽聽書閱讀 579評論 0 0
  • 01 公司上下都知道卓安車禍住院,大家工作都非常賣力。尤其是潘慧可做完自己的分內(nèi)事還幫著其它部門的同事做方案,出策...
    小鹿故事集閱讀 446評論 0 0
  • Mac下修改host文件,更改項目的本地映射地址,不生效,執(zhí)行telnet的時候,發(fā)現(xiàn)總會被其他代理改攔截了,導(dǎo)致...
    云高風(fēng)輕閱讀 5,289評論 0 2

友情鏈接更多精彩內(nèi)容