技术记录栈

记录点滴:java、datebase、linux、spring、javascript、nginx

2019/01/27

Microservices微服务架构设计

微服务这个新的架构术语,在最近几年变得越来越主流。微服务设计的定义:将应用程序设计成一套可独立部署的服务组件。

虽然这种架构并没有一种风格鲜明的定义,但还是有一些共同特征,如:围绕业务处理能力、自动部署、自治的处理单元、多种开发语言和数据分区处理等。

微服务这个在拥挤的软件架构道路上,平添的又一新名词,不管能否成为未来的主流,但在过去几年中,已经有很多项目采用了这种架构风格,事实上,已经成为构建企业应用程序的默认风格。遗憾的是,可以概述微服务风格,以及如何实现微服务的文献实在是匮乏。

简而言之,微服务架构风格是一种将单个应用程序,开发为一套小型服务的方法。每个小型服务都在自己的进程中运行,并以轻量级的方式(通常是HTTP API)进行通信。这些服务围绕业务功能进行构建,通过完全自动化的部署机制独立部署。最小化集中管理,可以采用不同的编程语言,使用不同的数据存储技术。

为了明确微服务风格,将它与传统一体化应用程序构建风格进行比较。企业应用程序通常由三个主要部分构成:客户端用户界面(浏览器中运行的HTML页面和javascript组成)、数据库(通常是关系数据库)和服务器端应用程序。服务器端应用程序将处理HTTP请求,执行单元逻辑,从数据库检索和更新数据,选择和填充返回给浏览器的HTML视图。服务器端应用程序是一个整体,对系统做出的任何更改,都会导致重新构建和部署。

这种传统一体化应用程序的构建方式。处理请求的所有逻辑都在一个进程中运行,通常需要借助于语言的基本特性,才能将程序逻辑进行分割,如:按类、方法和命名空间进行划分。通常采用负载均衡的方式进行水平扩展。

越来越多的人对于一体化的应用程序感到沮丧,特别是随着应用程序采用云部署策略。这种构建方式就的缺陷就更加明显,对应用程序的一小部分进行了更改,需要重建和部署整个应用。随着项目迭代周期的推移,良好的模块化结构是一种奢望,也很难做到对一个模块的更改,只会影响该模块。对服务的扩展需求,也很难做到有针性的合理利用资源,通常只能粗暴的对整个应用程序投入资源(计算能力、内存、网络带宽)。

传统应用和微服务的对比

图中为传统应用和微服务的对比

这些前车之鉴引出了微服务架构风格:将应用程序构建为服务套件。服务除了可独立部署和扩展外,每个服务还提供了牢固的模块边界,可以使用不同的编程语言构建服务。更可以由不同的团队进行管理。

微服务风格更不是天马行空的天才创新,其根源可以追溯到Unix的设计原则。只不过之前这种微服务架构并没有受到重视。

微服务架构的特征

虽然不能说微服务架构风格有一个正式的定义,但可以尝试给这种架构的共同特征打一些合适的标签。与概述其他共同特征一样,并非所有微服务架构都具有某些特征,但大多数微服务架构通常都会具有一些共同特征。

对服务进行组件化

一直以来都希望通过将组件进行集成来构建系统,就像在真实世界中看到的事物一样。在过去的几十年中,可以看到了大多数语言平台,都出现了大量的公共类库。

在谈论组件时,通常遇到的困难是:如何定义组件的构成。常规的理解是:组件是一个可独立更换和升级的软件单元。

微服务架构会用到很多第三方类依赖包/类库,但是将自己的系统进行组件化的方式是分解为服务。我们将依赖包/类库定义为:在程序中进行链接,在内存中进行调用的组件,而服务是进程外组件,它们通过web服务请求,或远程调用等机制进行通信。

将服务作为组件(而不是库)的一个主要原因是:服务是可独立部署的。如果在单个进程中一个应用程序由多个库组成,对任何单个组件的更改都会导致重新部署整个应用。但是,如果该应用程序被分解为多个服务,良好的微服务架构通过约定的服务边界,则单个服务的更改只需要重新部署该服务。

将服务作为组件的另一个好处是更明确的组件接口。大多数语言没有很好的机制,来显式定义已发布的接口,通常,只是通过文档和规约。组件化可以防止客户端破坏组件的封装,导致组件之间过于紧密的耦合。通过使用显式远程调用机制,服务可以更容易地避免这种情况。

使用这样的服务也有缺点。远程调用比进程内调用,需要付出更高的时间成本,因此,远程API需要更粗糙(一次调用做更多的事),这通常会导致API难以使用。如果需要更改组件之间的职责分配,那么在跨越流程边界时,这种改动将更加困难。

围绕业务处理能力进行组织

在将大型应用程序拆分为多个部分时,通常管理侧重于以技术拆分,从而导致团队被分为UI团队、服务器端逻辑团队和数据库团队。当团队按照这些方式分开时,即使是简单的更改,也可能导致跨团队的需求,需要进行项目的时间和预算批准。聪明的团队会围绕这一点进行优化,并以两害相权取其轻的原则。换句话说,逻辑无处不在,就看如何理解。这也是对康威定律(Conway's Law)的实践。

一个产品可以反映出制造该产品的组织结构。

Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization's communication structure.

-- Melvyn Conway, 1967

康威定律的实际应用

上图为康威定律的实际应用

对于一个接口的定义,这一论点也适用:项目中复杂的人,会定义出复杂的产品接口。

微服务划分方法有所不同,以围绕业务处理能力来组织服务 。这样的服务构建是行业中采用较为广泛的软件实现。包括用户界面、持久化和外部协作。因此,团队是跨职能的,包含了开发所需的全部技能:用户体验、数据库和项目管理。

团队边界加强后的服务边界

上图为团队边界加强后的服务边界

跨职能团队负责构建和运营每个产品,每个产品又被分解成许多通过消息总线进行通信的单个服务。

大型一体化应用程序也可以围绕业务功能进行模块化,尽管这不常见。但仍然可以通过监督的方式,将一个一体化应用程序的大型团队按照业务线进行划分,以便在业务线上划分自己的责任。在种方式的主要问题是,它们往往会围绕太多的背景进行组织。如果整体跨越许多这些模块化边界,团队中的个体成员可能难以短期搞清自己的边界。此外,我们看到模块化生产线需要大量的纪律来执行。而服务组件所遵循明确的分离,使得团队更容易保持清晰的边界。

产品不是项目

经常看到大多数应用程序开发工作都以项目的形式存在:目标是交付软件,然后,认为这些软件已经完成。在软件完成后,软件被移交给维护组织,构建软件的项目团队被解散。

微服务支持者不太提倡这种模式,而是,倾向于认为团队应该在其整个生命周期内拥有产品。对此,来自亚马逊的一个概念或许会有启发意义:"you build, you run it" ,开发团队对生产中的软件负全部责任。这使开发人员能够接触到软件在生产中的行为,并增加与用户的联系,因为,他们必须承担一些责任。

产品心态,与业务能力联系在一起。与其将软件视为一组要完成的功能,还不如将其视为一种持续的关系,其中的问题包括软件如何帮助用户增强业务处理能力。

强化终端及弱化通道

在构建不同进程之间的通信结构时,经常看到许多产品和方法,强调在通信机制本身中投入大量的精力。一个很好的佐证是企业服务总线(ESB), ESB产品通常包括用于消息路由、编排、转换和应用业务规则。

微服务社区支持另一种方法:强化终端及弱化管道。以微服务的风格构建的应用程序,最终的目标是尽可能地解耦和内聚——它们拥有自己的单元逻辑,在Unix中更像是过滤器——接收请求,适当地应用逻辑并生成响应。这些协议采用简单的RESTish协议进行组织,而不是复杂的协议,如:WS-Choreography、BPEL或由中心工具进行编排。

最常用的两种协议是HTTP协议和轻量级消息传递协议。最能诠释这一点一句话是:

Be of the web, not behind the web
//善用之,而非受制

-- Ian Robinson

微服务团队使用基于万维网(在很大程度上是Unix)的原则和协议,开发人员只需花费很少的精力就可以缓存经常使用的资源。

而第二种方法是通过轻量级消息总线进行消息传递。选择的基础设施通常是弱化的(如仅作为消息路由器) - 像RabbitMQ或ZeroMQ这样的简单实现,不仅仅提供可靠的异步结构,还在服务中强化了消息的生成和消费。

在一体化的应用中,组件在进程中执行,它们之间的通信通过方法/函数调用。将一体化应用改造成微服务,最大的问题在于改变通信模式。从内存中的方法调用转换到RPC繁琐的通信,这些通信效果并不好,所以,需要用粗粒度的方法替换细粒度的通信。

权力下放的治理

集中治理的结果之一是在单一技术平台上实现标准化。经验表明,这种方法是有局限性的 - 不是每个问题都是钉子,也不是每个解决方案都是锤子,应用正确的工具来完成工作,一体化应用程序可以在一定程度上利用不同的语言,但这并不常见。

构建微服务的团队也更喜欢采用不同的标准。他们并非在纸上写下一组标准,而是愿意生产一些有用的工具,让其他开发人员可以用它来解决同类问题。这些工具通常从工作中收集并在组内共享,有时,不仅仅是在内部使用,还会开源模型。在git和github风靡当下,开源实践在内部变得越来越普遍。

Netflix遵循了这一理念。共享有用的,最重要的是,这些都是经过实战考验的代码,libraries旨在鼓励其他开发人员以类似的方式解决同类的问题,如果需要,可以选择不同的方法。

也许分散治理的最高点是由亚马逊推广的build it/run it精神。团队负责他们所构建软件的所有方面,包括全天候运行。放弃这种高水准要求绝对不是常态,越来越多的公司将责任推向开发团队。Netflix是另一个采用这种精神的代表。当凌晨3点被唤醒,无疑是在编写代码时,专注于质量的强大动力。这些想法与传统的集中治理模式相差甚远。

分散数据管理

数据管理的分散化,以多种不同的方式出现。在最抽象的层面上,它意味着对真实世界的概念模型,在不同系统之间会有所不同。在整合大型企业时,这是一个常见问题,客户的销售视图会与所支持的视图不同。在销售视图中被称为客户的某些内容,可能根本不会出现在所支持的视图中。

此问题在应用程序之间很常见,也可能在应用程序中发生,特别是在将应用程序划分为单独的组件时。一种有用的思考方式是,有界上下文的领域驱动设计概念 。DDD将复杂域划分为多个有界上下文,并映射出它们之间的关系。此过程对单一应用和微服务体系结构都很有用,但服务和上下文边界之间存在自然关联,这有助于强化分离。

除了关于概念模型的分散决策之外,微服务还分散了数据存储决策。虽然单一应用程序更喜欢使用单个逻辑数据库来存储持久性数据,但企业通常更喜欢在一系列应用程序中使用单个数据库。微服务更喜欢让每个服务管理自己的数据库,可以是同一数据库的不同实例,也可以是完全不同的数据库系统 - 这种方法称为Polyglot Persistence。可以在一体化应用系统中使用多语言进行持久性,但它更常出现于微服务中。

分区数据管理

将数据责任分散到各个微服务会对管理更新有影响。处理更新的常用方法是在更新多个资源时,使用事务来保证一致性。这种方法常用于一整体化系统中。

使用这样的事务有助于一致性,但会产生明显的时间耦合,这在多个服务中是有问题的。众所周知,分布式事务很难实现,因此,微服务架构强调服务之间的无事务协调,并需要认识到一致性可能只是最终的一致性,而这个过程中产生的问题通过补偿机制来处理。

选择以这种方式管理不一致性,对于许多开发团队来说是一个新的挑战,但它通常与业务实践相匹配。为了对需求做出快速反应,企业通常会允许一定程度的不一致性,同时采取某种逆转流程来应对错误。只要修正错误的成本,小于使用更大一致性下丢失业务的成本。

基础设施高度自动化

在过去的几年中,基础设施自动化技术得到了极大的发展——云计算和AWS的发展降低了构建、部署和操作微服务的复杂性。

许多使用微服务构建的产品或系统,都是由具有大量持续交付经验的团队构建的,它的前身是持续集成。以这种方式构建软件的团队,广泛采用了基础设施自动化技术。

基础管道构建

由于这不是一篇关于持续交付的文章,在这里只关注几个关键特性。通过运行大量的自动化测试,确保软件运行正常。

由于持续交付、部署、自动化程度提高,需要一些有用的工具来帮助开发人员进行操作。除了Netflix有一组开源工具,还有其他一些工具,如:Dropwizard,都已经广泛使用了。

一个完整的应用程序将非常轻松地在这些环境中构建、测试和发布。事实证明,一旦投资了自动化生产流程,那么部署更多的应用程序就不再那么可怕了。

模块化部署

设计考虑失败因素

将服务作为组件使用的结果是,在设计应用程序时,使其能够容忍服务的失败。任何服务调用都可能由于服务端不可用而失败,客户端必须尽可能优雅地对此作出响应。与一体化设计相比,这是一个缺点,因为,它需要引入了额外的复杂性来处理它。其结果是,微服务团队不断地反思服务失败,如何影响用户体验。

微服务是未来吗?

写这篇文章的主要目的是解释微服务的主要思想和原则。清楚微服务架构风格是一个重要的想法 - 值得认真考虑。

在某种程度上微服务开创了一种架构风格,此外,有很多组织长期以来,一直在做我们称之为微服务的东西,但没有使用微服务这个名字。(通常被称为SOA——尽管SOA有许多相互矛盾的形式)。

然而,尽管有这些积极的经验,也并不能确信微服务,是软件架构未来的发展方向。对于这种事物很难在短期内,做出准确的判断。

通常,架构决策的真正后果只有在采用并实施几年后才会出现。以前经常看到这样的项目:一个优秀的团队,怀着对模块化的强烈渴望,构建了一个多年来已经衰败的一体化架构。许多人认为这种衰退在微服务中不太可能发生,因为服务边界是明确的。然而,现在就开始对微服务体系结构是否成熟进行评估,还为时尚早。