camunda 錯(cuò)誤處理、補(bǔ)償機(jī)制和分布式事務(wù)支持

前言

有段時(shí)間沒(méi)寫(xiě)文章了,這段時(shí)間一直在看camunda服務(wù)編排的一些東西,之前沒(méi)有接觸過(guò)工作流引擎和服務(wù)編排類(lèi)似的東西,花了一點(diǎn)時(shí)間來(lái)熟悉和理解。由于我們需要運(yùn)用camunda來(lái)做服務(wù)編排,因此對(duì)于一個(gè)編排,其中的工作單元是分布在多個(gè)微服務(wù)內(nèi)部的業(yè)務(wù)調(diào)用,如何在服務(wù)編排中保持分布式事務(wù)的支持就成了我的關(guān)注點(diǎn)。camunda提供了補(bǔ)償機(jī)制和對(duì)應(yīng)的SAGA 模式來(lái)完成分布式事務(wù)的支持。本篇文章將對(duì)這一塊進(jìn)行描述和探討。

錯(cuò)誤處理機(jī)制

對(duì)于分布式事務(wù)來(lái)說(shuō),歸根結(jié)底我們要考慮的就是,微服務(wù)調(diào)用出錯(cuò)的時(shí)候,我們的流程應(yīng)該如何流轉(zhuǎn),并在這個(gè)流轉(zhuǎn)過(guò)程中保證事務(wù)的一致性。因此我們第一個(gè)考慮的就是camunda本身的錯(cuò)誤處理機(jī)制是什么。

事務(wù)回滾

第一種錯(cuò)誤處理方式是事務(wù)回滾,要理解這個(gè)方式我們首先要看看camunda中事務(wù)是怎么組織的。在camunda中,流程的運(yùn)行是被封裝在一個(gè)個(gè)客戶(hù)線(xiàn)程(client thread)中??梢赃@么理解,當(dāng)一個(gè)啟動(dòng)或者繼續(xù)流程的請(qǐng)求到來(lái)之時(shí),在我們的服務(wù)器的處理線(xiàn)程中(類(lèi)似于tomcat中的http-thread-pool開(kāi)頭的那些線(xiàn)程),我們通過(guò)調(diào)用類(lèi)似于下面的API

runtimeService.startProcessInstanceByKey(...)

來(lái)發(fā)起一個(gè)流程,這時(shí)候整個(gè)流程就完全運(yùn)行在http-thread-pool線(xiàn)程中,這就稱(chēng)之為我們借用了一個(gè)客戶(hù)線(xiàn)程,在這個(gè)線(xiàn)程中,camunda始終會(huì)開(kāi)啟一個(gè)數(shù)據(jù)庫(kù)事務(wù),并在這個(gè)事務(wù)下進(jìn)行流程的運(yùn)轉(zhuǎn)。
在這個(gè)情況下,流程會(huì)一直運(yùn)轉(zhuǎn)到下一個(gè)等待狀態(tài)(wait status 代表流程會(huì)在后面的某個(gè)時(shí)期繼續(xù)),然后完成狀態(tài)的持久化到數(shù)據(jù)庫(kù)中,結(jié)束事務(wù)。在bpmn標(biāo)準(zhǔn)中,有下列幾個(gè)等待狀態(tài):

  • Tasks:
    1. 接受任務(wù)(receive task)
    2. 用戶(hù)任務(wù)(user task)
    3. 外部服務(wù)任務(wù)(service task: external task)
  • Events:
    1. 消息事件(message event)
    2. 定時(shí)事件(timer event)
    3. 信號(hào)事件(signal event)
  • Gateway
    1. 基于時(shí)間的邏輯門(mén)(Event Based Gateway)

我們可以通過(guò)下圖來(lái)理解這個(gè)過(guò)程:


事務(wù)模型

第一步,我們發(fā)起了完成task的請(qǐng)求,然后流程繼續(xù)運(yùn)行直到到達(dá)了定時(shí)等待事件,此時(shí)這個(gè)客戶(hù)線(xiàn)程中的工作流運(yùn)行已經(jīng)結(jié)束,我們完成當(dāng)前的事務(wù)并提交,然后返回控制給調(diào)用方,等待定時(shí)任務(wù)的觸發(fā)。
這是正常情況,當(dāng)我們出現(xiàn)異常的時(shí)候會(huì)怎么辦呢?我們通過(guò)下圖來(lái)進(jìn)行理解:


異常事務(wù)回滾

當(dāng)進(jìn)行到發(fā)送任務(wù)(send invoice to customer)時(shí)觸發(fā)了異常,這時(shí)候客戶(hù)線(xiàn)程里的事務(wù)會(huì)完全得到回滾,然后把異常信息拋出給調(diào)用方,當(dāng)下次該流程的請(qǐng)求進(jìn)來(lái)的時(shí)候,會(huì)發(fā)現(xiàn)流程的狀態(tài)點(diǎn)仍然是最開(kāi)始的那個(gè)用戶(hù)任務(wù)(provide shipping address)。這個(gè)就類(lèi)似于游戲中的保存點(diǎn),玩過(guò)游戲的同學(xué)都知道,我們?cè)谶@個(gè)保存點(diǎn)到下一個(gè)保存點(diǎn)游戲過(guò)程中,如果出現(xiàn)什么意外(游戲人物掛了,機(jī)器死機(jī)了,老媽回來(lái)了),我們下次進(jìn)入游戲,還是會(huì)回到最近的一個(gè)保存成功的保存點(diǎn),繼續(xù)玩。
這個(gè)錯(cuò)誤處理機(jī)制可以說(shuō)是非常簡(jiǎn)潔和直觀了,它異常處理完全交給了調(diào)用方,并保證了狀態(tài)的回滾,但是并沒(méi)有做出任何分布式事務(wù)一致性的支持。比方說(shuō)如果一個(gè)流程 A -> B-> C,當(dāng)C拋出異常的時(shí)候,我們并沒(méi)有提供把A和B任務(wù)中的操作回滾或者修正的能力,因此這個(gè)方案只是一個(gè)最初級(jí)的方案。

異常捕獲和邏輯判斷

我們知道我們自己diy的task,實(shí)際上就是一段段java代碼,因此,我們可以在我們的代碼中捕獲異常,并且通過(guò)設(shè)置執(zhí)行上下文中的參數(shù),來(lái)標(biāo)定我們的程序遭遇異常,一次來(lái)作為邏輯判斷的依據(jù),如下圖所示:


異常捕獲和邏輯判斷

這種方式怎么說(shuō)呢,是個(gè)方案,但是對(duì)于我們代碼的耦合較高,而且對(duì)于我們流程來(lái)說(shuō)會(huì)造成比較大的干擾,因?yàn)槿绻阋黾?xì)粒度,task級(jí)別的錯(cuò)誤排查和修正,你就免不了在每個(gè)task后面都加上這么一層邏輯判斷,會(huì)對(duì)編排流程本身的邏輯帶來(lái)困擾,這樣做出來(lái)的流程圖也不夠清晰。

BPMN 2.0 錯(cuò)誤事件

針對(duì)上述方案的局限性,在BPMN 2.0規(guī)范中給出了錯(cuò)誤事件來(lái)作為顯式的處理錯(cuò)誤的方案。以下圖為例:

錯(cuò)誤事件

一個(gè)典型的場(chǎng)景是如果我們要針對(duì)一個(gè)子流程中的錯(cuò)誤來(lái)做出處理,一個(gè)比較簡(jiǎn)單的方案是在這個(gè)子流程上疊加一個(gè)錯(cuò)誤邊界事件(error boundary event),通過(guò)定義這樣一個(gè)事件可以針對(duì)子流程中拋出的錯(cuò)誤來(lái)進(jìn)行額外的處理。需要注意的是,在這個(gè)圖中,如果出發(fā)了錯(cuò)誤邊界事件 進(jìn)入到錯(cuò)誤處理流程,那本身的執(zhí)行過(guò)程將會(huì)遭到中斷(interrupting),因此錯(cuò)誤事件也被稱(chēng)作是一種中斷事件(interrupting event),關(guān)于這一塊我在后續(xù)文章中會(huì)提及。
其實(shí)大家仔細(xì)看也會(huì)發(fā)現(xiàn),錯(cuò)誤事件,其實(shí)可以用前面的異常捕獲和判斷來(lái)等價(jià)完成,比如下圖:
錯(cuò)誤處理

因此我們可以知道,錯(cuò)誤事件其實(shí)可以看做是我們針對(duì)錯(cuò)誤的一個(gè)簡(jiǎn)化的建模手段,并且從語(yǔ)義上能夠給使用者一個(gè)清晰的感受,明晰了錯(cuò)誤處理流程和正常業(yè)務(wù)處理流程的邊界。

補(bǔ)償機(jī)制

在了解了camunda的錯(cuò)誤處理機(jī)制之后,我們就會(huì)想在錯(cuò)誤處理機(jī)制之上完成task的修正補(bǔ)償并最終支持分布式事務(wù),因此BPMN規(guī)范提出了補(bǔ)償事件(compensation event),通過(guò)對(duì)任務(wù)或者子流程定義一個(gè)compensation事件 和它對(duì)應(yīng)的處理單元來(lái)完成修正補(bǔ)償。camunda官方也提供了一個(gè)demo,github地址在此 。我們可以來(lái)分析下:

補(bǔ)償機(jī)制

在上述這個(gè)流程中,每個(gè)task都有一個(gè)補(bǔ)償邊界事件(compensation boundary event),按照補(bǔ)償事件的定義,補(bǔ)償事件只會(huì)在他標(biāo)識(shí)的task成功執(zhí)行完畢之后才有可能被觸發(fā)(A compensation boundary event can only be triggered after the activity to which it is attached completes successfully.)。這個(gè)跟之前一般的邊界事件是不一樣的。然后還有一個(gè)需要注意的是,在上面的流程圖中有一個(gè)虛線(xiàn)框框定的區(qū)域,這個(gè)區(qū)域在BPMN規(guī)范中被稱(chēng)為消息驅(qū)動(dòng)的子流程(event-driven subprocess)。這個(gè)子流程的用處就是在整個(gè)流程中如果出現(xiàn)了某個(gè)消息,就會(huì)觸發(fā)這個(gè)流程。
在上面這個(gè)流程里面,這個(gè)消息驅(qū)動(dòng)的子流程的開(kāi)始事件是一個(gè)錯(cuò)誤事件(error start event),因此,一旦流程中出現(xiàn)了錯(cuò)誤(錯(cuò)誤類(lèi)型需要在錯(cuò)誤事件中指定),這個(gè)消息子流程將就會(huì)啟動(dòng),而本身的執(zhí)行過(guò)程就會(huì)被中斷(大家還記得我們之前說(shuō)錯(cuò)誤事件是個(gè)中斷性的事件)。在錯(cuò)誤事件觸發(fā)消息子流程之后,會(huì)拋出一個(gè)補(bǔ)償事件,這個(gè)事件會(huì)觸發(fā)每個(gè)task下定義補(bǔ)償事件處理器,并最終結(jié)束這個(gè)流程。以Book flight任務(wù)出錯(cuò)為例,一旦出現(xiàn)了異常,消息子流程啟動(dòng)了補(bǔ)償機(jī)制,這時(shí)候由于Book hotel和Reserve car已經(jīng)執(zhí)行完成,他們對(duì)應(yīng)的補(bǔ)償事件處理任務(wù)Cancel car和Cancel hotel會(huì)被執(zhí)行(注意執(zhí)行順序會(huì)反過(guò)來(lái))。這就是camunda所描述的對(duì)分布式事務(wù)一致性進(jìn)行支持的SAGA 模式。
這個(gè)補(bǔ)償機(jī)制提供了一個(gè)基本的分布式事務(wù)一致性的支持,但是會(huì)有個(gè)問(wèn)題,就是通過(guò)消息子流程并不能完成對(duì)于流程的一個(gè)管控(也有可能是我還沒(méi)學(xué)到位)。比方說(shuō)我在一個(gè)任務(wù)A執(zhí)行完成之后繼續(xù)執(zhí)行了一串操作然后再這段操作中如果出現(xiàn)了異常,我不僅希望想有補(bǔ)償機(jī)制對(duì)這一段操作進(jìn)行補(bǔ)償,還要返回我最開(kāi)始的任務(wù)A(相當(dāng)于之前保存點(diǎn)機(jī)制),目前消息子流程就沒(méi)法做了。因此,我們就需要借助一個(gè)更強(qiáng)的工具,叫事務(wù)性子流程(transaction subprocess)。

事務(wù)性子流程

按照BPMN規(guī)范的定義,事務(wù)性子流程用來(lái)定一個(gè)業(yè)務(wù)事務(wù),當(dāng)一個(gè)業(yè)務(wù)事務(wù)被認(rèn)定為失敗的時(shí)候,這個(gè)事務(wù)中的任務(wù)就可以撤銷(xiāo)已經(jīng)完成的操作(通過(guò)之前說(shuō)的補(bǔ)償機(jī)制)。為了表示一個(gè)業(yè)務(wù)事務(wù)失敗,BPMN提供了一個(gè)新的事件:撤銷(xiāo)事件(cancel event)。撤銷(xiāo)事件有兩個(gè)形態(tài):

  • 當(dāng)它作為業(yè)務(wù)事務(wù)子流程的終態(tài)的時(shí)候,表明這個(gè)子流程是失敗的,需要回滾,繼而觸發(fā)流程中各個(gè)任務(wù)所定義的補(bǔ)償處理
  • 當(dāng)它作為業(yè)務(wù)事務(wù)子流程的邊界事件的時(shí)候,標(biāo)明這個(gè)子流程被回滾了,繼而觸發(fā)外部流程的回滾處理流程,和錯(cuò)誤事件一樣,撤銷(xiāo)事件是中斷的,即它會(huì)中斷外部流程的正常執(zhí)行路徑
    在上述概念引入之后,我們就可以做到一個(gè)分布式事務(wù)和流程的完整控制了,以下圖為例:


    事務(wù)性子流程和撤銷(xiāo)事件

    在這個(gè)流程中,在啟動(dòng)流程之后我們會(huì)到達(dá)一個(gè)接收等待任務(wù),作為我們流程的一個(gè)保存點(diǎn),在接收到指定的消息觸發(fā)流程繼續(xù)執(zhí)行進(jìn)入到了一個(gè)事務(wù)性子流程中,在這個(gè)子流程中我們會(huì)經(jīng)過(guò)兩個(gè)任務(wù),當(dāng)?shù)诙€(gè)任務(wù)出錯(cuò)的時(shí)候,我們會(huì)到達(dá)子流程的撤銷(xiāo)終態(tài),這會(huì)觸發(fā)我們第一個(gè)任務(wù)的補(bǔ)償處理,最終保證我們子流程事務(wù)的一致性。在此之后,子流程結(jié)束并觸發(fā)邊界上的撤銷(xiāo)事件,這時(shí)候外部的流程會(huì)進(jìn)入我們的撤銷(xiāo)處理邏輯,即進(jìn)入到log cancel任務(wù)中,并最終回到了我們的接收消息任務(wù),即回到了我們之前的保存點(diǎn),完成了一個(gè)事務(wù)的撤銷(xiāo)和邏輯的流轉(zhuǎn)。

結(jié)語(yǔ)

本文對(duì)于camunda中的錯(cuò)誤處理、補(bǔ)償機(jī)制和分布式事務(wù)的支持以及具體的流程設(shè)計(jì)做了說(shuō)明,有興趣的同學(xué)可以自己下來(lái)試驗(yàn)一下。另外關(guān)于最后一個(gè)demo我已經(jīng)把代碼放到了這里,歡迎大家嘗試和交流。

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

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

  • 文章綱要 此次分享的緣由 目前分布式事務(wù)問(wèn)題是怎么解決的 行業(yè)中有什么解決方案 這些解決方案分別有什么優(yōu)缺點(diǎn) 別人...
    程序員BUG閱讀 752評(píng)論 0 16
  • 1 Saga相關(guān)概念 1987年普林斯頓大學(xué)的Hector Garcia-Molina和Kenneth Salem...
    shoukai閱讀 89,022評(píng)論 4 65
  • 再有人問(wèn)你分布式事務(wù),把這篇扔給他 咖啡拿鐵純潔的微笑今天 前言 不知道你是否遇到過(guò)這樣的情況,去小賣(mài)鋪買(mǎi)東西,付...
    冬_2bf6閱讀 625評(píng)論 0 0
  • 江北顧家,世代為官,清正廉潔,在這一帶頗具威望。顧家大宅坐落在城南,內(nèi)外簡(jiǎn)樸又不失大氣。溫憶揪著裙子的衣擺,憑軒凝...
    胥子靈閱讀 238評(píng)論 0 0
  • 近來(lái),單是走開(kāi)就不容易。 終于承認(rèn),自己走進(jìn)了瓶頸,開(kāi)始每晚每晚的失眠,躺在床上大腦里滿(mǎn)滿(mǎn)的胡思亂想,于是,焦慮,...
    泡芙味的女孩子閱讀 256評(píng)論 0 0

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