微服务:从设计到部署

未匹配的标注

原文地址
中文电子书

以下是相关资料及个人总结

微服务简介

优点

  • 它解决了复杂问题。
  • 这种架构使得每个服务都可以由一个团队独立专注开发。
  • 微服务架构模式可以实现每一个微服务独立部署。

缺点

  • 微服务这个术语的重点过多偏向于服务的规模。
  • 微服务是一个分布式系统,其使得整体变得复杂。
  • 分区数据库架构。
  • 测试微服务应用程序也很复杂。
  • 实现了跨越多服务变更。
  • 部署基于微服务的应用程序也是非常复杂的。

使用API网关搭建微服务

《Deploying NGINX Plus as an API Gateway》
八步部署NGINX Plus API网关

搭建微服务:微服务架构进程间通信

简介

在单体应用程序中,组件可通过语言级方法或者函数相互调用。相比之下,基于微服务的应用程序是一个运行在多台机器上的分布式系统。通常,每个服务实例都是一个进程。因此,如图所示,服务必须使用进程间通信(IPC)机制进行交互。

微服务:从设计到部署

异步、基于消息的通信

笔者不太懂

同步的请求/响应 IPC

REST

单页应用的HATEOAS实战 | 洞见

微服务架构中的服务发现

微服务的事件驱动数据管理

分布式数据管理方面的挑战

  1. 如何实现维护多个服务之间的业务事务一致性
  2. 如何实现从多个服务中检索数据

事件驱动架构

许多应用使用了事件驱动架构作为解决方案。在此架构中,微服务在发生某些重要事件时发布一个事件,例如更新业务实体时。其他微服务订阅了这些事件,当微服务接收到一个事件时,它可以更新自己的业务实体,这可能导致更多的事件被发布。
您可以使用事件实现跨多服务的业务事务。一个事务由一系列的步骤组成。每个步骤包括了微服务更新业务实体和发布事件所触发的下一步骤。

微服务通过 Message Broker (消息代理)进行交换事件:

  1. Order Service(订单服务)创建一个状态为 NEW 的订单,并发布一个 Order Created (订单创建)事件。
    微服务:从设计到部署
  2. Customer Service (客户服务)消费了 Order Created 事件,为订单预留信用额
    度,并发布 Credit Reserved 事件。
    微服务:从设计到部署
  3. Order Service 消费了 Credit Reserved (信用预留)事件并将订单的状态更改为 OPEN。
    微服务:从设计到部署

优点:它能够实现跨越多服务并提供最终一致性事务。另一个好处是它还使得应用程序能够维护物化视图。
缺点:编程模型比使用 ACID 事务更加复杂。通常,您必须实现补偿事务以从应用程序级别的故障中恢复。例如,如果信用检查失败,您必须取消订单。此外,应用程序必须处理不一致的数据。因为未提交的事务所做的更改是可见的。如果从未更新的物化视图中读取,应用程序依然可以看到不一致性。另一个缺点是订阅者必须要检测和忽略重复的事件。

使用本地事务发布事件

诀窍在于存储业务实体状态的数据库中有一个用作消息队列的 EVENT 表。应用程序开启一个(本地)数据库事务,更新业务实体状态,将事件插入到 EVENT 表中,之后提交事务。一个单独的应用程序线程或进程查询 EVENT 表,将事件发布到 Message Broker,然后使用本地事务将事件标记为已发布。

微服务:从设计到部署
Order Service 将一行记录插入到 ORDER 表中,并将一个 Order Created 事件插入到 EVENT 表中。Event Publisher(事件发布者)线程或进程从 EVENT 表中查询未发布的事件,之后发布这些事件,最后更新 EVENT 表以将事件标记为已发布。

优点:是它保证了被发布的事件每次更新都不依赖于 2PC。此外,应用程序发布业务级事件,这些事件可以消除推断的需要。
缺点:它很容易出错,因为开发人员必须要记得发布事件。这种方法的局限性在于,由于其有限的事务和查询功能,在使用某些 NoSQL 数据库时,实现起来将是一大挑战。

使用事件溯源

事件溯源通过使用完全不同的、不间断的方式来持久化业务实体,实现无 2PC 原子性。应用程序不存储实体的当前状态,而是存储一系列状态改变事件。该应用程序通过回放事件来重建实体的当前状态。无论业务实体的状态何时发生变化,其都会将新事件追加到事件列表中。由于保存事件是一个单一操作,因此具有原子性。
要了解事件溯源的工作原理,以 Order(订单)实体为例。在传统方式中,每个订单都与 ORDER 表中的某行记录相映射,也可以映射到例如 ORDER_LINE_ITEM 表中的记录。
但当使用事件溯源时,Order Service 将以状态更改事件的形式存储 Order:Created(创建)、Approved(批准)、Shipped(发货)、Cancelled(取消)。每个事件包含足够的数据来重建 Order 的状态。

微服务:从设计到部署

事件被持久化在事件存储中,事件存储是一个事件数据库。该存储有一个用于添加和检索实体事件的 API。
事件存储还与我们之前描述的架构中的 Message Broker 类似。它提供了一个 API,使得服务能够订阅事件。事件存储向所有感兴趣的订阅者派发所有事件。可以说事件存储是事件驱动微服务架构的支柱。

优点:它解决了实现事件驱动架构的关键问题之一,可以在状态发生变化时可靠地发布事件。因此,它解决了微服务架构中的数据一致性问题。此外,由于它持久化的是事件,而不是领域对象,所以它主要避免了对象关系阻抗失配问题。事件溯源还提供了对业务实体所做更改的 100% 可靠的审计日志,可以实现在任何时间点对实体进行时间查询以确定状态。事件溯源的另一个主要好处是您的业务逻辑包括松耦合的交换事件业务实体,这使得从单体应用程序迁移到微服务架构将变得更加容易。
缺点:这是一种不同而陌生的编程风格,因此存在学习曲线。事件存储仅支持通过主键查找业务实体。您必须使用命令查询责任分离(CQRS)来实现查询。因此,应用程序必须处理最终一致的数据。
DDD 中的那些模式 — CQRS
微服务下的数据一致性的几种实现方式之概述

订单处理:本地事务
资金账户加款、积分账户增加积分:TCC型事务(或两阶段提交型事务),实时性要求比较高,数据必须可靠。
会计记账:异步确保型事务(基于可靠消息的最终一致性,可以异步,但数据绝对不能丢,而且一定要记账成功)
商户通知:最大努力通知型事务(按规律进行通知,不保证数据一定能通知成功,但会提供可查询操作接口进行核对)
微服务实现事务一致性实例

选择微服务部署策略

将整体拆分为微服务

微服务重构概述

策略一:停止挖掘

洞穴定律说到,每当您身处在一个洞穴中,您应该停止挖掘。当您的单体应用变得难以管理时,这是一个很好的建议。换句话说,您应该停止扩张,避免使单体变得更大。这意味着当您要实现新功能时,您不应该向单体添加更多的代码。相反,这一策略的主要思想是将新代码放在独立的微服务中。

应用此方法后,系统架构如图所示。

微服务:从设计到部署

除了新服务和传统的单体,还有另外两个组件。第一个是请求路由,它处理传入的(HTTP)请求,类似于第二章中描述的 API 网关。路由向新服务发送与新功能相对应的请求。它将遗留的请求路由到单体。
另一个组件是粘合代码,它将服务与单体集成。一个服务很少孤立存在,通常需要访问单体的数据。位于单体、服务或两者中的粘合代码负责数据集成。该服务使用粘合代码来读取和写入单体数据。
服务可以使用三种策略来访问单体数据:

  • 调用由单体提供的远程 API
  • 直接访问单体数据库
  • 维护自己的数据副本,与单体数据库同步

粘合代码有时被称为防护层(anti-corruption layer)。这是因为粘合代码阻止了服务被遗留的单体领域模型的概念所污染,这些服务具有自己的原始领域模型。粘合代码在两种不同的模型之间转换。防护层一词首先出现于埃里克·埃文斯(Eric Evans)所著的必读图书《领域驱动设计》(Domain Driven Design)中,并在白皮书中进行了改进。开发一个防护层并不是一件简单的事情。但是,如果您想要从单体地狱中走出来,这是必不可少的步骤。
使用轻量级服务来实现新功能有几个好处。它防止单体变得更加难以管理。该服务可以独立于单体开发、部署和扩展。可让您创建的每个新服务体验到微服务架构的优势。
然而,这种方法没有解决单体问题。要解决这些问题,您需要分解单体。让我们来看看这样做的策略。

策略二:前后端分离

缩小单体应用的一个策略是从业务逻辑层和数据访问层拆分出表现层。一个典型的企业应用由至少三种不同类型的组件组成:

  • 表现层(Presentation Layer,PL)
    处理 HTTP 请求并实现(REST)API 或基于 HTML 的 Web UI 组件。在具有复杂用户界面的应用中,表现层通常存在大量代码。
  • 业务逻辑层(Business Logic Layer,BLL)
    作为应用程序核心,实现业务规则的组件。
  • 数据访问层(Data Access Layer,DAL)
    访问基础架构组件的组件,如数据库和消息代理。

一方的表现逻辑和另一方的业务和数据访问逻辑之间通常有一个完全的隔离。业务层具有由一个或多个门面组成的粗粒度 API,其封装了业务逻辑组件。这个 API 是一个天然的边界,您可以沿着该边界将单体拆分成两个较小的应用程序。一个应用程序包含表现层。另一个应用程序包含业务和数据访问逻辑。分割后,表现逻辑应用程序对业务逻辑应用程序进行远程调用。

重构之前和之后的架构如图所示

微服务:从设计到部署

以这种方式拆分单体有两个主要优点。它使您能够独立于彼此开发、部署和扩展这两个应用。特别是它允许表现层开发人员在用户界面上快速迭代,并且可以轻松执行A/B 测试。这种方法的另一个优点是它暴露了可以被您开发的微服务调用的远程 API。
然而,这一策略只是一个局部解决方案。两个应用程序中的一个或两个很可能是一个无法管理的单体。您需要使用第三种策略来消除剩余的整体或单体。

策略三:提取服务

第三个重构策略是将庞大的现有模块转变为独立的微服务。每次提取一个模块并将其转换成服务时,单体就会缩小。一旦您转换了足够的模块,单体将不再是一个问题。或者它完全消失,或者变得足够小,它就可以被当做一个服务看待。

优先将哪些模块转换为微服务

一个庞大而复杂的单体应用由几十个或几百个模块组成,所有模块都是提取的候选项。弄清楚要先转换哪些模块往往存在一定的挑战。一个好的方法是从容易提取的几个模块开始。您将得到微服务的相关经验,特别是在提取过程方面。之后,您应该提取那些能给您最大利益的模块。
将模块转换为服务通常是耗时的。您想按照您将获得的利益对模块进行排列。提取频繁更改的模块通常是有益的。一旦将模块转换为服务,您就可以独立于单体开发和部署,这将加快开发工作。
提取这些与单体的其他模块有显著不同的模块也是有益的。例如,将有一个有内存数据库的模块转换为服务是很有用的,这样可以部署在具有大量内存的主机上,无论是裸机服务器、虚拟机还是云实例。同样,提取实现了计算昂贵算法的模块也是值得的,因为该服务可以部署在具有大量 CPU 的主机上。通过将具有特定资源需求的模块转换为服务,您可以使应用程序更加容易、廉价地扩展。
当找到要提取的模块时,寻找现有的粗粒度边界(又称为接缝)是有用的。它们使模块转成服务变得更容易和更连廉价。有关这种边界的一个例子是一个仅通过异步消息与应用程序的其他部分进行通信的模块。将该模块转变为微服务相对比较廉价和简单。

如何提取模块

提取模块的第一步是在模块和单体之间定义一个粗粒度的接口。因为单体需要服务拥有的数据,它很可能是一个双向 API,反之亦然。由于模块和应用程序的其余之间存在着复杂的依赖关系和细粒度的交互模式,因此实现这样的 API 通常存在挑战。由于领域模型类之间的众多关联,使用领域模型模式来实现的业务逻辑尤其具有挑战性。您通常需要进行重大的代码更改才能打破这些依赖。

微服务:从设计到部署

一旦实现了粗粒度的接口,您就可以将模块变成独立的服务。要做到这点,您必须编写代码以使单体和服务通过使用进程间通信(IPC)机制的 API 进行通信。上图显示了重构前、重构中和重构后的架构。
在此例中,模块 Z 是要提取的候选模块。其组件由模块 X 使用,并且它使用了模块Y。第一个重构步骤是定义一对粗粒度的 API。第一个接口是一个使用模块 X 来调用模块 Z 的入站接口。第二个接口是一个使用模块 Z 调用模块 Y 的出站接口。
第二个重构步骤是将模块转换为一个独立服务。入站和出站接口使用 IPC 机制的代码来实现。您将很可能需要通过将 Module Z 与 Microservice Chassis 框架相结合来构建服务,该框架负责处理诸如服务发现之类的横切点。
一旦您提取了一个模块,您就可以独立于单体和任何其他服务开发、部署和扩展其他服务。您甚至可以从头开始重写服务。在这种情况下,整合服务与单体的 API 代码成为在两个领域模型之间转换的防护层。每次提取服务时,您都会朝微服务方向迈近一步。随着时间的推移,单体将缩小,您将拥有越来越多的微服务。

总结

将现有应用程序迁移到微服务的过程是应用程序现代化的一种形式。您不应该从头开始重写您的应用来迁移到微服务。相反,您应该将应用程序逐渐重构为一组微服务。可以使用这三种策略:将新功能实现为微服务;从业务组件和数据访问组件中分离出表现组件;将单体中的现有模块转换为服务。随着时间推移,微服务的数量将会增长,您的开发团队的灵活性和速度也同样会增加

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
秦晓武
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 0
发起讨论 只看当前版本


暂无话题~