11 微服务架构

0.1. 微服务与SOA的关系

关于 SOA 和微服务的关系和区别,大概分为下面几个典型的观点。

  1. 微服务是SOA的实现方式:SOA 是一种架构理念,而微服务是 SOA 理念的一种具体实现方法:
    1. 微服务就是使用 HTTP RESTful 协议来实现 ESB 的 SOA
    2. 使用 SOA 来构建单个系统就是微服务
    3. 微服务就是更细粒度的 SOA
  2. 微服务是去掉ESB后的SOA:传统 SOA 架构最广为人诟病的就是庞大、复杂、低效的 ESB,因此将 ESB 去掉,改为轻量级的 HTTP 实现,就是微服务。
  3. 微服务是一种和 SOA 相似但本质上不同的架构理念:微服务和 SOA 只是类似,本质是不同的架构设计理念。
    1. 相似点在于两者都关注“服务”,通过服务的拆分来解决可扩展性问题。
    2. 本质上不同的地方在于几个核心理念的差异:是否有 ESB、服务的粒度、架构设计的目标等。

0.1.1. 对比微服务与SOA的实现

  1. 服务粒度:SOA 的服务粒度要粗一些,而微服务的服务粒度要细一些。对一个大型企业来说:
    1. “员工管理系统”就是一个 SOA 架构中的服务;
    2. 而采用微服务架构,则“员工管理系统”会被拆分为更多的服务,比如“员工信息管理”“员工考勤管理”“员工假期管理”和“员工福利管理”等更多服务。
  2. 服务通信:
    1. SOA 采用了 ESB 作为服务间通信的关键组件,负责服务定义、服务路由、消息转换、消息传递,总体上是重量级的实现。
    2. 微服务使用统一的协议和格式,例如,RESTful 协议、RPC 协议,无须 ESB 这样的重量级实现。
  3. 服务交付:
    1. SOA 对服务的交付并没有特殊要求,因为 SOA 更多考虑的是兼容已有的系统;
    2. 微服务的架构理念要求“快速交付”,相应地要求采取自动化测试、持续集成、自动化部署等敏捷开发相关的最佳实践。如果没有这些基础能力支撑,微服务规模一旦变大(例如,超过 20 个微服务),整体就难以达到快速交付的要求,这也是很多企业在实行微服务时踩过的一个明显的坑,就是系统拆分为微服务后,部署的成本呈指数上升。
  4. 应用场景:
    1. SOA 更加适合于庞大、复杂、异构的企业级系统,因为成本和影响太大,只能采用兼容的方式进行处理,而承担兼容任务的就是 ESB。
    2. 微服务更加适合于快速、轻量级、基于 Web 的互联网系统,这类系统业务变化快,需要快速尝试、快速交付;同时基本都是基于 Web,虽然开发技术可能差异很大(例如,Java、C++、.NET 等),但对外接口基本都是提供 HTTP RESTful 风格的接口,无须考虑在接口层进行类似 SOA 的 ESB 那样的处理。

SOA 和微服务本质上是两种不同的架构设计理念,只是在“服务”这个点上有交集而已,因此两者的关系应该是上面第三种观点。

smalllightweightautomated,基本上浓缩了微服务的精华,也是微服务与 SOA 的本质区别所在。SOA 和微服务是两种不同理念的架构模式,并不存在孰优孰劣,只是应用场景不同而已。

0.2. 微服务的陷阱

似乎微服务大大优于 SOA,导致在实践时不加思考地采用微服务:

  • 既不考虑团队的规模,
  • 也不考虑业务的发展,
  • 也没有考虑基础技术的支撑,

只是觉得微服务很牛就赶紧来实施,以为实施了微服务后就什么问题都解决了,而一旦真正实施后才发现掉到微服务的坑里面去了。

微服务具体有哪些坑:

  1. 服务划分过细,服务间关系复杂:虽然单个服务的复杂度下降了,但整个系统的复杂度却上升了,微服务将系统内的复杂度转移为系统间的复杂度。从理论的角度来计算,n 个服务的复杂度是 n×(n-1)/2,整体系统的复杂度是随着微服务数量的增加呈指数级增加的。
  2. 服务数量太多,团队效率急剧下降:一个简单的需求开发就需要涉及多个微服务,相互之间的接口就有 6 ~ 7 个,无论是设计、开发、测试、部署,都需要不停地在不同的服务间切换。
  3. 调用链太长,性能下降:微服务之间都是通过 HTTP 或者 RPC 调用的,每次调用必须经过网络。一般线上的业务接口之间的调用,平均响应时间大约为 50 毫秒,如果一个请求需要经过 6 次微服务调用,则性能消耗就是 300 毫秒,这在很多高性能业务场景下是难以满足需求的。为了支撑业务请求,可能需要大幅增加硬件,这就导致了硬件成本的大幅上升。
  4. 调用链太长,问题定位困难:一次用户请求需要多个微服务协同处理,任意微服务的故障都将导致整个业务失败。微服务数量较多,且故障存在扩散现象,快速定位到底是哪个微服务故障是一件复杂的事情。
  5. 没有自动化支撑,无法快速交付:靠人工操作,微服务不但达不到快速交付的目的,甚至还不如一个大而全的系统效率高,需要自动化测试、部署、监控。
  6. 没有服务治理,微服务数量多了后管理混乱:随着微服务种类和数量越来越多,如果没有服务治理系统进行支撑,微服务提倡的 lightweight 就会变成问题。,如:服务路由、服务故障隔离、服务注册与发现。

0.3. 最佳实践——方法

0.3.1. 服务粒度

针对微服务拆分过细导致的问题,建议基于团队规模进行拆分,类似贝索斯在定义团队规模时提出的“两个披萨”理论(每个团队的人数不能多到两张披萨都不够吃的地步),或者微服务拆分粒度的“三个火枪手”原则,即一个微服务三个人负责开发。

在实施微服务架构时,根据团队规模来划分微服务数量,如果业务规继续发展,团队规模扩大,再将已有的微服务进行拆分。

  • 系统规模来讲,3 个人负责开发一个系统,系统的复杂度刚好达到每个人都能全面理解整个系统,又能够进行分工的粒度。
  • 团队管理来讲,3 个人可以形成一个稳定的备份,即使 1 个人休假或者调配到其他系统,剩余 2 个人还可以支撑。
  • 技术提升的角度来讲,3 个人的技术小组既能够形成有效的讨论,又能够快速达成一致意见。

“三个火枪手”的原则主要应用于微服务设计和开发阶段,如果微服务经过一段时间发展后已经比较稳定,处于维护期了,无须太多的开发,那么平均 1 个人维护 1 个微服务甚至几个微服务都可以。当然考虑到人员备份问题,每个微服务最好都安排 2 个人维护,每个人都可以维护多个微服务。

0.3.2. 拆分方法

基于“三个火枪手”的理论,可以计算出拆分后合适的服务数量,但具体怎么拆也是有技巧的,不是只能按照业务来进行拆分,而是可以根据目的的不同灵活地选取不同的拆分方式。

0.3.2.1. 基于业务逻辑拆分

这是最常见的一种拆分方式,将系统中的业务模块按照职责范围识别出来,每个单独的业务模块拆分为一个独立的服务。

基于业务逻辑拆分虽然看起来很直观,但在实践过程中最常见的一个问题就是团队成员对于“职责范围”的理解差异很大,经常会出现争论,难以达成一致意见。

从业务的角度来拆分的话,规模粗和规模细都没有问题,因为拆分基础都是业务逻辑,要判断拆分粒度,不能从业务逻辑角度,而要根据“三个火枪手”的原则,计算一下大概的服务数量范围,然后再确定合适的“职责范围”,否则就可能出现划分过粗或者过细的情况,而且大部分情况下会出现过细的情况。

例如:

  1. 如果团队规模是 10 个人支撑业务,按照“三个火枪手”规则计算,大约需要划分为 4 个服务,那么“登录、注册、用户信息管理”都可以划到“用户服务”职责范围内;
  2. 如果团队规模是 100 人支撑业务,服务数量可以达到 40 个,那么“用户登录“就是一个服务了;
  3. 如果团队规模达到 1000 人支撑业务,那“用户连接管理”可能就是一个独立的服务了。

0.3.2.2. 基于可扩展拆分

将系统中的业务模块按照稳定性排序,将已经成熟和改动不大的服务拆分为稳定服务,将经常变化和迭代的服务拆分为变动服务

  • 稳定的服务粒度可以粗一些,即使逻辑上没有强关联的服务,也可以放在同一个子系统中;
  • 不稳定的服务粒度可以细一些,但也不要太细,始终记住要控制服务的总数量。

这样拆分主要是为了提升项目快速迭代的效率,避免在开发的时候,不小心影响了已有的成熟功能导致线上问题。

0.3.2.3. 基于可靠性拆分

将系统中的业务模块按照优先级排序,将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用。

具体拆分的时候,核心服务可以是一个也可以是多个,只要最终的服务数量满足“三个火枪手”的原则就可以。

这样拆分带来下面几个好处:

  1. 避免非核心服务故障影响核心服务
  2. 核心服务高可用方案可以更简单:核心服务的功能逻辑更加简单,存储的数据可能更少,用到的组件也会更少,设计高可用方案大部分情况下要比不拆分简单很多。
  3. 能够降低高可用成本:将核心服务拆分出来后,核心服务占用的机器、带宽等资源比不拆分要少很多。因此,只针对核心服务做高可用方案,机器、带宽等成本比不拆分要节省较多。

0.3.2.4. 基于性能拆分

基于性能拆分和基于可靠性拆分类似,将性能要求高或者性能压力大的模块拆分出来,避免性能压力大的服务影响其他服务。

常见的拆分方式和具体的性能瓶颈有关,可以拆分 Web 服务、数据库、缓存等。例如电商的抢购,性能压力最大的是入口的排队功能,可以将排队功能独立为一个服务。

以上几种拆分方式不是多选一,而是可以根据实际情况自由排列组合,例如可以基于可靠性拆分出服务 A,基于性能拆分出服务 B,基于可扩展拆分出 C/D/F 三个服务,加上原有的服务 X,最后总共拆分出 6 个服务(A/B/C/D/F/X)。

0.3.3. 基础设施

大部分人主要关注的是微服务的“small”和“lightweight”特性,但实际上真正决定微服务成败的,恰恰是那个被大部分人都忽略的“automated”。

因为服务粒度即使划分不合理,实际落地后如果团队遇到麻烦,自然会想到拆服务或者合服务;如果“ mated”相关的基础设施不健全,那微服务就是焦油坑,让研发、测试、运维陷入各种微服务陷阱中。

微服务基础设施如下图所示:

image

微服务并不是很多人认为的那样又简单又轻量级。要做好微服务,这些基础设施都是必不可少的,否则微服务就会变成一个焦油坑,让业务和团队在里面不断挣扎且无法自拔。

微服务并没有减少复杂度,而只是将复杂度从 ESB 转移到了基础设施。

虽然建设完善的微服务基础设施是一项庞大的工程,但也不用太过灰心。

  1. 第一个原因是:已经有开源的微服务基础设施全家桶了,例如大名鼎鼎的 Spring Cloud 项目,涵盖了服务发现、服务路由、网关、配置中心等功能;
  2. 第二个原因是:如果微服务的数量并不是很多的话,并不是每个基础设施都是必须的。通常情况下,建议按照下面优先级来搭建基础设施:
    1. 服务发现、服务路由、服务容错:这是最基本的微服务基础设施。
    2. 接口框架、API 网关:主要是为了提升开发效率,接口框架是提升内部服务的开发效率,API 网关是为了提升与外部服务对接的效率。
    3. 自动化部署、自动化测试、配置中心:主要是为了提升测试和运维效率。
    4. 服务监控、服务跟踪、服务安全:主要是为了进一步提升运维效率。

以上 3 和 4 两类基础设施,其重要性会随着微服务节点数量增加而越来越重要,但在微服务节点数量较少的时候,可以通过人工的方式支撑,虽然效率不高,但也基本能够顶住。

0.4. 最佳实践——基础设施

每项微服务基础设施都是一个平台、一个系统、一个解决方案,如果要自己实现,其过程和做业务系统类似,都需要经过需求分析、架构设计、开发、测试、部署上线等步骤。

0.4.1. 自动化测试

微服务将原本大一统的系统拆分为多个独立运行的“微”服务,微服务之间的接口数量大大增加,并且微服务提倡快速交付,版本周期短,版本更新频繁。如果每次更新都靠人工回归整个系统,则工作量大,效率低下,达不到“快速交付”的目的,因此必须通过自动化测试系统来完成绝大部分测试回归的工作。

自动化测试涵盖的范围包括代码级的单元测试、单个系统级的集成测试、系统间的接口测试,理想情况是每类测试都自动化。如果因为团队规模和人力的原因无法全面覆盖,至少要做到接口测试自动化

0.4.2. 自动化部署

相比大一统的系统,微服务需要部署的节点增加了几倍甚至十几倍,微服务部署的频率也会大幅提升,综合计算下来,微服务部署的次数是大一统系统部署次数的几十倍。

这么大量的部署操作,如果继续采用人工手工处理,需要投入大量的人力,且容易出错,因此需要自动化部署的系统来完成部署操作。自动化部署系统包括版本管理、资源管理(例如,机器管理、虚拟机管理)、部署操作、回退操作等功能。

0.4.3. 配置中心

微服务的节点数量非常多,通过人工登录每台机器手工修改,效率低,容易出错。特别是在部署或者排障时,需要快速增删改查配置,人工操作的方式显然是不行的。

除此以外,有的运行期配置需要动态修改并且所有节点即时生效,人工操作是无法做到的。综合上面的分析,微服务需要一个统一的配置中心来管理所有微服务节点的配置。

配置中心包括配置版本管理(例如,同样的微服务,有 10 个节点是给移动用户服务的,有 20 个节点给联通用户服务的,配置项都一样,配置值不一样)、增删改查配置、节点管理、配置同步、配置推送等功能。

0.4.4. 接口框架

微服务提倡轻量级的通信方式,一般采用 HTTP/REST 或者 RPC 方式统一接口协议。但在实践过程中:

  1. 统一接口协议,
  2. 统一接口传递的数据格式。

因此需要统一接口框架,它不是一个可运行的系统,一般以库或者包的形式提供给所有微服务调用。

0.4.5. API网关

系统拆分为微服务后,内部的微服务之间是互联互通的,相互之间的访问都是点对点的。如果外部系统想调用系统的某个功能,也采取点对点的方式,则外部系统会非常“头大”。因为在外部系统看来,它不需要也没办法理解这么多微服务的职责分工和边界,它只会关注它需要的能力,而不会关注这个能力应该由哪个微服务提供。

除此以外,外部系统访问系统还涉及安全和权限相关的限制,如果外部系统直接访问某个微服务,则意味着每个微服务都要自己实现安全和权限的功能,这样做不但工作量大,而且都是重复工作。

综合上面的分析,微服务需要一个统一的 API 网关,负责外部系统的访问操作。

API 网关是外部系统访问的接口,所有的外部系统接⼊系统都需要通过 API 网关,主要包括接入鉴权(是否允许接入)、权限控制(可以访问哪些功能)、传输加密、请求路由、流量控制等功能。

0.4.6. 服务发现

微服务种类和数量很多,如果这些信息全部通过手工配置的方式写入各个微服务节点,

  1. 首先配置工作量很大,配置文件可能要配几百上千行,几十个节点加起来后配置项就是几万几十万行了,人工维护这么大数量的配置项是一项灾难;
  2. 其次是微服务节点经常变化,可能是由于扩容导致节点增加,也可能是故障处理时隔离掉一部分节点,还可能是采用灰度升级,先将一部分节点升级到新版本,然后让新老版本同时运行。不管哪种情况,我们都希望节点的变化能够及时同步到所有其他依赖的微服务。如果采用手工配置,是不可能做到实时更改生效的。

因此,需要一套服务发现的系统来支撑微服务的自动注册和发现。服务发现主要有两种实现方式:自理式和代理式。

0.4.6.1. 自理式

image

自理式结构就是指每个微服务自己完成服务发现

自理式服务发现实现比较简单,因为这部分的功能一般通过统一的程序库或者程序包提供给各个微服务调用,而不会每个微服务都自己来重复实现一遍;并且由于每个微服务都承担了服务发现的功能,访问压力分散到了各个微服务节点,性能和可用性上不存在明显的压力和风险。

0.4.6.2. 代理式

image

代理式结构就是指微服务之间有一个负载均衡系统,由负载均衡系统来完成微服务之间的服务发现。

代理式的方式看起来更加清晰,微服务本身的实现也简单了很多,但实际上这个方案风险较大。

  1. 第一个风险是可用性风险,一旦 LOAD BALANCER 系统故障,就会影响所有微服务之间的调用;
  2. 第二个风险是性能风险,所有的微服务之间的调用流量都要经过 LOAD BALANCER 系统,性能压力会随着微服务数量和流量增加而不断增加,最后成为性能瓶颈。

因此 LOAD BALANCER 系统需要设计成集群的模式,但 LOAD BALANCER 集群的实现本身又增加了复杂性。

不管是自理式还是代理式,服务发现的核心功能就是服务注册表,注册表记录了所有的服务节点的配置和状态,每个微服务启动后都需要将自己的信息注册到服务注册表,然后由微服务或者 LOAD BALANCER 系统到服务注册表查询可用服务。

0.4.7. 服务路由

有了服务发现后,微服务之间能够方便地获取相关配置信息,但具体进行某次调用请求时,还需要从所有符合条件的可用微服务节点中挑选出一个具体的节点发起请求,这就是服务路由需要完成的功能。

服务路由和服务发现紧密相关,服务路由一般不会设计成一个独立运行的系统,通常情况下是和服务发现放在一起实现的。

  • 对于自理式服务发现,服务路由是微服务内部实现的;
  • 对于代理式服务发现,服务路由是由 LOAD BALANCER 系统实现的。

无论放在哪里实现,服务路由核心的功能就是路由算法。常见的路由算法有:

  • 随机路由、
  • 轮询路由、
  • 最小压力路由、
  • 最小连接数路由等。

0.4.8. 服务容错

系统拆分为微服务后,单个微服务故障的概率变小,故障影响范围也减少,但是微服务的节点数量大大增加。从整体上来看,系统中某个微服务出故障的概率会大大增加。

微服务具有故障扩散的特点,如果不及时处理故障,故障扩散开来就会导致看起来系统中很多服务节点都故障了,因此需要微服务能够自动应对这种出错场景,及时进行处理。否则,如果节点一故障就需要人工处理,投入人力大,处理速度慢;而一旦处理速度慢,则故障就很快扩散,所以需要服务容错的能力。

常见的服务容错包括请求重试、流控和服务隔离。通常情况下,服务容错会集成在服务发现和服务路由系统中。

0.4.9. 服务监控

系统拆分为微服务后,节点数量大大增加,导致需要监控的机器、网络、进程、接口调用数等监控对象的数量大大增加;同时,一旦发生故障,需要快速根据各类信息来定位故障。这两个目标如果靠人力去完成是不现实的,因此需要服务监控系统来完成微服务节点的监控。

服务监控的主要作用有:实时搜集信息并进行分析,避免故障后再来分析,减少了处理时间。

服务监控可以在实时分析的基础上进行预警,在问题萌芽的阶段发觉并预警,降低了问题影响的范围和时间。

通常情况下,服务监控需要搜集并分析大量的数据,因此建议做成独立的系统,而不要集成到服务发现、API 网关等系统中。

0.4.10. 服务跟踪

服务监控可以做到微服务节点级的监控和信息收集,但如果需要跟踪某一个请求在微服务中的完整路径,服务监控是难以实现的。因为如果每个服务的完整请求链信息都实时发送给服务监控系统,数据量会大到无法处理。

服务监控和服务跟踪的区别可以简单概括为宏观微观的区别。

例如,A 服务通过 HTTP 协议请求 B 服务 10 次,B 通过 HTTP 返回 JSON 对象:

  • 服务监控会记录请求次数、响应时间平均值、响应时间最高值、错误码分布这些信息;
  • 服务跟踪会记录其中某次请求的发起时间、响应时间、响应错误码、请求参数、返回的 JSON 对象等信息。

目前无论是分布式跟踪还是微服务的服务跟踪,绝大部分请求跟踪的实现技术都基于 Google 的 Dapper 论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》。

0.4.11. 服务安全

系统拆分为微服务后,数据分散在各个微服务节点上。

从系统连接的角度来说,任意微服务都可以访问所有其他微服务节点; 从业务的角度来说,部分敏感数据或者操作,只能部分微服务可以访问,而不是所有的微服务都可以访问,因此需要设计服务安全机制来保证业务和数据的安全性。

服务安全主要分为三部分:接入安全、数据安全、传输安全。

通常情况下,服务安全可以集成到配置中心系统中进行实现,即配置中心配置微服务的接入安全策略和数据安全策略,微服务节点从配置中心获取这些配置信息,然后在处理具体的微服务调用请求时根据安全策略进行处理。由于这些策略是通用的,一般会把策略封装成通用的库提供给各个微服务调用。

上次修改: 9 June 2020