引言
在微服务架构中,相较于传统架构,更加依赖于各个微服务之间的协作来实现完整的业务流程。因此,服务编排成为微服务架构中的一项必备技能。然而,编排涉及到RPC、分布式事务等复杂概念,其质量不能仅仅依赖于经验丰富的开发者,还需要完善的编排框架作为支撑。
首先,我们需要对流程进行分类:长流程和短流程。长流程包含人工活动,其完成时间因人为因素而存在较大波动;而短流程不包含人工活动,启动后预计在较短时间内完成。
相对而言,短流程更加关注响应时间、TPS等指标,以及自动保证事务一致性的能力。因此,在技术设计上,长流程和短流程会有显著区别。本文的重点在于短流程的相关内容。
一、编制与编排的区别
在讨论编排时,我们需要了解两个概念:编制和编排。
编制(Orchestration)的原意是乐队指挥,在演出时,乐队由指挥统一控制。
编排(Choreography)的原意是舞蹈编舞,舞者通过对外部环境(如音乐)的感应作出响应,并与舞伴的动作和表情相协调。
在微服务架构中,这两个词也有类似含义:
编制强调通过一个可执行的中心流程来协调内部和外部服务的交互,通过中心流程控制总体目标、操作和服务调用顺序。
编排则强调协作,通过消息交互序列控制各个部分资源的交互。参与的资源都是对等的,没有集中控制。
为了帮助理解,可以用一个实例来说明:
编制(Orchestration)就像交通信号灯,控制车辆何时通行。而编排(Choreography)类似于环岛,没有集中控制,仅有一系列规则指引车辆在接近时等待,直到有空间进入环岛,然后在适当时机离开。
编制看似不如编排灵活,但编排也有缺点:
- 编排可能导致一个业务流程嵌入多个服务中,维护复杂。
- 编排的对等特性可能导致服务间的强耦合,难以适应需求变化。
在扩展性和工程化方面,编制在一定程度上优于编排。编制适用于单一业务域,而编排更适合跨业务域的流程。
接下来的内容主要讨论编制模式,虽然在小节标题中使用了引号,但仍然使用“编排”一词。第一,编排这个概念已经深入人心,“服务编制”一词较少使用。第二,我认为刻意区分“编制”与“编排”意义不大,重要的是参与者对术语的理解一致。
二、“编排”的关键在于流程与适配
前不久,我参与了一个DevOps项目,其中有一部分工作如下:
在这简短的120行代码中,包含了25行注释、12行空行和83行功能代码。其中包括12次参数校验和18次RPC调用(其中14次为写操作),以及6个主要业务步骤,核心功能是实现添加用户。然而,绝大部分代码缺乏事务控制,因此,当数据不一致问题出现时,修复过程会异常繁琐。
我们不能仅仅依赖经验丰富的开发者来保证代码质量,编排是有规律可循的。结合我们的产品经验,我们来探讨构建一个编排框架需要做些什么。这里不会覆盖所有细节,只分享一些我认为重要的方面。
编排需要三个基本的活动模型(赋值、调用、空)和五个基本的控制模型(顺序、分支、异常抛出、异常捕获、并行)。许多编排框架提供了更多便捷的活动,例如普元的编排框架支持本地调用、REST调用、WebService调用等,使得使用更加方便。
有了这些基本模型,就能轻松编排出复杂的业务流程。
作为经验丰富的开发者,我们知道调用(invoke)时有同步和异步的区别。同步实现简单,但在多级级联编排中需避免因某个服务的长响应时间导致的雪崩效应,通常可通过合理的超时设置和服务熔断策略来避免;在异步调用时,应该能自动管理上下文并避免缓存溢出,同时自动建立异步响应与请求的关联。
提到并行时,也必须考虑不同的聚合方式:是部分聚合还是全部聚合?
流程编排完成后,仅仅是完成了第一步。接下来需要为每个被编排的服务提供正确的参数,这涉及到一个适配的过程。
一个编排服务(abcd)由a、b、c、d服务编排而成,每个服务都有其独立的输入输出参数。适配的过程就是从上下文中为输入参数赋值,并将输出结果写入上下文。
在编排服务执行的不同阶段,上下文的模型会有所不同。最初,只有系统级参数和入参(请求报文);在执行完一个被编排的服务后,上下文会增加该服务的输出参数(响应报文),上下文是一个不断扩展的过程。
因此,适配不仅存在于编排服务的入参与被编排服务的入参之间,也存在于被编排服务与其之前服务的输出参数之间。
最直接的方式是通过手工映射为参数赋值。但这种方式效率低且缺乏成就感。
当然,作为经验丰富的开发者,我们有一些独特的方法来解放双手。
这个方法就是使用元数据。通过为所有的输入和输出参数标记颜色,可以自动完成同色参数的映射。
在之前谈到执行上下文的组成模型时,还包括框架生成的部分数据,这部分数据主要包括流水号(id)和安全性考量。根据 《基于微服务的企业应用架构设计范式》,流水号的生成应遵循GAIR模式:
- globalId: 全局流水号,如果请求中的globalId为空,则由编排服务生成;否则保持不变。
- answerId: 响应流水号,由服务提供者生成;可作为提供者受理的凭证。
- inRequestId: 前台流水号,由前台生成。
- requestId: 请求流水号,由编排服务的协调器生成;生成规则由服务提供者定义。
能够编排流程并适配参数,这个编排框架已经具备了基本的运行能力。接下来需要考虑的是事务的一致性问题。
三、“编排”中的分布式事务
在分布式系统中,应满足最终一致性。
根据CAP理论,必须在可用性(availability)和一致性(consistency)之间做出选择。选择一致性意味着可能会阻塞其他并发访问,尤其是在系统高延迟或网络故障导致失去连接时。
基于目前的成功经验,一般优先选择可用性,但在服务和数据库之间维护数据一致性是根本需求。我们的编排框架应选择满足最终一致性,补偿模式是实现最终一致性的有效方法之一。
通过一个实例来说明补偿模式:一家旅行公司提供预订行程的业务,可以通过公司网站提前预订机票、火车票和酒店。
假设一位客户计划的行程包括:(1)6月19日9点从上海到北京的某航班,(2)某酒店住宿3晚,(3)6月22日17点从北京到上海的火车。客户提交行程后,旅行公司的预订业务按顺序调用航班预订服务、酒店预订服务和火车预订服务。只有火车票预订成功后,整个预订业务才算完成。
如果火车票预订失败,那么之前的航班和酒店预订都需要取消。取消之前的酒店和航班预订即为补偿过程。
在补偿模式中,参与补偿的微服务必须提供补偿操作,并确保补偿操作是幂等的。补偿框架可以在异常时自动调用补偿操作。
目前,RESTful作为一种轻量级的RPC协议被广泛采用,能否很好地支持RESTful服务的事务一致性是衡量编排框架成熟度的标准之一。我们公司的王博士设计了一套RESTful扩展规范来支持补偿模式的事务一致性。
通过使用PATCH的HTTP方法表示补偿操作,并支持通过服务查询编排服务的执行状态。
再补充一个常见问题:
由于通过RPC调用的关系,可能出现补偿请求比原交易先到达的情况。这会导致补偿操作直接失败,因为此时原交易尚未发生;最终原交易到达时可能被成功执行,导致事务不一致。
解决这个问题的方法是,当编排框架发现补偿操作的原交易不存在时,记录一条原交易的流水,从而确保原交易晚到时因记录流水失败而不会成功。
四、“编排”需要更友好的运维工具支持
在生产环境中,我们需要通过查看日志来排除故障,因此应有支持日志全路径回放的工具,帮助我们快速定位问题。
此外,本文所讨论的编排实际上是编制,它是一种集中式控制。这意味着如果被编排的服务响应缓慢,可能影响其他服务。因此需要更快速的监控来帮助我们识别这类服务,以便尽早优化。
五、总结
总的来说,服务编排有两种方式:编排和编制。编制是集中式控制,而编排强调协作。在扩展性和工程化上,编制在某种程度上优于编排。编制适用于单一业务域,而编排更适合跨业务域的流程。
考虑到服务编排的复杂性,我们不能完全依赖经验丰富的开发者。使用编排框架能够大大提高生产力。构建一个编排框架至少需要支持3+5模型,并采用元数据着色来自动适配服务参数。更进一步,还需要考虑事务(分布式)一致性问题,补偿模式通常是保证事务一致性的一种有效方法。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件举报,一经查实,本站将立刻删除。
文章由技术书栈整理,本文链接:https://study.disign.me/article/202510/10.microservices-orchestration.md
发布时间: 2025-03-05