第08课:微服务——最热门的架构

第08课:微服务——最热门的架构

在大型互联网应用中,如何更为合理的划分系统和团队边界、如果更加有效的组织系统开发过程、如何通过技术手段识别和消除开发过程中的浪费成为广大软件开发和技术管理人员所需要思考的命题。针对这些命题,我们的思路首先是根据业务划分系统和领域的边界,对一个大而全的系统进行分而治之,通过一些功能边界明确、业务高度抽象的模块或组件之间的组装去形成更大的业务体系。其次,在系统和领域边界内部,同样需要对业务体系进行合理建模,通过建立服务体系对服务进行一定粒度下的拆分和集成,从而降低业务实现的复杂性,并提高服务交互的灵活性。再次,通过服务拆分和集成的手段也可以推动研发团队组织架构的优化,并促进系统持续交付工作的有效开展。围绕这些解决问题的思路,微服务架构(Microservices Architecture)为我们提供了一种具体的解决方案。

所谓微服务(Microservices),就是一些具有足够小的粒度、能够相互协作且自治的服务体系。每个微服务都比较简单,仅关注于完成一个功能并能很好地完成该功能,而这里的功能代表都是一种业务能力。构建微服务体系需要一套完整的方法论和工程实践,而微服务架构代表的是实现微服务体系的架构模式,即为我们提供了这些方法论和工程实践。

服务边界

微服务架构实现的前提是识别服务,识别服务的切入点在于识别服务与服务之间的边界(Boundary)。明确服务边界是服务拆分和集成的前提。在微服务架构中,识别服务边界的方法主要参考领域驱动设计思想,我们已经在第4篇和第5篇中介绍了面向领域的策略设计和技术设计,尤其是其中的子域、界限上下文、聚合等概念与服务边界的确定关系密切。

在通过使用领域和界限上下文进行服务边界划分的过程中,经常会碰到这样一个问题,即这个服务看起来放这个子域合适,放另一个子域也合适,如何抉择呢?这就需要梳理服务边界划分的原则。常见的边界划分原则如下:

  • 服务关联度原则

该原则有几种表现形式,比如是否该服务变化时,其他服务也需要进行变化;或者说该服务中的数据是否通常由当前上下文中的范围内使用。

  • 业务能力职责单一原则

服务边界内的业务能力职责应单一,不是完成同一业务能力的服务不应该放在同一个上下文中。

  • 读写分离原则

对于数据读取类型的服务应该尽量放在单独的子域中,而且这种子域一般不应该是核心子域。

  • 组织关系原则

组织中业务结构的划分也是一种参考,因为一个业务部门的存在往往有其独特的业务价值。所以,一个团队一个上下文策略有时候反而是一种有效的拆分策略。团队的构建方式可以是职能团队(Function Team)也可以是特征团队(Feature Team),前者关注于某一个特定职能,如常见的服务端、前端、数据库、UI 等功能团队,而后者则代表一种跨职能的团队构建方式,团队中包括各种职能角色。上下文的构建以及界限的划分是一项跨职能的活动,如果团队组织架构具备跨职能特性,可以安排特定的团队负责特定的上下文并统一管理该上下文对应的界限。

在服务边界划分中,应该尽早识别和剥离通用领域,如账户管理、登录等服务;而对于系统中最复杂且相对多变的子域,需要及早进行隔离并充分考虑它与核心子域之间的协作关系。

服务拆分

服务拆分的维度

关于服务拆分的切入点,我们先从 MartinL.Abbott 所著《架构即未来》中介绍的 AKF 扩展立方体出发寻找一些灵感,然后给出本文中关于服务拆分的两大维度。

AKF 扩展立方体

AKF 扩展立方体(Scalability Cube)是一种可扩展模型,这个立方体有三个轴线,每个轴线描述扩展性的一个维度(见下图),分别是:

  • X轴

代表无差别的克隆服务和数据,工作可以很均匀的分散在不同的服务实例上。

  • Y轴

关注应用中职责的划分,比如数据类型、交易执行类型的划分。

  • Z轴

关注服务和数据的优先级划分,如分地域划分。

以上 X、Y 和 Z 轴的划分可以概括为 X 轴关注水平复制,Z 轴类似数据分区,而 Y 轴则强调基于不同的业务拆分。理论上按照这三个扩展维度,可以将一个单体系统进行无限扩展。举例来说,比如医疗行业中的用户预约挂号应用,一个集群撑不住时可以分成多个集群,后来用户激增还是不够用,经过分析发现是用户和医生访问量很大,就将预约挂号应用拆成了患者服务、医生服务、支付服务等三个服务。三个服务的业务特点各不相同,独立维护,各自都可以再次按需扩展。

在上图中,Y 轴就是我们所说的微服务的拆分模式,即基于不同的业务进行拆分。但在进行业务拆分过程中,我们发现业务往往与数据有较大耦合性,所以接下去我们把业务和数据结合起来对服务拆分的维度展开讨论。

业务与数据

服务拆分存在两大维度,即业务与数据。业务体现在各种功能代码中,通过确定业务的边界,并使用领域与界限上下文、领域事件等技术手段可以实现拆分。而数据的拆分则体现在如何将集中式的中心化数据转变为各个微服务各自拥有的独立数据,这部分工作同样十分具有挑战性。

关于业务和数据谁应该先拆分的问题,可以是先数据库后业务代码,也可以是先业务代码后数据库。然而在拆分中遇到的最大挑战可能会是数据层的拆分,因为在数据库中,可能会存在各种跨表连接查询、跨库连接查询以及不同业务模块的代码与数据耦合得非常紧密的场景,这会导致服务的拆分非常困难。因此在拆分步骤上我们更多的推荐数据库先行。数据模型能否彻底分开,很大程度上决定了微服务的边界功能是否彻底划清。

服务拆分的策略

服务拆分的方法需要根据系统自身的特点和运行状态,通常分为绞杀者与修缮者两种模式。

绞杀者模式

绞杀者模式(Strangler Pattern)最早由 Martin Fowler 提出,指的是在现有系统外围将新功能用新的方式构建为新的服务的策略,通过将新功能做成微服务方式,而不是直接修改原有系统,逐步的实现对老系统替换。采用这种策略,随着时间的推移,新的服务就会逐渐“绞杀”老的系统。对于那些规模很大而又难以对现有架构进行修改的遗留系统,推荐采用绞杀者模式。

绞杀者模式的示意图如下图所示,我们可以看到随着功能演进和时间的不断推移,老的遗留系统功能被逐步削弱,而采用微服务架构的新功能越积越多,最终会形成从量变到质变的过程。绞杀者模式在具体实施过程中,所需要把握的最主要一点原则就是对于任何需要开发的功能一定要完整的采用微服务架构,对于完全独立的新功能这点比较容易把握,而对于涉及到老业务变更的新功能则需要通过重构达到这一目标。

修缮者模式

修缮者模式就如修房或修路一样,将老旧待修缮的部分进行隔离,用新的方式对其进行单独修复。修复的同时,需保证与其他部分仍能协同工作。从这种思路出发,修缮者模式更多表现为一种重构技术。修缮者模式在具体实现上可以参考 Martine Fowler 的 BranchByAbstraction 重构方法,该重构方法的示意图如下图所示。

从上图中,可以看到这种模式的实现方式可以分成三个主要步骤。

  • 抽象层提取

首先通过识别内部的待拆分功能,对其增加抽象接口层,同时对原有代码进行改造,确保其同样实现该抽象层。这样在依赖关系上就添加了一个中间层。

  • 抽象层实现

为抽象层提供新的实现,新的实现采用微服务方式。

  • 抽象层替换

采用新的实现对原有的各个抽象层实现进行逐步替换,直至原有实现被完全废弃,从而完成新老实现方式之间的替换。

服务集成

服务之间势必需要集成,而这种集成关系远比简单的 API 调用要复杂。在本节中,我们将系统分析服务集成的方式以及在微服务架构中的表现形式。关于服务之间的集成存在一些通用的模式,我们也将在梳理这些模式的同时给出实现过程中的最佳实践。

业界关于系统集成存在一些主流的模式和工程实践(见下图),包括文件传输(File Transfer)、共享数据库(Shared Database)、远程过程调用(RPC)和消息传递(Messaging)。

以上四种主流的集成模式各有优缺点。文件传输方式最大的挑战在于如何进行文件的更新和同步;如果使用数据库,在多方共享的条件下如何确保数据库模式统一是一个大问题;RPC 容易产生瓶颈节点;而消息传递在提供松耦合的同时也加大了系统的复杂性。RPC 和消息传递面对的都是分布式环境下的远程调用,远程调用区别于内部方法调用,一方面网络不一定可靠和存在延迟问题,另一方面集成通常面对的是一些异构系统。

对于微服务架构而言,我们的思路是尽量采用标准化的数据结构并降低系统集成的耦合度。我们会根据需要采用上图所示的四种典型的系统集成模式,同时还会引入其它一些手段来达到服务与服务之间的有效集成。我们把微服务架构中服务之间的集成模式分为以下四大类(见下图)。

  • 接口集成

接口集成是服务之间集成的最常见手段,通常基于业务逻辑的需要进行集成。RPC、REST、消息传递和服务总线都可以归为这种集成方式。

  • 数据集成

数据集成同样可以用于微服务之间的交互,共享数据库是一个选择,但也可以通过数据复制(Data Replication)的方式实现数据集成。

  • 客户端集成

由于微服务是一个能够独立运行的整体,有些微服务会包含一些 UI 界面,这也意味着微服务之间也可以通过 UI 界面进行集成。

  • 外部集成

这里把外部集成单独剥离出来的原因在于现实中很多服务之间的集成需求来自于与外部服务的依赖和整合,而在集成方式上也可以综合采用接口集成、数据集成和 UI 集成。

微服务架构基础组件

微服务架构也是一种分布式架构,分布式系统相较于集中式系统具备优势的同时,也存在一些我们不得不考虑的特性。微服务架构的实现一方面与分布式系统架构的实现需求完全一致,另一部分则在分布式系统的基础之上表现出特定的需求。针对这些需求,本篇中将微服务架构基础组件梳理如下:服务之间的通信、面向事件驱动的架构设计方法、负载均衡、服务路由、API 网关和分布式配置中心等。其中服务通信、事件驱动、负载均衡、服务路由我们已经在上一篇中有了详细介绍,本篇将对 API 网关和分布式配置中心做详细展开。

API 网关

在设计模式存在一种外观模式(Façade Pattern,也叫门面模式),其设计意图在于为子系统中的一组接口提供一个一致的入口,这个入口使得这一子系统更加容易使用。表现为用户界面不与系统耦合,而外观类则与系统耦合。在层次化结构中,可以使用外观模式定义系统中每一层的入口。

API 网关本质上就是一种外观模式的具体实现,它是一种服务器端应用程序并作为系统访问的唯一入口。API 网关封装了系统内部架构,为每个客户端提供一个定制的 API。同时,它可能还具有身份验证、监控、缓存、请求管理、静态响应处理等功能。在微服务架构中,API 网关的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。

网关的作用

API 网关的需求来自于多个方面。通常,微服务提供的 API 粒度与客户端的要求不一定完全匹配,微服务一般提供细粒度的 API,这意味着客户端通常需要与多个服务进行交互。更为重要的是,网关能够起到客户端与微服务之间的隔离作用,随着业务需求的变化和时间的演进,网关背后的各个微服务的划分和实现可能需要做相应的调整和升级,这种调整和升级需要实现对客户端透明。

API 网关的作用体现在以下几个主要方面:

  • 解耦

API 网关使客户端和服务器端在调用关系和部署环境上实现解耦,向客户端隐藏了应用如何被划分到微服务的细节。尽管微服务架构支持客户端直接与微服务交互的方式,但当需要交互的微服务数量较多时,解耦就成为一项核心需求。

  • API 优化

API 网关向每个客户端提供最优的 API。在多客户端场景中,对于同一个业务请求,不同的客户端一般需要不同的数据。例如,对于同一个用户查询功能,PC 端会一次性获取所有数据,而对于手机 App 而言,可以通过分页的方式逐步加载用户信息。

  • 简化调用过程

由于能够对返回数据进行灵活处理,API 网关减少了请求往返次数,从而简化了客户端的调用,也提高了服务访问的性能。比如,API 使得客户端可以在一次请求中向多个服务拉取数据。请求数量的减少也会直接提升用户体验。

另一方面,API 网关也增加了系统的复杂性和响应时间,因为 API 网关作为单独的应用程序也需要进行开发、部署和管理,为了暴露每个微服务的端点,开发人员必须更新 API 网关。同时,通过 API 网关也多了一层网络跳转。但是我们认为 API 网关模式的缺点相比其优点是微不足道的,因此该模式目前被广泛应用在微服务架构设计和实现中。如下图所示,API 网关作为单一入口通过请求转换适配整合后台微服务体系,面向各种客户端提供统一服务。

网关的功能

API 网关的结构参考下图所示。在这个结构背后,我们需要挖掘其所应具备的核心功能,从而发挥上文中所提到的各项作用。

API 网关在实现上需要提供如下功能:

  • NIO 接入和异步接出

API 网关作为所有的客户端与微服务之间的桥梁,在两者之间多加了一层网络跳转。为了尽可能的消除这层网络跳转所带来的性能影响,API 网关在实现上需要提供 NIO 接入和异步接出的功能。参考第6篇《RPC——一切架构的基础》介绍的服务通信机制,我们明确这样的实现方式能够提供较高的通信性能。

  • 报文格式转换

API 网关的一大作用在于构建异构系统,如下图所示,API 网关作为单一入口通过协议转换整合后台基于 REST、AMQP 和 Dubbo 等不同风格和实现技术的微服务,面向 Web、Mobile、开放平台等特定客户端提供统一服务。

  • 安全性控制

一般而言,无论是对内网还是外网的接口都是需要做用户身份认证的,而用户认证在一些规模较大的系统都会采用统一的单点登录功能,如果每个微服务都要对接单点登录系统,那么显然比较浪费资源且开发效率低。API 网关是统一管理安全性的绝佳场所,可以将认证的部分抽取到网关层,然后微服务系统无需关注认证的逻辑只关注自身业务即可。常见的安全性技术如秘钥交换、客户端认证与报文加解密等功能都可以在 API 网关中加以实现。

  • 访问控制

某些场景下需要控制客户端的访问次数和访问频率,对于一些高并发系统有时还会有限流的需求。为了防止站点不被未知的大流量冲跨,在网关上可以配置一个阀值,当请求数超过阀值时就直接返回错误而不继续访问后台服务。

  • 业务路由支持

可以在网关层制定灵活的路由策略。针对一些特定的 API,我们需要设置白名单、路由规则等各类限制。而这些非业务功能的配置以及变更都可以在网关层单独操作。

分布式配置中心

配置管理的需求在任何类型的系统中都存在,而且随着业务复杂度的上升和技术架构的演变,系统对配置方式也会提出越来越高的要求。例如在单块系统中,配置管理方式典型的演变过程往往是这样的:刚开始是配置文件比较少,更新频率也不会太高,所以倾向于把所有配置跟源代码一起放在代码仓库中;之后由于配置文件数量和改动频率的增加,就会考虑将配置文件从代码仓库中分离出来,或者放在 CI 服务器上通过打包脚本打入应用包中,或者直接放到运行应用的服务器特定目录下,剩下的非文件形式的关键配置则存入数据库中。

上述配置管理的演变过程在单体应用阶段非常常见,在增加配置信息安全性的同时,也往往可以运行的很好。但到了微服务阶段,面对爆发式增长的应用数量和服务器数量,就显得无能为力。为此,在微服务架构中,一般都需要引入配置中心(Configuration Center)的设计思想和相关工具。

配置中心模型

所谓配置中心,简单来说就是一种统一管理各种应用配置的基础服务组件。本小结我们将给出配置中心的基本模型。

在讨论配置中心之前,我们先来梳理一下配置相关的内容和分类,参考如下:

  • 按配置的来源划分

主要有源代码文件,数据库和远程调用。

  • 按配置的适用环境划分

可分为开发环境,测试环境,预发布环境,生产环境等。

  • 按配置的集成阶段划分

可分为编译时,打包时和运行时。编译时,最常见的有两种,一是源代码级的配置,二是把配置文件和源代码一起提交到代码仓库中。打包时,即在应用打包阶段通过某种方式将配置打入最终的应用包中。运行时,是指应用启动前并不知道具体的配置,而是先从本地或者远程获取配置,然后再正常启动。

  • 按配置的加载方式划分

可分为单次加载型配置和动态加载型配置。

基于配置相关的内容和分类,构建一个合适的配置中心,至少需要满足如下4个核心需求:非开发环境下应用配置的保密性,避免将关键配置写入源代码;不同部署环境下应用配置的隔离性,比如非生产环境的配置不能用于生产环境;同一部署环境下的服务器应用配置的一致性,即所有服务器使用同一份配置;分布式环境下应用配置的可管理性,即提供远程管理配置的能力。

采用配置中心也就意味着采用集中式配置管理的设计思想(见下图)。在集中式配置中心中,开发、测试和生产等不同的环境配置信息统一保存在配置中心中,这是一个维度。而另一个维度就是分布式集群环境,需要确保集群中同一类服务的所有服务器保存同一份配置文件并且能够同步更新。

微服务实现技术

讲完微服务框架应该具备的核心组件之后,我们来看一下目前业界有哪些现成的框架可供我们直接使用,这里列举了 Dubbo 和 Spring Cloud 这两个目前最主流框架作为我们选型的基础。

Dubbo

Dubbo 是国内 SOA 框架集大成之作,基本具备一个 SOA 框架应有的所有功能,包括高性能通信、多协议集成、服务注册与发现、服务路由、负载均衡、服务治理等核心功能。作为一个 RPC 架构和 SOA 架构,Dubbo 无疑是非常优秀的,但在功能完备性上,API 网关、服务熔断器等核心组件在 Dubbo 中并没有完整体现。

从社区活跃度上,Dubbo 在2012年底基本已经不再更新,但不影响其在各大互联网公司的应用和扩展。好消息是最近 Alibaba 宣布重新启动 Dubbo 的维护工作。

Dubbo 的文档可以说在国内开源框架中算是一流的,非常全面且讲解的也比较深入,由于版本已经稳定不再更新,所以也不太会出现不一致的情况,学习成本较低。

Spring Cloud

Spring Cloud 是 Spring 家族中新的一员,重点打造面向服务化的功能组件,在功能上服务注册中心、API 网关、服务熔断器、分布式配置中心等组件都能在 Spring Cloud 中找到对应的实现。

从版本更新上,显然 Spring Cloud 也表现得非常活跃。目前 Spring Cloud 在 Github 的托管代码几乎每天都有更新,其发展仍处于高速迭代的阶段。

Spring Cloud 在一定程度上是一种集成型的框架,其内部大量依赖了各种外部的第三方工具和框架,所以文档在体量上自然要比 Dubbo 多很多,文档内容上还算简洁清楚,但是更多的是偏向整合,更深入的使用方法还是需要查看第三方组件的详细文档。

对比与结论

通过对比分析,这里推荐 Spring Cloud 作为我们实现微服务架构的主体框架。功能的完备性是我们选择 Spring Cloud 的主要原因,下表中列出了 Dubbo 与 Spring Cloud 功能对比一览,可以看到 Spring Cloud 提供了很多 Dubbo 所不具备但对微服务实现又必不可少的核心组件。

- Dubbo Spring Cloud
服务注册中心 Zookeeper Spring Cloud Netflix Eureka
服务调用方式 RPC RESTful API
API网关
服务熔断器 不完善 Spring Cloud Netflix Hystrix
分布式配置 Spring Cloud Config
服务跟踪 Spring Cloud Sleuth
消息总线 Spring Cloud Bus
数据流 Spring Cloud Stream
安全性 Spring Cloud Security

另一个选择 Spring Cloud 的原因在于服务之间的交互方式。我们知道微服务架构中推崇基于 HTTP 协议的 RESTful 风格实现服务间通信,而 Dubbo 的服务调用通过 RPC 实现。采用 RPC 方式会导致服务提供方与调用方接口产生较强依赖,而且服务对技术敏感,无法做到通用。Spring Cloud 采用的就是 RESTful 风格,这方面更加符合微服务架构的设计理念。

Spring Cloud 还具备一个天生的优势,因为它是 Spring 家庭的一员,而 Spring 在开发领域的强大地位给 Spring Cloud 起到很好的推动作用。同时,Spring Cloud 基于 Spring Boot,而Spring Boot 目前已经在越来越多的公司得到应用和推广,用来简化 Spring 应用的框架搭建以及开发过程。Spring Cloud 也继承了 Spring Boot 简单配置、快速开发、轻松部署的特点,让原本复杂的架构工作变得相对容易上手。

通过上面这几个环节的分析,相信读者对 Dubbo 和 Spring Cloud 有了一个初步的了解。从目前 Spring Cloud 的被关注度和活跃度上来看,很有可能将来会成为微服务架构的标准框架。

微服务与架构师

微服务架构也对处于快速演进过程中的架构师提出了新的要求,关注服务之间的交互,而不要过于关注各个服务内部实现细节;作出符合团队目标的技术选择,提供代码模板;考虑系统允许多少的可变性,并能够快速适应变化以及建设团队是架构师采用微服务架构的基本方法。

下篇导读

本篇讨论的对象是微服务架构,这是目前最为流行的一种架构设计模式。我们不会对微服务架构的方方面面做详细的展开,但了解文中介绍的服务边界、服务拆分和服务集成的基本方法,以及掌握微服务架构的核心组件和实现技术有助于为进一步学习打好基础。在下一篇中,我们将讨论消息传递系统,这是一种确保系统低耦合的常见架构设计方式和技术体系。

上一篇
下一篇
目录