微服务架构和微服务的设计模式

微服务在企业中可以带来积极的影响。 因此,如何处理微服务体系架构(MSA)和一些微服务设计模式以及微服务体系架构的一般目标或原则是很有必要的。 以下是微服务架构实现中要考虑的四个目标。

  1. 降低成本 — MSA将降低设计,实施和维护IT服务的整体成本
  2. 提高发布速度 — MSA将提高项目从构建到部署的速度
  3. 提升弹性 — MSA将提升我们服务网络的弹性
  4. 有可见性 — MSA为您的服务和网络上提供更好的可见性。

MSA是建立在哪些原则基础之上是需要你去了解的

  1. 可扩展性
  2. 可用性
  3. 弹性扩展
  4. 灵活性
  5. 独立性,自主性
  6. 去中心化治理
  7. 故障隔离
  8. 自动配置
  9. 通过DevOps持续交付

在坚持这些原则基础上推广自己的解决方案或者系统会带来一些挑战和问题,这些问题在许多解决方案中都很常见,而且可以通过使用正确的设计模式来解决, 这些就是微服务的设计模式,这些模式可以分为五个大类,而每一类又包含了许多设计模式。具体如下图所示:

Design Patterns for Microservices

解耦模式(Decomposition Patterns)

按业务能力解耦

通过运用单一职责原则,微服务总是会把服务之间的耦合变为松耦合,微服务通过业务能力解耦,而且服务的定义是对应于业务能力。业务能力这个概念来自于业务架构模型,某种程度上来说,业务确实是可以产生价值,业务能力经常是对应于一个业务实体对象。例如:

  • 订单管理对应于订单
  • 客户管理对应于客户

按照子域解耦

按照业务能力解耦一个应用可能是一个好的开始,但是你可能会遇到所谓的“神类”(God Classes),就是哪些不容易解耦的类,而且这些类在多个服务之间很常见。领域驱动设计(DDD) 参考应用问题空间–业务–做为一个域(domain)。一个域由多个子域组成,而每一个子域对应于业务的不同的部分。

子域可以分类如下:

  • 核心(Core) — 区分业务的关键 和 应用中最有价值的部分
  •  支撑(Supporting ) — 与业务相关,但是不是关键部分,可以内部实现,也可以外部实现
  •  泛化(Generic ) — 不针对特定的业务,理想情况下使用现成的软件实施

订单管理的子域包括:

  • 产品目录服务
  • 库存管理服务
  • 订单管理服务
  • 交付管理服务

按事务解耦/两阶段提交(2PC)模式

可以通过事务分解服务,然后系统中将会有多个事务。 分布式事务处理的重要参与者之一是事务处理协调器[3]。分布式事务包括两个步骤:

  • 准备阶段 — 在此阶段中,事务的所有参与者都准备提交并通知协调器他们已准备好完成事务
  • 提交或者回滚阶段 — 在此阶段中,事务协调器向所有参与者发出提交或回滚命令

2PC的问题是和单个微服务执行时间来对比耗时长。即使微服务在相同的网段中,协调微服务之间的事务依旧会拖慢整个系统。因此这个解决方案一般不使用在高负载的场景中

扼杀模式

以上三种设计模式用于对未开发的应用(greenfield apps)的解耦, 但是我们80%的工作都是和庞大而僵化的应用(遗留代码库)打交道。扼杀模式(Strangler Pattern)就是为了解决这个问题而来的。在相同的URI空间中创建两个独立共存的应用,随着时间的推移,重构过的新应用将“扼杀”或者替代原来的应用,直到最终把庞大而僵化的应用关闭掉。扼杀应用(Strangler Application)的步骤分为转换,共存和消灭三步[4]:

  • 转换(Transform ) —  用现代方式创建一个新的平行的站点
  • 共存(Coexist ) —  将已有的站点重定向到新的站点,新站点逐步实现老站点的功能
  • 消灭(Eliminate ) —  移除已有的站点的旧的功能

隔板模式

将应用程序的元素隔离到池中,以便如果其中一个失败,其他应用程序将继续运行提供服务,这个设计模式称为隔板模式(Bulkhead), 因为他类似于船体中一个个被隔离的分区。根据使用者负载和可用性要求,这些分区服务实例被分割到不同的组里面。这种设计模式有助于隔离故障(isolate failures), 并允许即使在故障期间仍可为某些使用者维持服务功能.

边车模式(Sidecar Pattern)

将应用程序的组件部署到单独的容器中以来提供隔离和封装,这个模式允许应用可以由多种多样的组件和技术组合而成,这种模式称为边车(Sidecar ), 因为类似摩托车旁边所附的边车。再这个模式中,sidecar 附在父应用上,并提供应用的支持特征。sidecar 和父应用共享相同的生命周期,随着父应用创建而创建,销毁而销毁。边车模式有时称为“边踢模式(sidekick pattern)”。

微服务的集成模式 (Integration Patterns for Microservices)

API网关模式 (API Gateway Pattern)

当把一个应用分解为多个小的微服务时,有一些问题需要我们考虑并处理:

  • 不同的微服务不同的通道上有多次调用
  • 需要处理不同的协议类型
  • 不同的消费者可能需要不同格式的响应

聚合模式(Aggregator Pattern)

在将业务功能分解为几个较小的逻辑代码段时,有必要考虑如何对每个服务返回的数据进行协同操作,消费者不负责处理这个事情。

聚合模式有助于解决此问题,它讨论了我们如何聚合来自不同服务的数据,然后将最终响应发送给消费者。 这可以通过两种方法来完成[6]:

  1. 组合的微服务将调用所有必须的微服务,组合数据,转换数据,然后返回给调用者。
  2. API网关还可以分发请求到多个微服务上,再聚合数据,然后发送消费者。

代理模式(Proxy Pattern)

我们只是通过API网关来暴露微服务。 我们允许获取API的特征,例如安全和GW中API的分类。 这个例子中,API网关具有三个API模块:

  • 移动API – 为FTGO移动客户端实现API
  • 浏览器API-实现浏览器中运行的JavaScript应用程序的API
  • 公用API-为第三方开发人员实现API

网关路由模式 (Gateway Routing Pattern)

API网关负责请求路由。 API网关通过将请求路由到相应的服务来实现一些API操作。 当API网关接收到请求时,它会查询路由映射,该路由映射指定将请求路由到的服务。 路由映射例如可以将HTTP方法和路径映射到服务的HTTP的URL上。 此功能与Web服务器(如NGINX)提供的反向代理功能相同。

微服务链模式 (Chained Microservice Pattern)

单个服务或微服务将可能会有多个依赖关系, 例如:销售微服务依赖产品微服务和订单微服务。微服务链模式将根据你的请求提供合并的结果。microservice-1 接收请求,然后和microservice-2通信,并且可能和microservice-3通信。 所有的这些服务都是同步调用。

分支模式(Branch Pattern)

微服务可能需要从包括其他微服务在内的多个来源获取数据,分支微服务模式是聚合器和链设计模式的混合,并允许来自两个或多个微服务的同时请求/响应处理。调用的微服务可以是微服务链。根据您的业务需求,分支模式还可用于调用不同的微服务链或单个链。

客户端UI组合模式(Client-Side UI Composition Pattern)

当通过分解业务功能/子域来开发服务时,负责用户体验的服务必须从多个微服务中提取数据。在单机世界中,从UI到后端服务只有一次调用来查询数据并且刷新/提交UI页面。不过,现在不一样了。 在微服务中,UI必须设计为屏幕/页面上具有多个部分/区域的框架。每个部分都将调用单独的后端微服务以提取数据。诸如AngularJS和ReactJS 之类的框架可以轻松地做到这一点,这些屏幕称为单页应用程序(SPA)。每个团队都开发一个客户端UI组件,例如AngularJS指令,该组件实现针对该页面/屏幕区域的微服务调用。一个UI团队负责实现页面的框架,这个框架通过组合多个特定服务UI (service-specific UI) 组件来构建页面/屏幕。

数据库模式(Database Patterns)

为微服务定义数据库架构时,我们需要考虑以下几点:

  • 服务之间必须是松散耦合的, 它们可以独立开发,部署和扩展。
  • 业务事务在跨越多个微服务的时候保证不变
  • 一些业务事务跨越多个微服务来查询数据
  • 有时数据库必须可以复制,并且可以弹性共享
  • 不同的服务有不同的数据存储要求

每一个服务对应一个数据库(Database per Service)

为了解决上述问题,必须为每个微服务设计一个数据库. 该数据库只能是该服务私有的,并且只能通过微服务的API访问,不能被其他的微服务直接访问。例如,对关系型数据库,我们可以使用 每个服务有私有化的表(private-tables-per-service), 每个服务有自己的schema (schema-per-service), 或者每个服务有私有的数据库服务器 (database-server-per-service)

每一个服务共享数据库 (Shared Database per Service)

我们已经讨论了每个服务一个数据库是微服务的理想选择,但它是微服务的反模式(anti-pattern)。如果一个单一而又庞大的应用,并试图把它拆分为微服务,那么数据库的反范式化(denormalization )就不那么容易。将每个微服务共享数据库不是理想的情况,但是是可行的解决方案。大多数人认为这是微服务的反模式,但对于brownfield 应用,这是将应用程序分解成较小逻辑部分的一个很好的开始。但是对于greenfield 应用不太适用。

命令查询的责任分离 (Command Query Responsibility Segregation,CQRS)

一旦我们实现了每个服务对应一个数据库,就需要将从多个微服务查询返回的数据连接起来。显然这是不可能的。CQRS建议将应用分为两个部分 — 命令端 (command side)和查询端 (query side):

  • 命令端处理创建,更新和删除请求
  • 查询端通过使用物化视图来处理查询部分

通常 事件溯源模式(event sourcing pattern)和它一起用来为任何数据更改创建事件。通过订阅事件流,可以使物化视图保持不断的更新

事件溯源模式(event sourcing pattern)

大多数应用程序都使用数据,一个典型的途径就是应用保持当前的状态。例如,传统的创建,读取,更新和删除(CRUD)中,典型的数据处理是从存储中读取数据,它包含经常使用事务锁定数据的限制。

事件溯源模式定义了一系列事件驱动的数据的处理操作,每一个事件处理操作都会记录在仅追加存储中(append-only store)。应用程序代码发送一系列 命令式的描述了数据上发生的动作的事件到事件持久化存储的地方。每个事件代表一组数据更改(例如,AddedItemToOrder)

这些事件持久化存储在充当系统记录系统的事件存储中。事件存储系统中事件发布的典型应用场景是:在应用中保持实体的物化视图和事件的动作一样来改变他们,以及集成的外部系统。例如系统可以维护一个针对所有用户的物化视图,用来填充UI部分的数据。当应用程序添加新订单,添加或删除订单上的项目以及添加运输信息时,这些事件描述了这些数据变化可以被处理并且可以更新到物化视图上。下面是这个模式的纵览:

 

事件履历模式(Saga Pattern)

当每一个微服务都有自己的数据库,并且一个业务事务跨越多个微服务的时候,我们是如何确保各个服务之间的数据一致性?每个请求都有一个补偿请求,该请求将在请求失败时执行。 它可以通过两种方式实现:

  • Choreography  — 如果没有中央协调,则每个服务都会产生并侦听另一个服务的事件,并决定是否应采取措施。Choreography  是指定两个或两个以上参与者的方式。 每一个参与者都无法控制对方的流程,或者任意可见的流程,这些可见的流程可以协调他们的活动和流程以共享信息和数值。当需要跨控制/可见性域进行协调时,请使用choreography 。你可以在简单的情况下将编排视为网络协议,它规定了各参与者之间可接受的请求和响应模式。

  • Orchestration —  一个Orchestration (对象)负责Saga的决策和业务逻辑顺序。当你已经控制流程中的所有参与者时,当它们全部处于一个控制范围内时,你可以控制活动的流程。当然,通常情况下,当你制定一个组织内的业务流程时,你已经控制了它。

 

 观察者模式(Observability Patterns)

日志聚合

考虑这样一种情况:一个应用包含多个微服务实例,每个请求经常在横跨多个微服务实例,那么每一个微服务实例都产生一个表转化格式的日志文件。 因此我们需要一个中心化的日志服务来将每个服务实例的日志收集起来。用户可以搜索分析并分析日志,并且配置一些当日志中出现特定信息的报警规则。例如:PCF确实有一个日志聚合器(Log aggregator), 用来收集PCF平台上各个应用的各个组件(router, controller, Diego, 等等…)的日志。AWS Cloud Watch也这样做。

性能指标

因为微服务架构导致服务的数量增加时,密切注意事务变得十分关键,以便监控微服务模式并且在问题发生的时候发出警告。

一个指标服务用来收集每个单独操作的统计信息。它应该聚合一个应用服务的所有指标,以便提供报告和警报。 聚合指标应该包含两个模块:

  • 推送 — 服务推送指标给指标服务 例如:NewRelic, AppDynamics
  • 拉取 — 指标服务可以从每个服务中拉取指标 例如:Prometheus

分布式跟踪

在微服务架构中,请求通常跨越多个微服务。 每个服务通过跨多个服务执行一个或多个操作来处理一个请求。 在进行故障排除时,有一个跟踪ID是非常值得的,这样我们可以端对端的跟踪请求

解决方案是引入一个事务ID,可以使用以下方法:

  • 为每个外部请求分配唯一的外部请求ID
  • 将外部请求ID传递给所有服务
  • 在所有日志消息中包括外部请求ID

健康检查

实施微服务架构后,有一种可能是:服务可能会启动但无法处理事务。每个服务都需要具有一个端点用来检查应用的健康程度,例如health。这个API应该检查主机的状态, 与其他服务/基础结构的连接以及其他任意特定的逻辑。

交叉关注模式(Cross-Cutting Concern Patterns)

外部配置(External Configuration)

一个典型的服务通常还会调用其他服务和数据库,对于每一个环境,例如dev, QA, UAT, prod,这些环境的端点URL或某些配置属性可能不同,这些属性中的任何一项更改都可能需要重新构建或重新部署服务。

为了避免代码修改,我们可以使用配置,将所有的配置信息都外部化,包括端点URL和认证信息。应用程序应该在启动时或运行时加载这些配置。这些配置可以在应用启动的时候访问到,或者这些配置在不需要重启服务的情况下可以更新。

服务发现模式

当遇见如图所示的微服务架构时,在微服务调用方面我们需要关注一些问题。

使用容器技术,IP地址可以动态分配给每个微服务实例, 每次地址更改时,消费者服务的调用都会中断,需要手动更改才能恢复。

消费者必须记住每个服务URL,并使其紧密耦合。

因此需要创建服务注册,该服务注册将保存每个生产型服务的元数据和每个服务的说明规范。服务实例在启动时应注册到注册中心,而在实例关闭时应注销。服务发现有两种类型:

  • 客户端,例如:Netflix Eureka
  • 服务端:例如: AWS ALB

断路器模式

一个服务通常会调用其他服务来查询数据,有一种可能性是下游的服务会关闭,这将会带来两个问题:第一个是:上游服务继续请求关闭的网络服务,直到耗尽网络资源,并且降低系统性能。第二个是:用户体验将是糟糕的且不可预测的。

消费者应通过代理来调用远程服务,该代理的行为类似于电路中的断路器。当连续的请求失败的次数超过阈值时,断路器将跳闸一段时间,并且在跳闸的这段时间内,所有的调用远程服务的尝试都将立即失败。当超过了断路器跳闸时间之后,断路器将允许有限数量的测试请求通过。如果这些请求成功,则断路器将恢复正常操作。否则,如果有一个请求失败,则断路器再次跳闸。对于 一个应用试图尝试调用一个远程服务或者获取共享资源,并且该操作很容易的失败的情况来说, 这个模式非常适用

 

蓝绿发布模式

在微服务架构中,一个应用程序可以具有许多微服务。 如果我们在停止所有服务之后然后部署增强版本,则停机时间将是巨大的,并且可能影响业务。同样,回滚将是一场噩梦。蓝绿发布模式可以避免这种情况。

实施蓝绿发布模式可以减少或消除停机时间,它通过运行两个相同的生产环境:Blue和Green,来实现这一目标。这里我们假设绿色是已存在的工作实例,蓝色是该应用程序的新版本。在任何时候,只有一个环境处于活动状态,该活动环境为所有生产流量提供服务。 所有云平台均提供用于实施蓝绿色部署的选项。

 

参考文献:https://dzone.com/articles/microservice-architecture-and-design-patterns-for?fromrel=true

K8S中文社区微信公众号

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址