微服务编排模式及引擎:保障端到端业务流成功的关键

2024-05-28
来源:网络整理

作者:研发工程师,腾讯CSIG。

简介微服务架构的核心之一,就是将庞大而复杂的业务系统拆分为高度内聚的微服务,每个服务负责相对独立的逻辑。服务拆分的好处无需重复,但要实现业务价值,不是关于单个服务的能力,而是关于协调所有服务,以确保企业端到端业务流的成功。那么,谁对端到端业务流程的成功负责呢?在研究工作流引擎的过程中,笔者了解了微服务编排模式和微服务编排引擎,可以很好地回答这个问题。

1. 工作流和微服务编排

1. 工作流程

在工作流程方面,给人的印象是 OA 系统中有各种休假审批流程。实际上,从广义上讲,工作流是工作流与其各个操作步骤之间业务规则的抽象、概括和描述。简单来说,为了实现某个业务目标,我们抽象出一系列步骤以及这些步骤之间的协作关系,这就是工作流。例如,订单履行流程、程序构建流程等。BPMN 流程图通常用于描述工作流。

(1)没有工作流时的任务协作

以用户购买逻辑为例,如果我们不应用工作流模型,我们通常会通过调用显示的代码来连接多个任务(步骤):

验证、付款、交付一气呵成,流畅自然。 喝着枸杞红枣,产品跑了过来,脸上带着笑容:“我们有会员卡充值的新业务,大致步骤会验证——>推荐——>支付——>充值。校验和付款之前已经完成,应该很快就会实施吧?”

如果你精通 if-else,那么在你听代码的那一刻,你就已经记住了代码:

一写下来,总觉得有些不对劲,“为什么需要通过添加新的任务节点来更改现有代码?这不符合开闭原则!”。

(2)应用工作流模型进行任务协作

工作流模型旨在解决此问题:将任务的实现与任务的协作关系分开。在上述相同的用户购物逻辑下,在工作流模型下,每个任务只实现自己的原子逻辑,任务协作关系用流程图来表示。

当新逻辑需要复用已有任务节点时,只需调整流程图即可,无需修改现有代码。

2. 工作流引擎

将任务实现与任务协作分开会创建一个专门用于维护任务协作的工作流引擎(通常也称为流程引擎)。

其中,最具代表性的就是这个。在 21 世纪初,当企业应用程序蓬勃发展时,它几乎是自动化流程的标准。互联网上关于介绍的文章已经够多了。今天,我们将介绍另一个由核心成员构建的用于微服务编排的工作流引擎。在开始之前,让我们先了解什么是微服务编排。

3. 微服务编排

微服务架构的核心之一,就是将庞大而复杂的业务系统拆分为高度内聚的微服务,每个服务负责相对独立的逻辑。例如,一个电子商务系统可以拆分为支付微服务、订单微服务、仓储微服务、物流微服务等。服务拆分的好处无需重复,但要实现业务价值,不是关于单个服务的能力,而是关于协调所有服务,以确保企业端到端业务流的成功。

那么,哪种服务负责端到端业务流程的成功呢?答案是否定的。事实上,在公司内部,端到端业务流可能没有正式记录,从一个微服务到另一个微服务的事件流是隐式地在代码中表示的。

许多微服务架构依靠相对纯粹的编排模式( )来解决这个问题。在此模式中,微服务通过向消息队列发送和接收事件来相互协作。编排模式为开发人员提供了高度的灵活性,但编排模式仍然不能解决问题:

结果是用于协调单个微服务的更严格的业务流程模式 ( )。在此模式下,将有一个中央控制引擎:

可以使用下图进一步了解微服务业务流程和微服务编排模式之间的区别:

根据我们前面描述的工作流模型,工作流引擎非常适合作为中央控制引擎来编排和调度微服务。那么,为什么传统的工作流引擎未能继续占领微服务编排市场,反而催生了新的微服务编排引擎呢?更有趣的是,核心开发也来自原班人马。

答案是,诸如此类的传统工作流引擎的架构无法适应当今的微服务场景

在设计之初就考虑到了这些问题,下面就为大家详细介绍一下。

2. 功能和顶层架构

1. 核心特点

是一个免费的开源工作流引擎,专为微服务编排而设计,可提供:

在设计之初,就考虑了超大规模微服务编排的问题。为了响应超大规模,支持:

2. 建筑

体系结构由四个主要组件组成:、 、 和

(一)

客户端发送指令:

- 创建工作流实例 ( )。

- 发布消息 ( )。

- 激活作业

- 完成作业

- 失败的作业

- 更新实例进程变量 ( )。

- 解决异常 ( )。

客户端程序可以完全独立于扩展,并且不执行任何业务逻辑。客户端是嵌入在应用程序(执行业务逻辑的微服务)中的库,用于与群集连接进行通信。客户端通过基于 HTTP/2 协议的 gRPC 连接到。

正式地,Java 和 Go 客户端可用。社区提供 C#、Ruby 和客户端实现。gRPC 协议便于生成其他语言的客户端。

,则调用执行单独任务的单元。

(2)

作为群集的入口,请求被转发到。它是无状态 () 和无会话 (),可以根据需要添加节点以实现负载平衡和高可用性。

(3)

它是一个分布式进程引擎,用于维护正在运行的进程实例的状态。可以分区实现水平扩展,可以进行复印实现容错。通常,群集具有多个节点。

需要强调的是,它不包含任何业务逻辑,它只负责:

形成点对点网络,使集群没有单点故障。集群中的所有节点都具有相同的职责,因此当一个节点不可用时,该节点的任务将透明地重新分配给网络中的其他节点。

(4)

系统在状态更改中提供事件流。此事件流数据有许多潜在用途,包括但不限于:

提供一个简单的 API,允许您将数据流式传输到任何存储系统。官方提供开箱即用的服务,社区也提供其他服务。

3. 内部核心实现

高吞吐量和高可用性微服务编排受益于三个关键实现:

1. 分布式

在设计时考虑了分布式部署,可以在不依赖外部组件的情况下构建集群,并且集群中的节点形成点对点网络。在网络中,所有节点都具有相同的责任,以确保集群中没有单点故障。

在内部,仅追加队列(可以通过类比来理解)被抽象化以处理和存储数据。当集群有多个节点时,队列被划分为多个分区(或分片)并分布在节点之间。每个分区有多个副本 ()。在所有副本中,根据 raft 协议选择一个副本,负责接收请求并执行所有处理逻辑。其他副本是被动追随者 ( )。如果不可用,则透明地选择一个新的。

2. 消息驱动

消息驱动架构体现在两种方式中:

(1)内部流处理模型

实际上,内部实现是一系列流处理器 ( ) 作用于记录的流 ( )。流处理模型提供统一的实现:

小程序请假审批流开发代码_请假审批权限表_请假的审批程序为

a. 状态机 ( )。

管理有状态实体:任务、工作流实例等。在内部,这些实体是作为流处理器管理的状态机实现的。状态机模式的概念很简单。状态机的实例始终处于逻辑状态。对于每个状态,一系列 () 定义下一个可能的状态。过渡到下一个新状态可能会产生输出或副作用。

如下图所示,是作业的状态机

椭圆表示状态,箭头表示状态转换。需要注意的是,状态转换只能用于特定状态。例如,当任务处于某种状态时,您无法完成该任务。

b. 事件和指令 ( 和 )。

状态机中的每个状态更改称为事件 ()。每个事件都作为记录发布到流中。可以通过发送命令来触发状态更改。从两个来源接收指令:

收到命令后,它将作为记录写入流中。

c. 有状态流处理 ( )。

流处理器有序地从流中读取记录,然后根据记录所关联的实体的生命周期解析指令。流处理器循环执行以下步骤:

使用流中的指令 ()

根据状态生命周期和实体的当前状态,确定指令是否适用

如果适用,该指令将应用于状态机。如果指令由客户端发送,则发送响应。

如果订单不适用,请拒绝。如果它是由客户端发送的,则会发送错误响应消息。

发布新事件并报告实体的新状态。

例如,如果处理作业命令,则会生成作业事件。

d. 命令触发器 ( )。

一个实体状态的变化可以自动触发另一个实体的指令。例如,当任务完成时,相应的流程实例应继续执行后续任务,即 Job 事件触发指令。

处理背压

收到客户端请求后,该请求将写入事件流,然后由流处理器处理。如果处理速度太慢或流中堆积的客户端请求过多,则处理器可能需要很长时间才能处理新收到的请求指令。如果继续收到来自客户端的新请求,则待处理任务(积压工作)将继续增加,并且任务处理延迟将超过可以接收的时间。为了避免这个问题,采用了背压机制。当收到的请求超出了它在特定延迟期内可以处理的限制时,某些请求将被拒绝。

可以处理的最大请求速率取决于机器的处理能力、网络延迟、系统的当前负载等。因此,未配置固定的最大请求限制。相反,自适应算法用于动态确定已接收但尚未处理的请求数的限制。收到新请求时,请求数量会增加;当处理请求并将响应发送回客户端时,请求数会减少。当请求数量达到限制时,系统会拒绝后续请求。

如果客户端请求因背压而被拒绝,则客户端可以使用适当的重试策略重试。如果废品率一直很高,则表示它处于恒定的高负载下。在这种情况下,建议降低请求速率。

(2)和发布-订阅模型

业务微服务集成 SDK,为每个 SDK 创建一个,并在其中实现业务逻辑。这就像向普通 Java 方法添加注解一样简单,以 Java SDK 为例。

@Component@Slf4j
public class SomeJob { @EnhancedJobWorker(type = "some-service.SomeJob") public void handleTask(final EnhancedJobClient client, final ActivatedJob job) { // 业务逻辑 // .... // 根据业务逻辑执行情况,结单 if (success) { client.completeJob(job); } else { throw SomeException("失败原因"); }}

与引擎主动调用服务的模式不同,创建后,会在内部启动轮询线程,并定期(默认)轮询相关事件(作业)。

最后返回任务后,任务信息会传递到业务逻辑中执行。整个时间如下所示:

客户端主动轮询模型进一步解耦引擎任务状态维护和微服务业务处理逻辑,允许业务根据自身的处理能力,以相对恒定的速率处理任务。当短时间内创建大量任务时,队列模型可以累积任务并平滑流量。

3. 运行时数据和历史数据的分离

在处理任务、工作流或内部维护时,会生成有序的记录流

虽然客户端无法直接查看流,但它可以加载和配置用户代码,以可加载的方式处理每条记录。提供统一的条目来处理写入流的记录

只有通过 YAML 配置文件配置的配置文件才会被挂载。配置完成后,它将在下次启动时开始接收记录。需要注意的是,从那时起,只能保证接收数据。最大的效果是可以减轻集群无限期存储数据的压力。当不再需要某些数据时,它会先查询删除数据是否安全,如果安全,则永久删除数据,从而减少集群的磁盘占用。无论它如何加载(是否通过外部 jar 包),一切都以相同的方式完成并以定义的方式进行交互。

(1) 加载 ()。

配置完成后,它将在启动阶段安装。在挂载阶段,将验证每个配置,如果出现以下任何问题,启动将失败:

(2)处理()。

在任何给定时间,某个主节点都只有一个主节点。当一个节点到达某个地方时,它要做的一件事就是运行一个实例。此流处理器为每个配置的实例创建一个实例,并将每条记录转发到这些实例。

这意味着每个分区只有一个且只有一个实例:如果有 4 个分区和至少 4 个线程处理记录,则可能有 4 个实例同时导出记录。

请注意 at-once 语义,即记录将至少被看到一次,可能被多次看到。在这些情况下,可以重复:

为了减少处理的重复记录数,流处理器会记录最近成功导出的每条记录的偏移位置 ()。有这个偏移量就足够了,因为流是有序的记录序列,而在流中,偏移量()是单调增加的。成功导出记录后,将更新和维护此偏移量。

注意:虽然已尽一切努力尽量减少处理的重复记录的数量,但会发生重复记录,因此有必要确保操作的幂等性。幂等可以在代码中实现,但如果导出到外部系统,建议在外部系统中进行重复数据删除,以减少负载。

(3) 错误处理 ( ).

如果错误出现在 #open() 阶段,则流处理器将失败,然后重新启动以修复错误。在最坏的情况下,它不会运行,直到错误被修复。

如果错误出现在 # 阶段,则将记录错误消息,其余工作将正常完成。

如果在处理过程中发生错误,则无限期地重试同一记录,直到不再生成错误。在最坏的情况下,一个故障将导致其他故障挂起。目前,您需要实现自己的重试/错误处理策略,该策略将来可能会更改。

(4)性能影响( )。

对于每个负载,自然会对性能产生一些影响。对于某些人来说,一个慢速也会导致其他慢速,在最坏的情况下,完全阻塞线程。因此,推荐的逻辑尽可能简单,数据扩充和转换的逻辑放在外部系统中。

四、亲身体验

作为公司的子项目,自2017年以来一直独立开源运营。公司核心团队来源于早期团队,主营业务为核心工作流咨询服务。从今年3月开始,已经宣布将提供打包引擎、控制台、建模工具等SaaS服务,但核心流程引擎仍以开源的方式迭代。

它不依赖外部组件,可以使用本地 JAR 包快速构建集群环境 [1]。参考官方指南[2]快速创建演示项目。

官方[3],在8核32G单节点、4个分区1个副本的配置下,可以达到3.2w/s的性能。而且,添加节点基本上可以线性增加处理能力。

在实际的 6 个 8C 16G SKTE POD 集群中,我们使用 Test 对 3w/s(每个进程 2 个节点,包含约 50 个进程变量)的性能进行压力测试。与官方数据存在一定差距,由于官方环境和配置不完整,无法完全模拟。资源瓶颈主要出在磁盘 I/O 上。但与我们之前 5~6 k/s 的崩溃相比,这个数据是一个很大的进步。

另一方面,它仍处于早期迭代阶段,稳定性和功能完整性仍在优化中。目前支持BPMN任务,任务调度中遗漏任务的概率很小。但是,从建筑设计、场景定位、社区活动等角度来看,这是一个可以期待的项目。

设置一个标志,然后作者会继续输出内部实现的深入分析文章。分布式、异步和线程模型的实现仍然值得学习。对微服务编排和流程引擎感兴趣的同学也欢迎交换砖块~

参考链接:

[1] 快速设置集群环境的准则:

[2] 快速创建演示项目:

[3] 官方:

[4] 官方资料库:

[5] 官方文件:

[6] 问答社区:

近期热点文章

探讨游戏项目管理的专业思路

云开发低代码开发平台设计初探

如何在技术领域发挥自己的影响力

让我知道你在看

分享