微服务架构设计

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

这种架构没有一种风格鲜明的定义,但还是有一些共同特征,如:

围绕业务处理能力、自动部署、自治的处理单元、多种开发语言和数据分区处理等。

微服务在拥挤的软件架构道路上,平添的又一新名词,不管能否成为未来的主流,但在过去几年中,已有很多项目采用了这种架构风格。

事实上,已经成为构建企业应用的默认风格。

遗憾的是,可以概述微服务风格,及如何实现微服务的文献实在匮乏。

简而言之,微服务架构风格是一种将单个应用程序,开发为一套小型服务的方法。

每个小型服务都在自己的进程中运行,以轻量级的方式(通常是HTTP API)进行通信。

这些服务围绕业务功能进行构建,使用完全自动化的部署机制独立部署。

最小化集中管理,支持不同编程语言,不同数据存储技术。

为明确微服务风格,将它与传统单体应用进行比较。

企业应用程序通常由三个主要部分构成:

1、客户端用户界面(浏览器中运行的HTML页面和javascript构成)。

2、数据库(通常是关系数据库)

3、服务器端应用程序。

服务器端应用程序处理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有许多相互矛盾的形式)。

然而,尽管有这些积极的经验,并不能确信微服务是软件架构未来发展方向。

对于这种事物很难在短期内做出准确判断。

通常,架构决策的真正后果只有在采用并实施几年后才会出现。

以前经常看到这样的项目:

一个优秀的团队,怀着对模块化的强烈渴望,构建了一个在如今看来已经衰败的单体应用架构。

许多人认为这种衰退在微服务中不太可能发生,因为,服务边界是明确的。

然而,现在就开始对微服务体系结构是否成熟进行评估,还为时尚早。