本文主要介绍好分期自主研发的流程引擎 EasyFlow,内容主要包含方案设计、最佳实践以及部分代表性的问题的解决等,希望通过本文的分享,为有相关诉求的团队提供一定的思路参考。
一、何为 EasyFlow
在互联网金融公司的业务实现中,总会遇到几个技术问题:如何能快速对接不同的资金渠道达到线上放款目标?如何能在合作方系统不稳定或网络异常情况下保证内部系统稳定运行?如何能将上百个差异化的流程抽象管理?如何能高效的、低资源消耗的情况下执行亿级任务?如何能在繁杂的渠道对接过程中,实现低代码化开发?
好分期是如何能快速对接 40+ 线上渠道、高效管理 150+ 差异业务流程的呢?EasyFlow 便是答案。
二、背景
我们选择自研 EasyFlow 项目主要有两方面的考虑:
流程可视化、低代码化开发
提升系统的可用性
1. 流程可视化、低代码化开发
传统资金对接流程采用硬编码模式来指定业务的前后依赖节点,该模式存在以下主要问题:
依赖关系使用硬编码关联,无法直观了解业务流程,前后依赖关系变更时需要重新编写代码
流程复用程度低,相似的渠道流程因细节的差异也无法进行复用
线上问题处理困难,需要编写大量 SQL 通过数据库的任务状态以及代码的逻辑关系才能定位问题
通过可视化的实现,不仅解决了流程的界面化的创建、策略配置,减少大量的流程类开发工作,也为实际流程运行时的运营工作提供了便捷的处理方式,可视化能将流程实际运行情况清晰的展示到界面上,使得运营人员能快速对线上问题进行定位。
2. 提升系统可用性
传统流程执行机制依赖数据库进行异步任务节点的存储,通过系统的 quartz 定时调度分批次限流进行任务的拉取执行,该模式存在以下主要问题:
对数据库的使用压力大,一般的流程类的操作是业务数据的几十倍甚至上百倍,当业务量大时,流程操作对数据库的压力就会造成灾难性的影响
三方系统、三方业务不稳定对系统的中间件影响巨大,容易造成整体的服务卡顿和资源使用的浪费
当业务量不均匀请求时,分批次执行任务容易造成资源无法充分利用
将 MySQL+quartz 的扫表操作转换为 RabbitMQ+MySQL 的业务模型,可以在一定程度上降低数据库压力;并将流程调度交给 RabbitMQ 管理, 充分利用集群节点解决实时性任务执行的时效性问题,减少定时拉取任务的等待间隔;考虑到实际生产环境中可能会出现因合作方系统不稳定、业务需要经过多次重试才能得到正确结果、以及网络抖动带来的一些列不确定因素,通过消息路由动态策略更新和匹配,进一步提升队列的使用率和流程的执行效率。
三、EasyFlow 与开源流程引擎对比
EasyFlow 相比较市场主流的开源流程引擎,更小巧、精简,对于三方 jar 包依赖数量也略低于其他系统,能轻松的集成到各个系统中。并且对于基础数据库表的数量也进行了精简,对于部署所需的资源要求也相对较低。自研的可视化页面,简洁美观,并将流程编程升级为可视化配置,极大的降低代码维护量。
四、EasyFlow 方案设计与实现
之前章节介绍了 EasyFlow 的一些特点以及解决问题的能力,但是具体是如何实现的呢?本章节会概要介绍 EasyFlow 流程引擎的调度算法设计、可视化编排设计以及提高队列使用率的消息路由的设计方案。
我们选用 EasyFlow 的底层消息中间件时进行了多个产品的对比,结合业务实际场景的吞吐量、可靠性的要求,选取了能保证消息可靠传递、并通过复杂的路由逻辑去找到消费者的 RabbitMQ 作为消息驱动的策略核心,生产者通过与 Broker 连接并且建立信道(Channel),将消息发送至交换器(Exchange)。然后根据 Exchange 的类型,将消息路由到指定队列。 虽然从吞度量上不如 Kafka 的性能出众,但是基于消息队列实现业务渠道也需要考虑平替 MySQL+quartz 模式的可靠性,以及支持 producer 与 queue 入队之间保留路由功能的情况,我们最终选择了 RabbitMQ。
1. EasyFlow 引擎核心关键定义
流程(Flow): 用来定义流程的名称和编码等基本信息
任务(Task):最底层的具体执行某一个固定动作的独立的单元。
流程节点(FlowNode):流程编排时对应流程中的一个个的点,可以是任务也可以是流程(子流程)。其中有两个节点比较特殊,分别是开始节点(Start)和 结束节点(End),用来表示流程的开始及结束,为虚节点(该节点无实际对应的任务或流程),流程节点可以引用其他流程成为子流程
流程编排(FlowDesign):由多个流程节点串联组成
流转条件(CondExp):流程编排中由父节点到子节点连接而成的有向边上的条件表达式
2. 流程执行过程
如图所示,通过可视化的流程定义,在内存中构建成 BPMN 业务流程图,利用图节点的指向动态创建后续流程节点实例, 通过条件表达式作为分路由的判断依据,同时对多个父节点实例的状态判断是否可以继续创建下一节点,或者是执行结束。
3. 低代码可视化设计与实现
EasyFlow 集成 mermaid-js 页面渲染技术,将流程类的配置信息,渲染成可视化的流程图形,并将可视化的流程图样式尽可能像 UML 流程图的样式贴合,不仅将繁杂的流程数据通过可视化的方式进行了清晰的展示,也能让产品技术人员通过最熟悉的样式,进行线上流程准确性的校验。
mermaid-js 是一种基于 JavaScript 的绘图工具,使用类似于 Markdown 的语法,使用户可以方便快捷地通过代码创建图表。Mermaid 作为早期支持 UML 图表渲染较好的技术,并且多个绘图软件例如:Typora,MarkdownPad 等,均使用的 mermaid-js 作为可视化渲染技术,所以我们也选用了这个技术。
研发人员通过标准简洁的流程创建流程以及流程执行规则配置,即可快速制定业务流程的规则。
EasyFlow 规定了流程间的上下文状态定义,通过标准或个性化的规则配置,可以将业务结果与流程的执行策略紧密结合,依据每个节点执行后不同状态,自动实现流程图中差异化执行。
执行延迟配置:通过延迟条件的配置,可以在不影响消息重试的频次修改条件下,通过业务逻辑的判断,达到实际业务执行的延迟控制功能
循环次数配置:设置单个节点的重试次数,在配置范围内可以正常重试节点,达到最大重试次数时,则会暂停流程并且预警,通知相关运营人员进行人工干预
匹配条件配置:EasyFlow 引入 SpEL 表达式解析功能,在创建配置流程节点过程中,可以设置 SpEL 表达式,并根据流程执行过程中的关键变量值以及调用表达式解析,进行流程分发的判断,将流程逻辑从硬编码优化为可视化界面配置。
我们引用 SpEL 表达式的目的是为了做流程通用化的处理与实际业务解耦。
图 1
如图 1 所示:在创建“担保方进件-进件审核”节点时,需要在前置任务处理成功,流程上下文_result 对应的结果是 true 才会进行流程的处理,“审核结果回调业务方”是在前置任务_result 为 false 时执行。
图 2
如图 2 所示:“还款-线下-还款到资金方”需要根据流程上下文中的 prepaymentFlag 为 false 且_result 为 true 情况下执行,“还款-回调发起方”(成功)需要根据流程上下文中的 prepaymentFlag 为 true 且_result 为 true 时执行,“还款-回调发起方”(失败)需要根据流程上下文中的_result 为 false 时执行。
为了能通用化处理流程引擎的动态规则匹配,并且支持匹配逻辑的扩展性,我们引用了 SpEL 表达式,进行灵活的规则维护和匹配工作。差异化的业务流程,不是简单的 true 和 false 就能进行业务区分的,对于复杂业务场景,可能会出现多个匹配条件共同作用,如果每新增一个判断条件都需要调整流程的判断逻辑,那就得不偿失了。通过可视化的实现以及 SpEL 的引入,不仅解决了流程的界面化的创建、策略制定,也为实际流程运行时的运营工作提供了便捷的处理途径,可视化能将流程实际运行情况通过状态清晰的展示到界面上,使得运营人员进行线上问题定位时一目了然。
4. 智能消息分发路由
消息队列存在自身的使用限制,比如并发速度受消费者单位速率的限制,死信队列会因不同的过期时间导致积压等问题,在众多差异的队列使用场景中,都会对 EasyFlow 的使用效率造成影响。为了应对互联网金融的差异化的业务场景,EasyFlow 建立了消息路由模块,实时的针对消息规则以及业务场景进行匹配,提高流程的处理效率。
在使用 EasyFlow 初期,队列的部署策略如图所示
流程消息生产到主队列,由消费者监听消费,当出现当前流程需要重试时,则会设置延迟执行时间,并发送到死信队列进行等待,当到达设置的消息执行时间后,死信队列会将消息放回主队列等待二次消费。这种模式在消息量积压不大、单笔消费速度快、消费过程对接的 RPC 等耗时流程相对耗时较短、以及业务场景要求的重试间隔整体固定时表现比较正常。但是随着好分期接入的资金渠道越来越多,流程的复杂性也逐步提升,各家服务的系统不稳定的问题频发,导致对消费这执行效率低,也会因消费者长时间消费占用,导致队列积压,整体流程执行暂停的重大问题。
为了将 EasyFlow 更好的应用于相对不稳定的业务的场景中,我们进行了流程引擎的高可用升级,将实际业务异常与队列的使用相结合,建立了消息路由的模块,更智能的针对不同异常场景的业务消息进行分发,保证流程引擎的高效运行。
如图所示,我们之所以选择 RabbitMQ 作为消息中间件,就是为了使用 RabbitMQ 的 exchage 自身支持路由的特性,将消息路由节点放置在了 producer 发送消息后的阶段,根据消息属性,经过消息路由的路由规则判断,进行对应的 topic 的选择,并进行消息的分发。
如图所示,该方案是基于业务场景高效使用消息队列的策略和实现方法,由三个模块构成:消息分发路由、路由规则维护模块、路由规则缓存。其中消息分发路由提供根据业务场景以及业务节点类型进行内部的计算,通过拉取规则缓存中的实时规则,对熔断降级、业务配置、消费耗时计算以及重试次数等不同维度,按照优先级进行快速高效的路由分发功能,经过多个规则的校验,得出最合理的队列进行消息的生产和后续的消费工作;路由规则维护模块是根据实际消息的消费过程的执行情况进行动态的规则维护,并同步到路由缓存进行规则更新;路由缓存是存储路由规则的缓存服务,该服务通过同步路由规则维护模块产生的规则变更,为消息分发路由的实时计算提供数据基础。
我们针对实际的线上业务以及其复杂度和对应的大部分异常进行兼容,并将异常场景与流程引擎引擎消息分发路由策略进行了结合。解决了如何在遵循队列执行特性的情况下,通过合理的分配路由算法,提高消息队列的执行效率的问题。在不同的业务场景或异常场景下,通过读取路由缓存的规则策略,以达到高效且稳定的实时选取最适合的发送队列,最大程度的来保障消息队列的运行效率,避免队列的阻塞积压。
根据线上业务的特殊性,每个任务的轮询时间频次是不一致的,所以我们采用对死信队列增加延迟插件的方式,解决了不同执行延迟消息的非阻塞的问题,从而增强业务系统承载能力。
上面使用 DLX + TTL 的模式,消息首先会路由到一个正常的队列,根据设置的 TTL 进入死信队列,与之不同的是通过 x-delayed-message 声明的交换机,它的消息在发布之后不会立即进入队列,先将消息保存至 Mnesia ,这个插件将会尝试确认消息是否过期,首先要确保消息的延迟范围是 Delay > 0, Delay =< ERL_MAX_T(在 Erlang 中可以被设置的范围为 (2^32)-1 毫秒),如果消息过期通过 x-delayed-type 类型标记的交换机投递至目标队列,整个消息的投递过程也就完成了。
五. EasyFlow 在好分期的应用现状
目前好分期资金接入系统 WeFund 已全面接入 EasyFlow 流程引擎,现阶段已管理维护 WeFund 超过 150+差异流程,1.5 亿+流程实例创建,200 亿+任务执行的场景,RabbitMQ 消息量每天达 1800w+,运行任务 180w+,并发峰值超过 1300+TPS;并且 EasyFlow 已经能够很好的适配复杂的业务场景,在同等资源下的资金接入工作由原有的 14 天缩短至 7 天,将业务流程的编排和策略制定实现 0 代码化开发,研发人员只需要专注于各家渠道的业务对接工作即可,不仅缩短了开发资源,对于测试、运营维护等工作也减少了大量的资源投入。
作者简介:
吴迪,北京微财科技有限公司产品技术高级总监
李军,北京微财科技有限公司技术总监
周正杭,北京微财科技有限公司 JAVA 开发资深工程师
彭伟煌,北京微财科技有限公司 JAVA 开发高级工程师
徐东,北京微财科技有限公司 JAVA 开发高级工程师
评论 5 条评论