微服务的拆分策略

Posted 木兮同学

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微服务的拆分策略相关的知识,希望对你有一定的参考价值。

文章目录

什么是架构的风格?

架构风格根据结构组织模式定义了一系列系统,更具体地说,架构风格确定可以在该风格的实例中使用的组件和连接器的词汇表,以及关于如何组合它们的一组约束。特定的架构风格提供了有限的元素(组件)和关系(连接器),可以从中定义应用程序架构的视图,应用程序通常使用多种架构风格的组合。例如,单体架构是如何将实现视图构造为单个(可执行与可部署)组件的架构样式。微服务架构将应用程序构造为一组松散耦合的服务。

分层式架构风格

架构的典型例子是分层架构,分层架构将软件元素按“层”的方式组织,每层都有明确定义的职责,分层架构还限制了层之间的依赖关系,每一层只能依赖于紧邻其下方的层(如果严格分层)或其下面的任何层。

流行的三层架构是应用于逻辑视图的分层架构。它将应用程序的类组织到以下层中∶

  • 表现层:包含实现用户界面或外部API的代码。
  • 业务逻辑层:包含业务逻辑。
  • 数据持久化层:实现与数据库交互的逻辑。

分层架构是架构风格的一个很好的例子,但它确实有一些明显的弊端∶

  • 单个表现层:它无法展现应用程序可能不仅仅由单个系统调用的事实。
  • 单一数据持久化层:它无法展现应用程序可能与多个数据库进行交互的事实。
  • 将业务逻辑层定义为依赖于数据持久化层:理论上,这样的依赖性会妨碍你在没有数据库的情况下测试业务逻辑。

六边形架构风格

六边形架构是分层架构风格的替代品。如图下图所示,六边形架构风格选择以业务逻辑为中心的方式组织逻辑视图。应用程序具有一个或多个入站适配器,而不是表示层,它通过调用业务逻辑来处理来自外部的请求。同样,应用程序具有一个或多个出站适配器,而不是数据持久化层,这些出站适配器由业务逻辑调用并调用外部应用程序。此架构的一个关键特性和优点是业务逻辑不依赖于适配器。相反,各种适配器都依赖业务逻辑。

  • 业务逻辑具有一个或多个端口(port),端口定义了一组操作,关于业务逻辑如何与外部交互。有两种端口:入站和出站端口。入站端口是业务逻辑公开的 API,它使外部应用程序可以调用它。入站端口的一个实例是服务接口,它定义服务的公共方法。出站端口是业务逻辑调用外部系统的方式。出站端口的一个实例是存储库接口,它定义数据访问操作的集合。
  • 业务逻辑的周围是适配器。与端口一样,有入站和出站两种类型的适配器。 入站适配器通过调用入站端口来处理来自外部世界的请求。入站适配器的一个实例是 Spring MVC Controller,它实现一组 REST 接口(endpoint)或一组 Web 页面。另一个实例是订阅消息的消息代理客户端。多个入站适配器可以调用相同的入站端口。出站适配器实现出站端口。并通过调用外部应用程序或服务处理来自业务逻辑的请求。出站适配器的一个实例是实现访问数据库的操作的数据访问对象(DAO)类。另一个实例是调用远程服务的代理类。出站适配器也可以发布事件。
  • 六边形架构风格的一个重要好处是它将业务逻辑与适配器中包含的表示层和数据访问层的逻辑分离开来。业务逻辑不依赖于表示层逻辑或数据访问层逻辑。
  • 由于这种分离,单独测试业务逻辑要容易得多。另一个好处是它更准确地反映了现代应用程序的架构。可以通过多个适配器调用业务逻辑,每个活配器实现特定的 API 或用户界面。业务逻辑还可以调用多个适配器,每个适配器调用不同的外部系统。六边形架构是描述微服务架构中每个服务的架构的好方法。

分层架构和六边形架构都是架构风格的实例。每个都定义了架构的构建块(元素),并对它们之间的关系施加了约束。六边形架构和分层架构(三层架构)构成了软件的逻辑视图。现在让我们将微服务架构定义为构成软件的实现视图的架构风格。

微服务架构是一种架构风格

单体架构和微服务架构都是架构风格。

  • 单体架构是一种架构风格,它的实现视图是单个组件∶单个可执行文件或 WAR 文件。这个定义并没有说明其他的视图。例如,单体应用程序也可以具有六边形架构风格的逻辑视图。
  • 微服务架构也是一种架构风格。它的实现视图由多个组件构成∶一组可执行文件或 WAR 文件。它的组件是服务,连接器是使这些服务能够协作的通信协议。每个服务都有自己的逻辑视图架构,通常也是六边形架构。如下图,此架构中的服务对应于业务功能,例如订单管理和餐馆管理等。
  • 微服务架构强加的一个关键约束是服务松耦合。因此,服务之间的协作方式存在一定限制。

为应用程序定义微服务架构

根据业务能力进行服务拆分

创建微服务架构的策略之一就是采用业务能力进行服务拆分。业务能力是一个来自于业务架构建模的术语。业务能力是指一些能够为公司(或组织)产生价值的商业活动。特定业务的业务能力取决于这个业务的类型。例如,在线商店的业务能力包括:订单管理、库存管理和发货等等。

根据子域进行服务拆分

领域驱动设计(Domain Driven Design,DDD)是构建复杂软件的方法论,这些软件通常都以面向对象和领域模型为核心。领域模型以解决具体问题的方式包含了一个领域内的知识。它定义了当前领域相关团队的词汇表,DDD 也称之为通用语言。领域模型会被紧密地映射到应用的设计和实现环节。在微服务架构的设计层面,DDD 有两个特别重要的概念,子域和限界上下文。

  • 领域驱动为每一个子域定义单独的领域模型。子域是领域的一部分,领域是 DDD 中用来描述应用程序问题域的一个术语。识别子域的方式跟识别业务能力一样:分析业务并识别业务的不同专业领域,分析产出的子域定义结果也会跟业务能力非常接近。例如外卖软件的子域包括:订单获取、订单管理、餐馆管理、送餐和会计等。
  • DDD 把领域模型的边界称为限界上下文(bounded context)。限界上下文包括了实现这个模型的代码集合。当使用微服务架构时,每一个限界上下文对应一个或者一组服务。换一种说法,我们可以通过 DDD 的方式定义子域,并把子域对应为每一个服务,这样就完成了微服务架构的设计工作。

拆分的指导原则

单一职责原则(SRP),软件架构和设计的主要目标之一是确定每个软件元素的职责。

单一职责原则如下:改变一个类应该只有一个理由。

  • 类所承载的每一个职责都是对它进行修改的潜在原因。如果一个类承载了多个职责,并且互相之间的修改是独立的,那么这个类就会变得非常不稳定。遵照 SRP 原则,你所定义的每一个类都应该只有一个职责,因此也就只有一个理由对它进行修改。
  • 我们在设计微服务架构时应该遵 SRP 原则,设计小的、内聚的、仅仅含有单一职责的服务。这会缩小服务的大小并提升它的稳定性。

另外一个有用的原则是闭包原则(CPP):在包中包含的所有类应该是对同类的变化的一个集合,也就是说,如果对包做出修改,需要调整的类应该都在这个包之内。

  • 这就意味着,如果由于某些原因,两个类的修改必须耦合先后发生,那么就应该把它们放在同一个包内。这样做的目标是当业务规则发生变化时,开发者只需要对一个交付包做出修改,而不是大规模地修改(和重新编译)整个应用。采用闭包原则,极大地改善了应用程序的可维护性。
  • 在微服务架构下采用 CCP 原则能够把根据同样原因进行变化的服务放在一个组件内。这样做可以控制服务的数量,当需求发生变化时,变更和部署也更加容易。理想情况下,一个变更只会影响一个团队和一个服务。CCP 是解决分布式单体这种可怕的反模式的法宝。

拆分单体应用为服务的难点

从表面上看,通过定义与业务能力或子域相对应的服务来创建微服务架构的策略看起来很简单。但是,你可能会遇到几个障碍∶

  • 网络延迟。
  • 同步进程间通信导致可用性降低。
  • 在服务之间维持数据一致性。
  • 获取一致的数据视图。
  • 上帝类阻碍了拆分。

让我们来看看每个问题,先从网络延迟开始。

网络延迟

网络延迟是分布式系统中一直存在的问题。你可能会发现,对服务的特定分解会导致两个服务之间的大量往返调用。有时,你可以通过实施批处理 API 在一次往返中获取多个对象,从而将延迟减少到可接受的数量。但在其他情况下,解决方案是把多个相关的服务组合在一起,用编程语言的函数调用替换昂贵的进程间通信。

同步进程间通信导致可用性降低

另一个需要考虑的问题是如何处理进程间通信而不降低系统的可用性。例如,实现 createorder() 操作最常见的方式是让 Order Service 使用 REST 同步调用其他服务。这样做的弊端是 REST 这样的协议会降低 Order Service 的可用性。如果任何一个被调用的服务处在不可用的状态,那么订单就无法创建了。有时候这可能是一个不得已的折中,但是在学习异步消息之后,你就会发现其实有更好的办法来消除这类同步调用产生的紧耦合并提升可用性。

在服务之间维持数据一致性

  • 另一个挑战是如何在某些系统操作需要更新多个服务中的数据时,仍|日维护服务之间的数据一致性。例如,当餐馆接受订单时,必须在Kitchen Service 和 Delivery Service 中同时进行更新。Kitchen Service会更改 Ticket 的状态。Delivery Service 安排订单的交付。这些更新都必须以原子化的方式完成。
  • 传统的解决方案是使用基于两阶段提交(two phase commit)的分布式事务管理机制。但对于现今的应用程序而言,这不是一个好的选择,你必须使用一种非常不同的方法来处理事务管理,这就是 Saga。Saga 是一系列使用消息协作的本地事务。Saga 比传统的 ACID 事务更复杂,但它们在许多情况下都能工作得很好。Saga 的一个限制是它们最终是一致的。如果你需要以原子方式更新某些数据,那么它必须位于单个服务中,这可能是分解的障碍。

获取一致的数据视图

分解的另一个障碍是无法跨多个数据库获得真正一致的数据视图。在单体应用程序中,ACID 事务的属性保证查询将返回数据库的一致视图。相反,在微服务架构中,即使每个服务的数据库是一致的,你也无法获得全局一致的数据视图。如果你需要一些数据的一致视图,那么它必须驻留在单个服务中,这也是服务分解所面临的问题。

上帝类阳阻碍了拆分

分解的另一个障碍是存在所谓的上帝类。上帝类是在整个应用程序中使用的全局类。上帝类通常为应用程序的许多不同方面实现业务逻辑。它有大量字段映射到具有许多列的数据库表。大多数应用程序至少有一个这样的上帝类,每个类代表一个对领域至关重要的概念:银行账户、电子商务订单、保险政策等等。因为上帝类将应用程序的许多不同方面的状态和行为捆绑在一起,所以将使用它的任何业务逻辑拆分为服务往往都是一个不可逾越的障碍。


《微服务架构设计模式》第二章 微服务的拆分策略

以上是关于微服务的拆分策略的主要内容,如果未能解决你的问题,请参考以下文章

微服务的拆分策略

微服务的拆分策略

微服务的拆分策略

微服务拆分策略

学习笔记:《微服务架构设计模式》之服务的拆分策略

《微服务架构设计模式》读书笔记 | 第2章 服务的拆分策略 #yyds干货盘点#