您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
微服务:从设计到部署(三)
 
来源:网络 发布于: 2017-10-11
  2362  次浏览      17
 

本文是系列文章,文章的第一篇微服务:从设计到部署(一),第二篇微服务:从设计到部署(二)

5、事件驱动数据管理

本书主要介绍如何使用微服务构建应用程序,这是本书的第五章。第一章介绍了微服务架构模式,讨论了使用微服务的优点与缺点。第二和第三章描述了微服务架构内通信方式的对比。第四章探讨了与服务发现相关的内容。在本章中,我们稍微做了点调整,研究微服务架构中出现的分布式数据管理问题。

5.1、微服务和分布式数据管理问题

单体应用程序通常具有一个单一的关系型数据库。使用关系型数据库的一个主要优点是您的应用程序可以使用 ACID 事务,这些事务提供了以下重要保障:

原子性(Atomicity) — 所作出的更改是原子操作,不可分割

一致性(Consistency) — 数据库的状态始终保持一致

隔离性(Isolation) — 即使事务并发执行,但他们看起来更像是串行执行

永久性(Durable) — 一旦事务提交,它将不可撤销

因此,您的应用程序可以很容易地开始事务、更改(插入、更新和删除)多行记录,并提交事务。

使用关系型数据库的另一大好处是它提供了 SQL,这是一种丰富、声明式和标准化的查询语言。您可以轻松地编写一个查询组合来自多个表的数据,之后,RDBMS 查询计划程序将确定执行查询的最佳方式。您不必担心如何访问数据库等底层细节。因为您所有的应用程序数据都存放在同个数据库中,因此很容易查询。

很不幸的是,当我们转向微服务架构时,数据访问将变得非常复杂。因为每个微服务所拥有的数据对当前微服务来说是私有的,只能通过其提供的 API 进行访问。封装数据可确保微服务松耦合、独立演进。如果多个服务访问相同的数据,模式(schema)更新需要对所有服务进行耗时、协调的更新。

更糟糕的是,不同的微服务经常使用不同类型的数据库。现代应用程序存储和处理着各种数据,而关系型数据库并不总是最佳选择。在某些场景,特定的 NoSQL 数据库可能具有更方便的数据模型,提供了更好的性能和可扩展性。例如,存储和查询文本的服务使用文本搜索引擎(如 Elasticsearch)是合理的。类似地,存储社交图数据的服务应该可以使用图数据库,例如 Neo4j。因此,基于微服务的应用程序通常混合使用 SQL 和 NoSQL 数据库,即所谓的混合持久化(polyglot persistence)方式。

分区的数据存储混合持久化架构具有许多优点,包括了松耦合的服务以及更好的性能与可扩展性。然而,它也引入了一些分布式数据管理方面的挑战。

第一个挑战是如何实现维护多个服务之间的业务事务一致性。要了解此问题,让我们先来看一个在线 B2B 商店的示例。Customer Service (顾客服务)维护客户相关的信息,包括信用额度。Order Service (订单)负责管理订单,并且必须验证新订单,不得超过客户的信用额度。在此应用程序的单体版本中,Order Service 可以简单地使用 ACID 交易来检查可用信用额度并创建订单。

相比之下,在微服务架构中,ORDER (订单)和 CUSTOMER (顾客)表对其各自的服务都是私有的,如图 5-1 所示:

Order Service 无法直接访问 CUSTOMER 表。它只能使用客户服务提供的 API。订单服务可能使用了分布式事务,也称为两阶段提交(2PC)。然而,2PC 在现代应用中通常是不可行的。CAP 定理要求您在可用性与 ACID 式一致性之间做出选择,可用性通常是更好的选择。此外,许多现代技术,如大多数 NoSQL 数据库,都不支持 2PC。维护服务和数据库之间的数据一致性至关重要,因此我们需要另一套解决方案。

第二个挑战是如何实现从多个服务中检索数据。例如,我们假设应用程序需要显示一个顾客和他最近的订单。如果 Order Service 提供了用于检索客户订单的 API,那么您可以使用应用程序端连接以检索数据。应用程序从 Customer Service 中检索客户,并从 Order Service 中检索客户的订单。但是,假设 Order Service 仅支持通过主键查找订单(也许它使用了仅支持基于主键检索的 NoSQL 数据库)。在这种情况下,没有有效的方法来检索所需的数据。

5.2、事件驱动架构

许多应用使用了事件驱动架构作为解决方案。在此架构中,微服务在发生某些重要事件时发布一个事件,例如更新业务实体时。其他微服务订阅了这些事件,当微服务接收到一个事件时,它可以更新自己的业务实体,这可能导致更多的事件被发布。

您可以使用事件实现跨多服务的业务事务。一个事务由一系列的步骤组成。每个步骤包括了微服务更新业务实体和发布事件所触发的下一步骤。下图依次展示了如何在创建订单时使用事件驱动方法来检查可用信用额度。

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

Order Service (订单服务)创建一个状态为 NEW 的订单,并发布一个 Order Created (订单创建)事件。

Customer Service (客户服务)消费了 Order Created 事件,为订单预留信用额度,并发布 Credit Reserved 事件。

Order Service 消费了 Credit Reserved (信用预留)事件并将订单的状态更改为 OPEN。

更复杂的场景可能会涉及额外的步骤,例如在检查客户信用的同时保留库存。

假设(a)每个服务原子地更新数据库并发布事件,稍后再更新,(b)Message Broker 保证事件至少被传送一次,您可以实现跨多服务的业务事务。需要注意的是,这些并不是 ACID 事务。它们只提供了更弱的保证,如最终一致性。该事务模型称为 BASE 模型。

您还可以使用事件来维护多个微服务预先加入所拥有的数据的物化视图(materialized view)。维护视图的服务订阅了相关事件并更新视图。图 5-5 展示了 Customer Order View Updater Service (客户订单视图更新服务)根据 Customer Service 和 Order Service 发布的事件更新 Customer Order View (客户订单服务)。

当 Customer Order View Updater Service 接收到 Customer 或 Order 事件时,它会更新 Customer Order View 数据存储。您可以使用如 MongoDB 之类的文档数据库实现 Customer Order View,并为每个 Customer 存储一个文档。Customer Order View Query Service(客户订单视图查询服务)通过查询 Customer Order View 数据存储来处理获取一位客户和最近的订单的请求。

事件驱动的架构有几个优点与缺点。它能够实现跨越多服务并提供最终一致性事务。另一个好处是它还使得应用程序能够维护物化视图。

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

5.3、实现原子性

在事件驱动架构中,同样存在着原子更新数据库和发布事件相关问题。例如,Order Service 必须在 ORDER 表中插入一行数据,并发布 Order Created 事件。这两个操作必须原子完成。如果在更新数据库后但在发布事件之前发生服务崩溃,系统将出现不一致性。确保原子性的标准方法是使用涉及到数据库和 Message Broker 的分布式事务。然而,由于上述原因,如 CAP 定理,这并不是我们想做的。

5.4、使用本地事务发布事件

实现原子性的一种方式是应用程序使用仅涉及本地事务的多步骤过程来发布事件。诀窍在于存储业务实体状态的数据库中有一个用作消息队列的 EVENT 表。应用程序开启一个(本地)数据库事务,更新业务实体状态,将事件插入到 EVENT 表中,之后提交事务。一个单独的应用程序线程或进程查询 EVENT 表,将事件发布到 Message Broker,然后使用本地事务将事件标记为已发布。设计如图 5-6 所示。

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

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

该方法通过让应用程序使用本地事务更新状态和发布事件来消除对 2PC 的依赖。现在我们来看一下通过应用程序简单地更新状态来实现原子性的方法。

5.5、挖掘数据库事务日志

不依靠 2PC 来实现原子性的另一种方式是使用线程或进程发布事件,该线程或进程对数据库的事务或者提交日志进行挖掘。当应用程序更新数据库时,更改信息被记录到数据库的事务日志中。Transaction Log Miner 线程或进程读取事务日志并向 Message Broker 发布事件。设计如图 5-7 所示。

一个使用此方法的示例是 LinkedIn Databus 开源项目。Databus 挖掘 Oracle 事务日志并发布与更改相对应的事件。LinkedIn 使用 Databus 保持与记录系统一致的各种派生数据存储。

另一个例子是 AWS DynamoDB 中的流机制,它是一个托管的 NoSQL 数据库。DynamoDB 流包含了在过去 24 小时内对 DynamoDB 表中的项进行的更改(创建、更新和删除操作),其按时间顺序排列。应用程序可以从流中读取这些更改,比如,将其作为事件发布。

事务日志挖掘有各种好处与坏处。一个好处是它能保证被发布的事件每次更新都不依赖于 2PC。事务日志挖掘还可以通过将事件发布与应用程序的业务逻辑分离来简化应用程序。一个主要的缺点是事务日志的格式对于每个数据库来说都是专有的,甚至在数据库版本之间格式就发生了改变。而且,记录于事务日志中的低级别更新可能难以对高级业务事件进行逆向工程。

事务日志挖掘消除了应用程序在做一件事时对 2PC 的依赖:更新数据库。现在我们来看看另一种可以消除更新并仅依赖于事件的不同方式。

5.6、使用事件溯源

事件溯源通过使用完全不同的、不间断的方式来持久化业务实体,实现无 2PC 原子性。应用程序不存储实体的当前状态,而是存储一系列状态改变事件。该应用程序通过回放事件来重建实体的当前状态。无论业务实体的状态何时发生变化,其都会将新事件追加到事件列表中。由于保存事件是一个单一操作,因此具有原子性。

要了解事件溯源的工作原理,以 Order(订单)实体为例。在传统方式中,每个订单都与 ORDER 表中的某行记录相映射,也可以映射到例如 ORDER_LINE_ITEM 表中的记录。

但当使用事件溯源时,Order Service 将以状态更改事件的形式存储 Order:Created(创建)、Approved(批准)、Shipped(发货)、Cancelled(取消)。每个事件包含足够的数据来重建 Order 的状态。

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

事件溯源有几个好处。它解决了实现事件驱动架构的关键问题之一,可以在状态发生变化时可靠地发布事件。因此,它解决了微服务架构中的数据一致性问题。此外,由于它持久化的是事件,而不是领域对象,所以它主要避免了对象关系阻抗失配问题。事件溯源还提供了对业务实体所做更改的 100% 可靠的审计日志,可以实现在任何时间点对实体进行时间查询以确定状态。事件溯源的另一个主要好处是您的业务逻辑包括松耦合的交换事件业务实体,这使得从单体应用程序迁移到微服务架构将变得更加容易。

事件溯源同样有缺点。这是一种不同而陌生的编程风格,因此存在学习曲线。事件存储仅支持通过主键查找业务实体。您必须使用命令查询责任分离(CQRS)来实现查询。因此,应用程序必须处理最终一致的数据。

5.7、总结

在微服务架构中,每个微服务都有私有的数据存储。不同的微服务可能会使用不同的 SQL 或者 NoSQL 数据库。虽然这种数据库架构具有明显的优势,但它创造了一些分布式数据管理挑战。第一个挑战是如何实现维护多个服务间的业务事务一致性。第二个挑战是如何实现从多个服务中检索数据。

大部分应用使用的解决方案是事件驱动架构。实现事件驱动架构的一个挑战是如何以原子的方式更新状态以及如何发布事件。有几种方法可以实现这点,包括了将数据库作为消息队列、事务日志挖掘和事件溯源。

微服务实战:NGINX 与存储优化

by Floyd Smith

基于微服务的存储方式涉及大数量和各种数据存储,访问和更新数据将变得更加复杂,DevOps 在维护数据一致性方面面临着更大的挑战。NGINX 为这种数据管理提供了重要支持,主要有三个方面:

数据缓存与微缓存(microcaching)

使用 NGINX 缓存静态文件和微缓存应用程序生成的内容可减轻应用程序的负载、提高性能并减少问题的发生。

数据存储的灵活性与可扩展性

一旦将 NGINX 作为反向代理服务器,您的应用程序在创建、调整大小、运行和调整数据存储服务器的大小时可获得很大的灵活性,以满足不断变化的需求 — 每个服务都拥有自己的数据存储是很重要的。

服务监控与管理,包括数据服务

随着数据服务器数量的增加,支持复杂操作和具有监控和管理工具显得非常重要。NGINX Plus 内置了这些工具和应用程序性能管理合作伙伴的接口,如 Data Dog、Dynatrace 和 New Relic。

微服务相关的数据管理示例可在 NGINX 微服务参考架构的三大模型中找到,其为您设计决策和实施提供了起点。

6、选择部署策略

本书主要介绍关于如何使用微服务构建应用程序,这是本书的第六章。第一章介绍了微服务架构模式,讨论了使用微服务的优点与缺点。之后的章节讨论了微服务架构的方方面面:使用 API 网关、进程间通信、服务发现和事件驱动数据管理。在本章中,我们将介绍部署微服务的策略。

6.1、动机

部署单体应用程序意味着运行一个或多个相同副本的单个较大的应用程序。您通常会在每台服务器上配置 N 个服务器(物理或虚拟)并运行 M 个应用程序实例。单体应用程序的部署并不总是非常简单,但它比部署微服务应用程序要简单得多。

微服务应用程序由数十甚至上百个服务组成。服务以不同的语言和框架编写。每个都是一个迷你的应用程序,具有自己特定的部署、资源、扩展和监视要求。例如,您需要根据该服务的需求运行每个服务的一定数量的实例。此外,必须为每个服务实例提供相应的 CPU、内存和 I/O 资源。更具挑战性的是尽管如此复杂,部署服务也必须快速、可靠和具有成本效益。

有几种不同的微服务部署模式。我们首先看看单主机多服务实例模式。

6.2、单主机多服务实例模式

部署微服务的一种方式是使用单主机多服务实例(Multiple Service Instances per Host)模式。当使用此模式时,您可以提供一个或多个物理主机或虚拟主机,并在每个上运行多个服务实例。从多方面来讲,这是应用程序部署的传统方式。每个服务实例在一个或多个主机的标准端口上运行。主机通常被当作宠物对待。

图 6-1 展示了该模式的结构:

这种模式有几个变体。一个变体是每个服务实例都是一个进程或进程组。例如,您可以在 Apache Tomcat 服务器上将 Java 服务实例部署为 Web 应用程序。一个 Node.js 服务实例可能包含一个父进程和一个或多个子进程。

此模式的另一个变体是在同一进程或进程组中运行多个服务实例。例如,您可以在同一个 Apache Tomcat 服务器上部署多个 Java Web 应用程序,或在同一 OSGI 容器中运行多个 OSGI 软件包。

单主机多服务实例模式有优点也有缺点。主要优点是其资源使用率相对较高。多个服务实例共享服务器及其操作系统。如果进程或进程组运行了多个服务实例(例如,共享相同的 Apache Tomcat 服务器和 JVM 的多个 Web 应用程序),则效率更高。

这种模式的另一个优点是部署服务实例相对较快。您只需将服务复制到主机并启动它。如果服务是使用 Java 编写的,则可以复制 JAR 或 WAR 文件。对于其他语言,例如 Node.js 或 Ruby,您可以直接复制源代码。在任一情况下,通过网络复制的字节数都是相对较小的。

另外,由于缺乏开销,通常启动一个服务是非常快的。如果该服务是自己的进程,你只需要启动它即可。如果服务是在同一容器进程或进程组中运行的几个实例之一,则可以将其动态部署到容器中或者重新启动容器。

尽管这很有吸引力,但单主机多服务实例模式有一些明显的缺点。一个主要的缺点是服务实例很少或者没有隔离,除非每个服务实例是一个单独的进程。虽然您可以准确地监视每个服务实例的资源利用率,但是您不能限制每个实例使用的资源。一个行为不当的服务实例可能会占用掉主机的所有内存或 CPU。

如果多个服务实例在同一进程中运行,那么将毫无隔离可言。例如,所有实例可能共享相同的 JVM 堆。行为不当的服务实例可能会轻易地破坏在同一进程中运行的其他服务。此外,您无法监控每个服务实例使用的资源。

这种方式的另一个重要问题是部署服务的运维团队必须了解执行此操作的具体细节。服务可以用多种语言和框架编写,因此开发团队必须与运维交代许多细节。这种复杂性无疑加大了部署过程中的错误风险。

正如您所见,尽管这种方式简单,但单主机多服务实例模式确实存在一些明显的缺点。现在让我们来看看可以绕过这些问题部署微服务的其他方式。

6.3、每个主机一个服务实例模式

部署微服务的另一种方式是使用每个主机一个服务实例(Service Instance per Host)模式。当使用此模式时,您可以在主机上单独运行每个服务实例。这种模式有两种不同形式:每个虚拟机一个服务实例模式和每个容器一个服务实例模式。

6.3.1、每个虚拟机一个服务实例模式

当您使用每个虚拟机一个服务实例模式时,将每个服务打包成一个虚拟机(VM)镜像(如 Amazon EC2 AMI)。每个服务实例都是一个使用该 VM 镜像启动的 VM(例如,一个 EC2 实例)。

图 6-2 展示了该模式的结构:

这是 Netflix 部署其视频流服务的主要方式。Netflix 使用 Aminator 将每个服务打包为 EC2 AMI。每个运行的服务实例都是一个 EC2 实例。

您可以使用多种工具来构建自己的虚拟机。您可以配置您的持续集成(CI)服务器(比如 Jenkins)来调用 Aminator 将服务打包为一个 EC2 AMI。Packer 是自动化虚拟机镜像创建的另一个选择。与 Aminator 不同,它支持各种虚拟化技术,包括 EC2、DigitalOcean、VirtualBox 和 VMware。

Boxfuse 公司有一种非常棒的方式用来构建虚拟机镜像,其克服了我将在下面描述的虚拟机的缺点。Boxfuse 将您的 Java 应用程序打包成一个最小化的 VM 镜像。这些镜像可被快速构建、快速启动且更加安全,因为它们暴露了一个有限的攻击面。

CloudNative 公司拥有 Bakery,这是一种用于创建 EC2 AMI 的 SaaS 产品。您可以配置您的 CI 服务器,以在微服务通过测试后调用 Bakery。之后 Bakery 将您的服务打包成一个 AMI。使用一个如 Bakery 的 SaaS 产品意味着您不必浪费宝贵的时间来设置 AMI 创建基础架构。

每个虚拟机一个服务实例模式有许多优点。VM 的主要优点是每个服务实例运行是完全隔离的。它有固定数量的 CPU 和内存,且不能从其他服务窃取资源。

将微服务部署为虚拟机的另一个优点是可以利用成熟的云基础架构。如 AWS 之类的云提供了有用的功能,例如负载平衡和自动扩缩。

将服务部署为虚拟机的另一个好处是它封装了服务的实现技术。一旦服务被打包成一个虚拟机,它就成为一个黑匣子。VM 的管理 API 成为部署服务的 API。部署变得更加简单、可靠。

然而,每个虚拟机一个服务实例模式也有一些缺点。一个缺点是资源利用率较低。每个服务实例都有一整个 VM 开销,包括操作系统。此外,在一个典型的公共 IaaS 中,VM 具有固定大小,并且 VM 可能未被充分利用。

此外,公共 IaaS 中的 VM 通常是收费的,无论它们是处于繁忙还是空闲。如 AWS 之类的 IaaS 虽然提供了自动扩缩功能,但很难快速响应需求变化。因此,您经常需要过度配置 VM,从而增加部署成本。

这种方法的另一缺点是部署新版本的服务时通常很慢。由于大小原因,通常 VM 镜像构建很慢。此外,VM 实例化也很慢,同样是因为它们的大小。而且,操作系统也需要一些时间来启动。但请注意,这并不普遍,因为已经存在由 Boxfuse 构建的轻量级 VM。

每个虚拟机一个服务实例模式的另一个缺点是通常您(或组织中的其他人)要对很多未划分的重担负责。除非您使用 Boxfuse 这样的工具来处理构建和管理虚拟机的开销,否则这将是您的责任。这个必要而又耗时的活动会分散您的核心业务。

接下来让我们看看另一种部署更轻量级微服务的替代方式,它也有许多与虚拟机一样的优势。

6.3.2、每个容器一个服务实例模式

当您使用每个容器一个服务实例模式(Service Instance per Container)模式时,每个服务实例都在其自己的容器中运行。容器是一个操作系统级虚拟化机制。一个容器是由一个或多个运行在沙箱中的进程组成。从进程的角度来看,它们有自己的端口命名空间和根文件系统。您可以限制容器的内存和 CPU 资源。一些容器实现也具有 I/O 速率限制。容器技术的相关例子有 Docker 和 Solaris Zones。

图 6-3 展示了该模式的结构:

要使用此模式,请将您的服务打包成一个容器镜像。容器镜像是由运行服务所需的应用程序和库组成的文件系统镜像。一些容器镜像由完整的 Linux 根文件系统组成。此外它更加轻便。例如,要部署一个 Java 服务,您可以构建一个包含了 Java 运行时的容器镜像,可能是一个 Apache Tomcat 服务器和编译好的 Java 应用程序。

将服务打包成一个容器镜像后,您将启动一个或多个容器。通常在每个物理或虚拟主机上运行多个容器。您可以使用集群管理工具(如 Kubernetes 或 Marathon)来管理容器。集群管理工具将主机视为一个资源池。它根据容器所需的资源和每个主机上可用的资源来决定每个容器放置的位置。

每个容器一个服务实例模式模式有优点和缺点。容器的优点与虚拟机的类似。它们将服务实例彼此隔离。您可以轻松地监控每个容器所消耗的资源。此外,与 VM 一样,容器封装了服务实现技术。容器管理 API 作为管理您的服务的 API。

然而,与虚拟机不同,容器是轻量级技术。容器镜像通常可以非常快速地构建。例如,在我的笔记本电脑上,将一个 Spring Boot 应用程序打包成一个 Docker 容器只需要 5 秒钟的时间。容器也可以很快地启动,因为没有繁琐的操作系统引导机制。当一个容器启动时,它所运行的就是服务。

使用容器有一些缺点。虽然容器基础架构正在快速发展走向成熟,但它并不像虚拟机的基础架构那么成熟。此外,容器不像 VM 那样安全,因为容器彼此共享了主机的 OS 内核。

容器的另一个缺点是您需要负责未划分的容器镜像管理重担。此外,除非您使用了托管容器解决方案[如 Google Container Engine 或 Amazon EC2 Container Service(ECS)],否则您必须自己管理容器基础设施以及可能运行的 VM 基础架构。

此外,容器通常部署在一个按单个 VM 收费的基础设施上。因此,如之前所述,可能会产生超额配置 VM 的额外成本,以处理负载峰值。

有趣的是,容器和 VM 之间的区别可能会有些模糊。如之前所述,Boxfuse VM 可以很快地构建和启动。Clear Containers 项目旨在创建轻量级虚拟机。Unikernels 也正在蓬勃发展。Docker 公司于 2016 年初收购了 Unikernel 系统。

还有一个日益流行的 server-less(无服务器)部署概念,这是一种避免了“在容器中还是在虚拟机中部署服务”问题的方法。接下来我们来看看。

6.4、Serverless 部署

AWS Lambda 就是一个 serverless 部署技术示例。它支持 Java、Node.js 和 Python 服务。要部署微服务,请将其打包成 ZIP 文件并将上传到 AWS Lambda。您还要提供元数据,其中包括了被调用来处理请求(又称为事件)的函数的名称。AWS Lambda 自动运行足够的微服务服务实例来处理请求。您只需根据每个请求所用时间和内存消耗来付费。当然,问题往往出现在细节上,您很快注意到了 AWS Lambda 的局限性。但是,作为开发人员的您或组织中的任何人都无需担心服务器、虚拟机或容器的任何方面 ,这非常有吸引力,足以令人难以置信。

Lambda 函数是无状态服务。它通常通过调用 AWS 服务来处理请求。例如,当图片上传到 S3 存储桶时Lambda 函数将被调用,可插入一条记录到 DynamoDB 图片表中,并将消息发布到 Kinesis 流以触发图片处理。 Lambda 函数还可以调用第三方 Web 服务。

有四种方法调用 Lambda 函数:

直接使用 Web 服务请求

自动响应一个 AWS 服务(如 S3、DynamoDB、Kinesis 或 Simple Email Service)生成的事件

通过 AWS API 网关自动处理来自应用程序客户端的 HTTP 请求

按照一个类似 cron 的时间表,定期执行

正如您所见,AWS Lambda 是一个便捷的微服务部署方式。基于请求的定价意味着您只需为服务实际执行的工作支付。另外,由于您不需要对 IT 基础架构负任何责任,因此可以专注于开发应用程序。

然而,其也存在一些明显的局限性。Lambda 函数不适用于部署长时间运行的服务,例如消耗第三方消息代理消息的服务。请求必须在 300 秒内完成。服务必须是无状态的,因为理论上,AWS Lambda 可能为每个请求运行一个单独的实例。他们必须使用受支持的语言之一来编写。服务也必须快速启动,否则,他们可能会因超时而终止。

6.5、总结

部署微服务应用程序充满着挑战。您可能有数个甚至数百个使用了各种语言和框架编写的服务。每个应用程序都是一个迷你应用程序,有自己特定的部署、资源、扩展和监视需求。有几个微服务部署模式,包括每个虚拟机一个服务实例和每个容器一个服务实例模式。部署微服务的另一个有趣的选择是 AWS Lambda,一种 serverless 方式。在本书的下一章也是最后一章中,我们将介绍如何将单体应用程序迁移到微服务架构。

微服务实战:使用 NGINX 在不同主机上部署微服务

by Floyd Smith

NGINX 对于各种类型的部署具有许多优势 — 无论是单体应用程序、微服务应用程序还是混合应用程序(将在下一章介绍)。使用 NGINX,您可以智能抽取不同的部署环境出来并整合入 NGINX。如果您使用针对不同部署环境的工具,则有许多应用程序功能将以不同的方式工作,但如果使用 NGINX,那么在所有环境中都可以使用相同的方式进行工作。

这一特性也为 NGINX 和 NGINX Plus 带来了第二个优势:通过在多个部署环境中同时运行应用程序来扩展应用程序的能力。假设您拥有和管理着的本地服务器,但是您的应用程序使用情况正在增长,并且预计将超出这些服务器可以处理的峰值。如果你已经使用了 NGINX,您就有了一个强大的选择:扩展到云端 — 例如,扩展到 AWS 上,而不是购买、配置和保持额外的服务器来为了以防万一。也就是说,当您的本地服务器上的流量达到容量限制时,可根据需要在云中启动其他微服务实例来处理。

这只是因使用 NGINX 变得更加灵活的一个例子。维护单独的测试和部署环境、切换环境的基础设施、以及管理各种环境中的应用程序组合都变得更加现实和可实现。

NGINX 微服务参考架构被明确设计为支持这种灵活部署,其假设在开发和部署期间使用容器技术。如果您还没尝试,可以考虑转移到容器、NGINX 或 NGINX Plus,以轻松地转移到微型服务,以及使您的应用程序、开发和部署灵活性以及人员更具前瞻性。

7、重构单体为微服务

本书主要介绍如何使用微服务构建应用程序,这是本书的第七章,也是最后一章。第一章介绍了微服务架构模式,讨论了使用微服务的优点与缺点。随后的章节讨论了微服务架构的方方面面:使用 API ??网关、进程间通信、服务发现、事件驱动数据管理和部署微服务。在本章中,我们将介绍单体应用迁移到微服务的策略。

我希望这本电子书能够让您对微服务架构、其优点和缺点以及何时使用它有很好的了解。微服务架构也许很适合您的组织。

您正工作于大型复杂的单体应用程序上,这是相当不错的机会。然而,您开发和部署应用程序的日常经历是缓慢而痛苦的。微服务似乎是一个遥不可及的天堂。幸运的是,有一些战略可以用来逃离单体地狱。在本文中,我将描述如何将单体应用程序逐渐重构为一组微服务。

7.1、微服务重构概述

单体应用程序转换为微服务的过程是应用程序现代化的一种形式。这是几十年来开发人员一直在做的事情。因此,在将应用程序重构为微服务时,有一些想法是可以重用的。

一个不要使用的策略是“大爆炸”重写。就是您将所有的开发工作都集中在从头开始构建新的基于微服务的应用程序。虽然这听起来很吸引人,但非常危险,有可能会失败。据 AsMartin Fowler 讲到:“大爆炸重写的唯一保证就是大爆炸!”("the only thing a Big Bang rewrite guarantees is a Big Bang!")。

您应该逐步重构单体应用程序,而不是通过大爆炸重写。您可以逐渐添加新功能,并以微服务的形式创建现有功能的扩展 — 以互补的形式修改单体应用,并且一同运行微服务和修改后的单体。随着时间推移,单体应用程序实现的功能量会缩小,直到它完全消失或变成另一个微服务。这种策略类似于在 70公里/小时的高速公路上驾驶一辆汽车,很具挑战性,但比尝试大爆炸改写的风险要小得多。

Martin Fowler 将这种应用现代化策略称为杀手应用(Strangler Application)。这个名字来自发现于热带雨林中的葡萄树(也称为绞杀榕)。一棵葡萄树生长在一棵树上,以获取森林冠层之上的阳光。有时,树死了,留下一个树形的腾。应用现代化也遵循相同的模式。我们将构建一个新的应用程序,包括了围绕遗留应用的微服务(它将会慢慢缩小或者最终消亡)。

让我们来看看能做到这点的不同策略。

7.2、策略一:停止挖掘

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

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

除了新服务和传统的单体,还有另外两个组件。第一个是请求路由,它处理传入的(HTTP)请求,类似于第二章中描述的 API 网关。路由向新服务发送与新功能相对应的请求。它将遗留的请求路由到单体。

另一个组件是粘合代码,它将服务与单体集成。一个服务很少孤立存在,通常需要访问单体的数据。位于单体、服务或两者中的粘合代码负责数据集成。该服务使用粘合代码来读取和写入单体数据。

服务可以使用三种策略来访问单体数据:

调用由单体提供的远程 API

直接访问单体数据库

维护自己的数据副本,与单体数据库同步

粘合代码有时被称为防护层(anti-corruption layer)。这是因为粘合代码阻止了服务被遗留的单体领域模型的概念所污染,这些服务具有自己的原始领域模型。粘合代码在两种不同的模型之间转换。防护层一词首先出现于埃里克·埃文斯(Eric Evans)所著的必读图书《领域驱动设计》(Domain Driven Design)中,并在白皮书中进行了改进。开发一个防护层并不是一件简单的事情。但是,如果您想要从单体地狱中走出来,这是必不可少的步骤。

使用轻量级服务来实现新功能有几个好处。它防止单体变得更加难以管理。该服务可以独立于单体开发、部署和扩展。可让您创建的每个新服务体验到微服务架构的优势。

然而,这种方法没有解决单体问题。要解决这些问题,您需要分解单体。让我们来看看这样做的策略。

7.3、策略二:前后端分离

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

表现层(Presentation Layer,PL)

处理 HTTP 请求并实现(REST)API 或基于 HTML 的 Web UI 组件。在具有复杂用户界面的应用中,表现层通常存在大量代码。

业务逻辑层(Business Logic Layer,BLL)

作为应用程序核心,实现业务规则的组件。

数据访问层(Data Access Layer,DAL)

访问基础架构组件的组件,如数据库和消息代理。

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

重构之前和之后的架构如图 7-2 所示。

以这种方式拆分单体有两个主要优点。它使您能够独立于彼此开发、部署和扩展这两个应用。特别是它允许表现层开发人员在用户界面上快速迭代,并且可以轻松执行 A/B 测试。这种方法的另一个优点是它暴露了可以被您开发的微服务调用的远程 API。

然而,这一策略只是一个局部解决方案。两个应用程序中的一个或两个很可能是一个无法管理的单体。您需要使用第三种策略来消除剩余的整体或单体。

7.4、策略三:提取服务

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

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

一个庞大而复杂的单体应用由几十个或几百个模块组成,所有模块都是提取的候选项。弄清楚要先转换哪些模块往往存在一定的挑战。一个好的方法是从容易提取的几个模块开始。您将得到微服务的相关经验,特别是在提取过程方面。之后,您应该提取那些能给您最大利益的模块。

将模块转换为服务通常是耗时的。您想按照您将获得的利益对模块进行排列。提取频繁更改的模块通常是有益的。一旦将模块转换为服务,您就可以独立于单体开发和部署,这将加快开发工作。

提取这些与单体的其他模块有显著不同的模块也是有益的。例如,将有一个有内存数据库的模块转换为服务是很有用的,这样可以部署在具有大量内存的主机上,无论是裸机服务器、虚拟机还是云实例。同样,提取实现了计算昂贵算法的模块也是值得的,因为该服务可以部署在具有大量 CPU 的主机上。通过将具有特定资源需求的模块转换为服务,您可以使应用程序更加容易、廉价地扩展。

当找到要提取的模块时,寻找现有的粗粒度边界(又称为接缝)是有用的。它们使模块转成服务变得更容易和更连廉价。有关这种边界的一个例子是一个仅通过异步消息与应用程序的其他部分进行通信的模块。将该模块转变为微服务相对比较廉价和简单。

7.4.2、如何提取模块

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

一旦实现了粗粒度的接口,您就可以将模块变成独立的服务。要做到这点,您必须编写代码以使单体和服务通过使用进程间通信(IPC)机制的 API 进行通信。图 7-3 显示了重构前、重构中和重构后的架构。

在此例中,模块 Z 是要提取的候选模块。其组件由模块 X 使用,并且它使用了模块 Y。第一个重构步骤是定义一对粗粒度的 API。第一个接口是一个使用模块 X 来调用模块 Z 的入站接口。第二个接口是一个使用模块 Z 调用模块 Y 的出站接口。

第二个重构步骤是将模块转换为一个独立服务。入站和出站接口使用 IPC 机制的代码来实现。您将很可能需要通过将 Module Z 与 Microservice Chassis 框架相结合来构建服务,该框架负责处理诸如服务发现之类的横切点。

一旦您提取了一个模块,您就可以独立于单体和任何其他服务开发、部署和扩展其他服务。您甚至可以从头开始重写服务。在这种情况下,整合服务与单体的 API 代码成为在两个领域模型之间转换的防护层。每次提取服务时,您都会朝微服务方向迈近一步。随着时间的推移,单体将缩小,您将拥有越来越多的微服务。

7.5、总结

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

微服务实战:用 NGINX 征服单体

by Floyd Smith

如本章所述,将单体转换为微服务可能是一个缓慢而具有挑战性的过程,但这同样具有许多好处。使用 NGINX,您可以在实际开始转换过程之前获得微服务器的一些优势。

您可以通过将 NGINX 放在您现有的单体应用之前,以节省迁移微服务所花费的大量时间。以下简要说明与微服务有关的好处:

更好地支持微服务

如第五章尾栏所述,NGINX 和 NGINX Plus 具有利于开发基于微服务的应用的功能。当您开始重新设计单体应用时,由于 NGINX 的功能,您的微服务将执行得更好、更易于管理。

跨环境的功能抽象

从您管理的服务器甚至是各种公共云、私有云和混合云上将功能迁移到 NGINX 作为反向代理服务器可以减少部署在新环境中的设施数量变化。这补充扩展了微服务所固有的灵活性。

NGINX 微服务参考架构可用性

当您迁移到 NGINX 时,您可以借鉴 NGINX 微服务参考架构(MRA,Microservices Reference Architecture),以便在迁移到微服务之后定义应用程序的最终结构,并根据需要使用的 MRA 部分应用于您创建的每个新的微服务。

总而言之,实现使用 NGINX 作为您转型的第一步,压倒您的单体应用程序,使其更容易获得微服务的所有优势,并为您提供用于进行转换的模型。您可以了解有关 MRA 的更多信息,并获得 NGINX Plus 的免费试用版。

   
2362 次浏览       17
相关文章

企业架构、TOGAF与ArchiMate概览
架构师之路-如何做好业务建模?
大型网站电商网站架构案例和技术架构的示例
完整的Archimate视点指南(包括示例)
相关文档

数据中台技术架构方法论与实践
适用ArchiMate、EA 和 iSpace进行企业架构建模
Zachman企业架构框架简介
企业架构让SOA落地
相关课程

云平台与微服务架构设计
中台战略、中台建设与数字商业
亿级用户高并发、高可用系统架构
高可用分布式架构设计与实践