65.9K
CodeProject 正在变化。 阅读更多。
Home

使用微服务和复杂事件处理器进行消息驱动的业务流程编排

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2019年5月6日

CPOL

21分钟阅读

viewsIcon

10895

本白皮书定义了一套使用事件驱动方法(可能是构建分布式企业解决方案的最佳方法之一)构建微服务的架构最佳实践。

引言

我们收到一份请求,需要帮助一家大型企业重新构想其数字化战略和业务价值链,通过引入企业级信息系统来重塑整个业务。

简而言之,这是一项利用现有解决方案基础从头开始开发完整解决方案堆栈的工作。我们需要提供一个解决方案,能够执行所有与法律、规则和行政指令相关的主要业务操作。它需要我们支持以下主要要求:

  • 支持一个能处理来自多个地点的大型开发团队的架构
  • 支持持续交付和持续集成
  • 具备内置功能,可根据需求轻松扩展和扩容
  • 拥有冗余服务器以减少服务器故障的影响
  • 具备随着时间推移改变其核心业务流程的灵活性
  • 支持与现成产品集成
  • 将所有子系统与一个主IS系统集成,其中每个活动都受到监控和控制
  • 支持通过多种设备访问
  • 出于安全和保密原因,避免使用外部云托管解决方案
  • 支持技术多样性

单体系统设计方法

在大多数系统实现中,企业的眼前需求通过快速、隔离、低成本的解决方案来解决,使其能够领先于颠覆者和竞争对手。事实证明,这对这个客户来说是一个糟糕的主意,原因有很多,尽管它看起来是一个简单、用户友好的解决方案。这种隔离的解决方案方法最终创建了一系列纠缠不清的单体应用程序,它们彼此共享业务功能,使其高度依赖,更难管理、扩展或更改。

图1:紧密耦合的单体应用程序与临时应用间集成

如上图所示,单体应用程序很复杂,其架构更难理解,因为存在多个相互影响的其他依赖系统。此外,逻辑并不明显,尤其是在维护模式下从一个方面(或系统角度)审视时。在这种情况下,企业可能会失去轻松维护、快速更改、构建新功能和试运行策略等能力。

尽管最近(大约十年)Web服务和Web API框架的进步设法部分改善了这种情况,但它也导致了可能导致瓶颈(如果链中的某些服务被调用得比其他服务更频繁)、服务挂起和级联故障(如果链中的某些服务不可用)等的长调用链。此外,Web服务(SOA)方法在业务解决方案的可维护性、可部署性、可伸缩性和可配置性方面也面临着自身的挑战。

总而言之,当企业需要一个具有自愈能力的自主系统时,单体应用程序正在以日益复杂的挑战来争取业务敏捷性。

单体架构的优缺点

优点

  • 复杂性较低
  • 易于管理
  • 易于维护/错误跟踪/修复

缺点

  • 可伸缩性选项有限
  • 缺乏解决方案敏捷性
  • 缺乏对DevOps周期的支持

我们还有其他选择吗?

现在让我们退后一步,以不同的方式重新审视这个解决方案。如果我们按照物理世界中模块化的趋势重新构建相同的业务功能会怎样?这是一种看待相同问题的新方法。只需想象一个组织中的员工或部门如何运作,并识别其操作边界、角色和职责。然后思考他们将如何提供能力来帮助企业实现其更大的运营目标。这是一个扁平的结构,每个员工代表着能力,而这些能力由组织不同层级的另一组人员进行编排。让我们看看如何将这种思维过程扩展到重新定义该系统的架构。

微服务架构

图2:一个提供业务敏捷性的微服务架构

微服务架构的主要优点是服务的去中心化和简化。换句话说,微服务是DevOps革命后的第一个架构,它通过内置的容错、持续交付和持续集成降低了变更成本。然而,它仍然是一个高度复杂的架构,包含许多移动部件。但根据我们解决方案的主要业务要求,鉴于已有的严谨工程实践可以控制增加的复杂性,选择微服务作为我们的解决方案是最合乎逻辑的。

正确设计微服务面临的最大挑战之一是我们每个人都训练和实践过的“单体思维”。在审视任何现有单体应用程序之前,必须改变单体思维。因此,作为第一步,我们确定了一种新的解决方案技术,有助于实现从单体解决方案到微服务解决方案的范式转变。

理想情况下,在提取服务时,必须先考虑宏观层面,然后再考虑微观层面的操作。因此,我们考虑了许多真实世界组织的例子,并应用了各种微服务解决方案技术来识别更好的技术。在其中一次尝试中,我们以其手动形式的运作方式对业务进行了精确分析,并将所有可开发的垂直业务能力归类为一组独立的微观层面服务。

拆分大型单体应用程序的艺术始于识别领域及其交互。这是一个应用领域驱动设计 (DDD) 技术来寻找限界上下文(不要被教科书定义所迷惑,在这方面,请注意一个限界上下文有时也可以由共享单个领域模型的多个物理服务组成)的好地方,这将基本定义微服务的操作边界。“限界上下文”意味着业务中你可以单独识别的一个区域,它对其职责具有明确的边界。

让我们看一个与银行相关的例子

运营部是银行一个非常重要的部门。“整个‘运营服务’”是最初的限界上下文,还是“出纳服务”(属于“运营服务”的一部分)是限界上下文?从可行性角度来看,“运营服务”(宏观层面)将是一个比“出纳服务”(微观层面)更健康的起点。这正是您将简单的事情复杂化的地方。是的,一旦识别出边界,就可以进一步分解以识别每个细粒度级别的业务服务。

选择服务的操作边界总是很棘手。如果您选择一个大型操作作为微服务功能,它将无法提供微服务的架构价值。但如果您摘取一个超小的功能作为微服务,它将不会为业务带来价值,因为它会在服务之间造成高度摩擦/依赖。

此外,当我们谈论微服务时,我们也在谈论那些能够提供自主管理重要业务能力的服务。作为提供业务敏捷性的自主服务的一部分,重要的是要考虑当这些服务或系统的部分发生故障时会发生什么,以及系统如何应对故障。

微服务架构 - 消息流解决方案

任何复杂的系统都需要一种轻量级且灵活的方式相互交互,以提供真正的业务能力。这对于微服务也是如此。有些调用需要立即响应,而另一些则可以等待一段时间。还有一些其他系统只需要收到事件通知。

微服务确实允许能力解耦,但它们在相互交叉通信时需要额外考虑。当一个服务依赖于另一个服务来提供业务能力时(尤其是在需要立即给出同步响应时),耦合变得更紧密。但是,您这里有多种选择:

同步 – 这种方法允许一个服务直接调用另一个服务并等待其响应,然后再响应UI。

HTTP (超文本传输协议) – 您可以使用HTTP,一个无状态协议,作为标准。可以发出HTTP请求,期望从下游服务累积数据并返回响应。由于调用是异步的,因此在等待响应时可能需要阻塞线程。

图3:应用程序同步调用下游微服务

异步 – 这种方法将允许一个服务同时与一个或多个下游微服务通信。

  • HTTP – 您也可以将相同的HTTP协议用于异步调用模式,但这次您需要采用CQRS(命令查询责任分离)模式或类似模式才能使其工作。CQRS是一种将写入逻辑与读取逻辑分离的架构风格。UI可以使用一种服务方法将记录添加到数据库中,并使用另一种服务用新添加的详细信息更新/刷新用户界面。

    图4:CQRS架构的一个工作示例实现
  • AMQP (高级消息队列协议) – 您可以使用一个在两个微服务之间运行的系统来执行中介工作。在这种异步形式中,您将使用消息总线来保存传入消息,直到接收/订阅的微服务准备好处理它。

微服务架构 - 事件驱动架构

事件代表着关于已发生事件的信息(数据)流。“事件驱动”是一个系统对其环境做出反应的架构能力。更正式地说,事件驱动架构(EDA)是一种软件架构模式,它促进事件的产生、检测、消费和响应。

让我们看下面的例子

  1. 注册微服务创建一个新用户并发布一个“用户创建”事件。
  2. 通知服务接收事件并尝试向用户发送一封包含欢迎消息的电子邮件。
  3. UserManager服务也接收事件并尝试将记录添加到用户主表中。

采用EDA有很多优点。当系统响应事件而不是“即时”查询时,它可以很容易地开发成自治、容错和弹性的。此外,当架构由事件驱动时,它不仅耦合极其松散,而且分布良好。

这种分布式系统面临的主要挑战之一是保持服务之间的数据一致性。一种解决方案是使用事件驱动的最终一致性方法。在这种方法中,事件源发布已发生的事件,然后订阅者(事件的)对这些事件作出反应并相应地响应/更新其环境,使整个环境最终保持一致。

微服务架构 - 复杂事件处理器

图6:提议系统的总体架构

微服务架构 – 开发架构框架概念

在这个 DDD 设计练习中,我们利用了对手动操作系统的更熟悉的知识和理解来开发相应的软件系统。在手动系统中,记录由人工使用文档和文件进行维护。尽管它们存在所有问题,但大多数手动系统有一个巨大的优势,那就是它们稳定的流程和操作模型,这些模型经过多代演变以应对复杂情况。

考虑以下例子。设想一家拥有众多部门和员工的相当大的五金店。在雨天,如果主收银员不在岗,他们会关闭收银台并贴上“服务不可用”的告示吗?相反,他们会安排下一个有能力的员工接管职责来处理这种情况。在繁忙的午餐时间,大多数银行都会增加人手来应对出纳服务的负荷。在这种情况下,当地银行经理会根据优先级做出决定,以减少一项服务的服务交付能力并增加另一项服务。当一块土地被出售时,契约转让会发生,但不是立即发生,而是记录最终会保持一致。总之,即使在最关键的服务不可用时,手动操作的系统也有自己的方式继续其主要操作。这就是我们试图融入微服务架构设计的概念。

微服务架构 – 开发解决方案方法

图7:一个典型的组织利用其资源提供业务服务

现在,让我们再次审视一个典型的组织。在一个组织中,我们有员工,每个员工都有指定的职责或一组要执行的操作。所有这些职责最终都与组织提供的主要服务相关联。基于这种理解,我们是否应该将员工执行的每个操作识别为一组内部微服务?因此,在一个员工的操作边界内,可能存在五到六个这样的服务。接下来,我们再看看员工的工作环境(用来存放个人物品的储物柜,以及存放文件和文具的办公桌)。这个环境是一个容器,用于承载员工执行工作,这在微服务世界中可以是一个Docker容器。办公桌和文件就像一个内部微服务的本地数据库。

每个部门都由许多员工组成,这些员工需要相互沟通。同样,部门和业务单元也需要相互沟通,才能为最终用户提供真正的业务价值。您可以将这种情况想象为:一个微服务需要同步或异步调用另一个服务来完成其操作。如果微服务需要立即响应,那么直接调用依赖服务可能是一个选择。如果可以等待响应,那么消息队列可能是一个不错的选择。这种解决方案需要与手动系统中处理和管理相同问题的方式保持一致。

显然,这种方法更适用于相当复杂的系统。但是,如果你愿意用复杂性换取敏捷性,它也适用于简单的系统。为了成功地将软件系统映射到真实世界的组织,你需要正确分析软件系统,以解释它如何在纯手动环境中运行。下一步是使用软件模型自动化该手动过程。强烈建议在手动系统和软件系统之间保持相似的模块、通信模式、规则和命名约定,以便更好地可视化。

总而言之,在“领域驱动设计”中,通过比业务更了解业务领域来实现成功。领域专家与软件开发团队之间协作不力,以及缺乏研究和背景准备,可能导致整个解决方案生命周期中的许多失败。

领域驱动设计(DDD)、微服务和限界上下文

在领域驱动设计(DDD)中,限界上下文是中心点。领域故事告诉你每个工作人员如何在领域内相互协作,这就是你用来识别限界上下文的依据。然后你还需要识别它们是如何相互连接的。

从概念角度来看,领域驱动设计(DDD)旨在构建更贴近业务的解决方案模型。如前所述,限界上下文是一个有趣的概念,它定义了数据集普遍存在的边界。然后,当你超出该边界时,相同的数据集开始呈现不同的含义。在某个业务上下文中能够有意义地代表该数据集的实体称为领域模型。因此,如下图所示,一个业务上下文可以有许多物理服务,它们引用相同的领域模型(为该限界上下文定义),而该领域模型又派生自可能在多个其他限界上下文之间共享的实体。

图8:分组后以微指标形式交付的业务能力

在我们的方法中,我们选择了一种迭代式的DDD设计。让我们在下面定义我们设计过程的主要步骤:

  1. 了解主要领域、子领域和操作领域及其操作
    • 识别主要业务能力和功能
    • 识别交付业务能力的过程
    • 识别流程缺陷以及改进和优化流程的方法
    • 定义通用语言
  2. 理解内部和外部交互并定义有意义的操作边界
    • 从功能(任务/工作)角度识别参与交付业务能力的人员(员工/工人)
    • 识别通信方法(直接通信或通过中介)
    • 识别交互的频率、优先级、策略、效率和格式
  3. 识别并定义限界上下文和领域模型
    • 识别操作边界
    • 在寻找一起交付更高效的业务功能时定义限界上下文
    • 为每个限界上下文识别丰富的领域模型(贫血领域模型在微服务上下文中是一种反模式)。在这方面,每个限界上下文中的实体应自行组织以适应该领域的行为。
  4. 解决方案设计
    • 评估技术应用
    • 评估技术选项
    • 选择和引入技术

设计决策和理由

API 管理器

我们显然决定使用APIM作为保护层,以防止未经授权访问企业资产和服务。我们有三个主要系统:

  • 主要信息系统(一套Web/移动应用程序;它们都支持在线访问;总共包含近600个微服务;每天有数百万用户访问)。
  • 现成产品套件(所有产品都是基于Web/移动的,服务分离;它们应该与连接无关;每天有数千名员工访问)。
  • 远程部署系统(Web/移动应用程序;它们应该与连接无关;它们只支持远程部门所需的服务功能;每天有数千名员工访问)。

供应商做出了政策决定,将所有系统本地运行在供应商数据中心。主IS系统服务和现成产品后端服务部署在主API管理器后面,该管理器是中央数据中心的主要保护层。我们评估了为所有服务保留一个API管理器的选项,因为这将便于从中心位置管理访问、系统维护、流量控制和数据分析以及配置,但考虑到远程中心缺乏基础设施支持,我们不得不放弃这一选项。我们还被迫为每个远程办事处引入多个API管理器,这些办事处必须拥有支持离线访问的本地数据中心,并且还必须雇佣新的系统工程师来执行支持工作。然而,采用这种设计方法,我们不得不投入额外的精力使远程应用程序与连接无关。我们还引入了一个自定义软件代理(Merge-bot),以实现本地API数据库与主数据库的同步,从而为决策者提供整合报告。

基于容器的虚拟化

(参考:https://insights.sei.cmu.edu/sei_blog/2017/09/virtualization-via-containers.html

在真正的DDD应用设计中,如果微服务不能与最小可行硬件单元配对以独立运行,那么它们将无法拥有今天所具备的诸多优势。换句话说,微服务的真正力量只有在与容器化技术结合后才能体现出来。

在我们的设计中,容器通过降低硬件成本、提高可伸缩性、支持空间隔离、CI/CD、安全性和保密性为我们带来了许多优势。

尽管基于容器的虚拟化有许多优点,但我们在设计工作中不得不明确解决以下挑战和相关风险:

  • 一个硬件设备中的应用程序可能共享资源,包括操作系统内核、VM资源、容器引擎和主服务器的实际硬件。我们计划拥有两个硬件服务器实例以减轻此风险和单点故障。
  • 大量的干扰路径使得查找错误和对所有此类路径进行根本原因分析变得困难,尤其是在发生内存冲突(VM或容器)时。我们必须向客户解释这些缺点所带来的好处,并在项目开始前达成共识。
  • 容器蔓延(不必要的容器化)可能会增加容器管理所需的时间和精力。

为了解决其中一些问题,我们引入了一个软件工具来管理集群和容器编排。在选择集群管理解决方案时,我们还考虑了持续集成和持续交付。

消息队列/服务总线/事件处理器 – 渐进式架构

微服务是我们大多数业务需求的逻辑响应。尽管微服务提供了系统灵活性和业务敏捷性,但还需要额外的设计决策来降低复杂性,提高速度并改善系统性能。

考虑到单独的数据库许可证所需的成本,我们决定为每个限界上下文(一个限界上下文服务于属于该上下文的多个微服务)设置一个数据库模式。我们还为每个模式引入了用户ID,以防止开发人员跨限界上下文访问数据。然而,为了在复杂的业务事务跨越边界时保持数据一致性,我们采用了最终一致的事件驱动方法。一个关键亮点是我们在做出架构决策方面的灵活性——我们采用了多语言持久性架构,其中数据库、编程语言以及架构模式都是根据最适合该工作而选择的。因此,我们没有硬性规定,从而使事情变得僵化。

当以敏捷模式增强业务能力时,我们面临严峻挑战;每当您向一个上下文引入新能力时,您倾向于重新定义限界上下文的边界,这导致我们不得不进行额外的循环来平衡解决方案。这时领域驱动设计就派上用场了。我们当时只专注于一个业务领域,并以现实世界中的实践方式完成了我们的实现。

当一个服务需要与同一上下文中的另一个服务通信时,我们优先考虑使用HTTP请求直接调用。这是为了降低系统的复杂性。正如您在现实生活中可能观察到的,这就像银行服务人员直接获得经理批准,以进行超出限额的柜台取款。

同样,当您发现下游服务性能不佳时,您可以决定扩大环境规模,看看情况是否好转。在其他情况下,仅扩大规模可能无法满足负载要求;那么您需要向外扩展并将服务分配到其他新容器。在这些情况下,我们的实际方法是使新分配的服务使用与同一限界上下文中的其他服务共享的原始模式。这是为了减少与数据持久性/管理相关的复杂性。同样,您在现实生活中也会发现一些类似的设计决策。例如,设想一名银行员工表现不佳——您首先对该员工进行培训并监控进展(向上扩展),如果该员工在达到最大生产力限制后仍无法支持负载,您就会增加更多(向外扩展)服务人员。但是,当您决定向外扩展服务时,您的解决方案会进入下一个级别,届时您将被迫管理分发请求到多个端点所带来的额外挑战。

在我们的设计练习中,出现了一些模式:

  1. 当服务在同一限界上下文内分布式部署时,我们将其识别为部门,然后在软件系统中模拟其操作模式。例如,在会计部门,您可能有多名职员履行职责,他们可能拥有从主内容存储库借来的发票、账单和文件/账簿副本以履行职责。所有工作都堆积在职员的办公桌上——同样,我们为在同一限界上下文内运行的每个服务使用一个模式(数据缓存环境)。
  2. 当同一部门的一个员工需要与另一个员工沟通时,你会尝试直接与该员工沟通。如果服务繁忙,你可以将消息放入队列中,供服务在完成待处理工作后拾取。持有消息的消息队列以先进先出(FIFO)的方式将它们传递给一个或多个其他服务。为了实现这个功能,我们使用了一个特殊的基(抽象)服务类实现,其中包含共同行为可供派生。
  3. 当一个部门与另一个部门沟通时,需要更多的秩序。因此,对于跨边界通信,我们使用了服务总线。每个部门都有自己的服务总线。发布到服务总线的任何消息都会被多个订阅者(其他部门)引用。这种方法允许跨边界服务通信遵循开闭原则,因为源服务允许稍后添加更多与消息相关的功能而无需更改原始服务实现。例如,关于用户注册的消息可能需要发送电子邮件,因此电子邮件服务可以订阅该消息。但随着系统的扩展,您可能还会引入发送额外短信的选项。在这种情况下,您可以稍后实现短信功能,并让该服务订阅相同的消息以引入短信发送功能。
  4. 在所有这些通信的中间,我们还希望拥有另一个平台,它基本上监听所有事件,忽略某些事件并根据模式、规则和逻辑对其他某些事件采取行动,同时保持低延迟和高吞吐量。为了实现这一要求,我们在中间引入了一个CEP(复杂事件处理器)。

这种方法需要有人不断分析流量数据,以查看是否有任何下游服务表现不佳。这就是功能丰富的API管理器以及容器集群管理解决方案派上用场的地方。调整环境以提高性能是一项持续的活动。

历史

  • 2019-05-06:初始版本

参考文献

© . All rights reserved.