camunda 错误处理、补偿机制和分布式事务支持

前言

有段时间没写文章了,这段时间一直在看camunda服务编排的一些东西,之前没有接触过工作流引擎和服务编排类似的东西,花了一点时间来熟悉和理解。由于我们需要运用camunda来做服务编排,因此对于一个编排,其中的工作单元是分布在多个微服务内部的业务调用,如何在服务编排中保持分布式事务的支持就成了我的关注点。camunda提供了补偿机制和对应的SAGA 模式来完成分布式事务的支持。本篇文章将对这一块进行描述和探讨。

错误处理机制

对于分布式事务来说,归根结底我们要考虑的就是,微服务调用出错的时候,我们的流程应该如何流转,并在这个流转过程中保证事务的一致性。因此我们第一个考虑的就是camunda本身的错误处理机制是什么。

事务回滚

第一种错误处理方式是事务回滚,要理解这个方式我们首先要看看camunda中事务是怎么组织的。在camunda中,流程的运行是被封装在一个个客户线程(client thread)中。可以这么理解,当一个启动或者继续流程的请求到来之时,在我们的服务器的处理线程中(类似于tomcat中的http-thread-pool开头的那些线程),我们通过调用类似于下面的API

runtimeService.startProcessInstanceByKey(...)

来发起一个流程,这时候整个流程就完全运行在http-thread-pool线程中,这就称之为我们借用了一个客户线程,在这个线程中,camunda始终会开启一个数据库事务,并在这个事务下进行流程的运转。
在这个情况下,流程会一直运转到下一个等待状态(wait status 代表流程会在后面的某个时期继续),然后完成状态的持久化到数据库中,结束事务。在bpmn标准中,有下列几个等待状态:

  • Tasks:
    1. 接受任务(receive task)
    2. 用户任务(user task)
    3. 外部服务任务(service task: external task)
  • Events:
    1. 消息事件(message event)
    2. 定时事件(timer event)
    3. 信号事件(signal event)
  • Gateway
    1. 基于时间的逻辑门(Event Based Gateway)

我们可以通过下图来理解这个过程:


事务模型

第一步,我们发起了完成task的请求,然后流程继续运行直到到达了定时等待事件,此时这个客户线程中的工作流运行已经结束,我们完成当前的事务并提交,然后返回控制给调用方,等待定时任务的触发。
这是正常情况,当我们出现异常的时候会怎么办呢?我们通过下图来进行理解:


异常事务回滚

当进行到发送任务(send invoice to customer)时触发了异常,这时候客户线程里的事务会完全得到回滚,然后把异常信息抛出给调用方,当下次该流程的请求进来的时候,会发现流程的状态点仍然是最开始的那个用户任务(provide shipping address)。这个就类似于游戏中的保存点,玩过游戏的同学都知道,我们在这个保存点到下一个保存点游戏过程中,如果出现什么意外(游戏人物挂了,机器死机了,老妈回来了),我们下次进入游戏,还是会回到最近的一个保存成功的保存点,继续玩。
这个错误处理机制可以说是非常简洁和直观了,它异常处理完全交给了调用方,并保证了状态的回滚,但是并没有做出任何分布式事务一致性的支持。比方说如果一个流程 A -> B-> C,当C抛出异常的时候,我们并没有提供把A和B任务中的操作回滚或者修正的能力,因此这个方案只是一个最初级的方案。

异常捕获和逻辑判断

我们知道我们自己diy的task,实际上就是一段段java代码,因此,我们可以在我们的代码中捕获异常,并且通过设置执行上下文中的参数,来标定我们的程序遭遇异常,一次来作为逻辑判断的依据,如下图所示:


异常捕获和逻辑判断

这种方式怎么说呢,是个方案,但是对于我们代码的耦合较高,而且对于我们流程来说会造成比较大的干扰,因为如果你要做细粒度,task级别的错误排查和修正,你就免不了在每个task后面都加上这么一层逻辑判断,会对编排流程本身的逻辑带来困扰,这样做出来的流程图也不够清晰。

BPMN 2.0 错误事件

针对上述方案的局限性,在BPMN 2.0规范中给出了错误事件来作为显式的处理错误的方案。以下图为例:

错误事件

一个典型的场景是如果我们要针对一个子流程中的错误来做出处理,一个比较简单的方案是在这个子流程上叠加一个错误边界事件(error boundary event),通过定义这样一个事件可以针对子流程中抛出的错误来进行额外的处理。需要注意的是,在这个图中,如果出发了错误边界事件 进入到错误处理流程,那本身的执行过程将会遭到中断(interrupting),因此错误事件也被称作是一种中断事件(interrupting event),关于这一块我在后续文章中会提及。
其实大家仔细看也会发现,错误事件,其实可以用前面的异常捕获和判断来等价完成,比如下图:
错误处理

因此我们可以知道,错误事件其实可以看做是我们针对错误的一个简化的建模手段,并且从语义上能够给使用者一个清晰的感受,明晰了错误处理流程和正常业务处理流程的边界。

补偿机制

在了解了camunda的错误处理机制之后,我们就会想在错误处理机制之上完成task的修正补偿并最终支持分布式事务,因此BPMN规范提出了补偿事件(compensation event),通过对任务或者子流程定义一个compensation事件 和它对应的处理单元来完成修正补偿。camunda官方也提供了一个demo,github地址在此 。我们可以来分析下:

补偿机制

在上述这个流程中,每个task都有一个补偿边界事件(compensation boundary event),按照补偿事件的定义,补偿事件只会在他标识的task成功执行完毕之后才有可能被触发(A compensation boundary event can only be triggered after the activity to which it is attached completes successfully.)。这个跟之前一般的边界事件是不一样的。然后还有一个需要注意的是,在上面的流程图中有一个虚线框框定的区域,这个区域在BPMN规范中被称为消息驱动的子流程(event-driven subprocess)。这个子流程的用处就是在整个流程中如果出现了某个消息,就会触发这个流程。
在上面这个流程里面,这个消息驱动的子流程的开始事件是一个错误事件(error start event),因此,一旦流程中出现了错误(错误类型需要在错误事件中指定),这个消息子流程将就会启动,而本身的执行过程就会被中断(大家还记得我们之前说错误事件是个中断性的事件)。在错误事件触发消息子流程之后,会抛出一个补偿事件,这个事件会触发每个task下定义补偿事件处理器,并最终结束这个流程。以Book flight任务出错为例,一旦出现了异常,消息子流程启动了补偿机制,这时候由于Book hotel和Reserve car已经执行完成,他们对应的补偿事件处理任务Cancel car和Cancel hotel会被执行(注意执行顺序会反过来)。这就是camunda所描述的对分布式事务一致性进行支持的SAGA 模式
这个补偿机制提供了一个基本的分布式事务一致性的支持,但是会有个问题,就是通过消息子流程并不能完成对于流程的一个管控(也有可能是我还没学到位)。比方说我在一个任务A执行完成之后继续执行了一串操作然后再这段操作中如果出现了异常,我不仅希望想有补偿机制对这一段操作进行补偿,还要返回我最开始的任务A(相当于之前保存点机制),目前消息子流程就没法做了。因此,我们就需要借助一个更强的工具,叫事务性子流程(transaction subprocess)

事务性子流程

按照BPMN规范的定义,事务性子流程用来定一个业务事务,当一个业务事务被认定为失败的时候,这个事务中的任务就可以撤销已经完成的操作(通过之前说的补偿机制)。为了表示一个业务事务失败,BPMN提供了一个新的事件:撤销事件(cancel event)。撤销事件有两个形态:

  • 当它作为业务事务子流程的终态的时候,表明这个子流程是失败的,需要回滚,继而触发流程中各个任务所定义的补偿处理
  • 当它作为业务事务子流程的边界事件的时候,标明这个子流程被回滚了,继而触发外部流程的回滚处理流程,和错误事件一样,撤销事件是中断的,即它会中断外部流程的正常执行路径
    在上述概念引入之后,我们就可以做到一个分布式事务和流程的完整控制了,以下图为例:


    事务性子流程和撤销事件

    在这个流程中,在启动流程之后我们会到达一个接收等待任务,作为我们流程的一个保存点,在接收到指定的消息触发流程继续执行进入到了一个事务性子流程中,在这个子流程中我们会经过两个任务,当第二个任务出错的时候,我们会到达子流程的撤销终态,这会触发我们第一个任务的补偿处理,最终保证我们子流程事务的一致性。在此之后,子流程结束并触发边界上的撤销事件,这时候外部的流程会进入我们的撤销处理逻辑,即进入到log cancel任务中,并最终回到了我们的接收消息任务,即回到了我们之前的保存点,完成了一个事务的撤销和逻辑的流转。

结语

本文对于camunda中的错误处理、补偿机制和分布式事务的支持以及具体的流程设计做了说明,有兴趣的同学可以自己下来试验一下。另外关于最后一个demo我已经把代码放到了这里,欢迎大家尝试和交流。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • 文章纲要 此次分享的缘由 目前分布式事务问题是怎么解决的 行业中有什么解决方案 这些解决方案分别有什么优缺点 别人...
    程序员BUG阅读 723评论 0 16
  • 1 Saga相关概念 1987年普林斯顿大学的Hector Garcia-Molina和Kenneth Salem...
    shoukai阅读 88,113评论 4 65
  • 再有人问你分布式事务,把这篇扔给他 咖啡拿铁纯洁的微笑今天 前言 不知道你是否遇到过这样的情况,去小卖铺买东西,付...
    冬_2bf6阅读 609评论 0 0
  • 江北顾家,世代为官,清正廉洁,在这一带颇具威望。顾家大宅坐落在城南,内外简朴又不失大气。温忆揪着裙子的衣摆,凭轩凝...
    胥子灵阅读 169评论 0 0
  • 近来,单是走开就不容易。 终于承认,自己走进了瓶颈,开始每晚每晚的失眠,躺在床上大脑里满满的胡思乱想,于是,焦虑,...
    泡芙味的女孩子阅读 199评论 0 0