主页

索引

模块索引

搜索页面

微服务架构

微服务的陷阱

  1. 服务划分过细,服务间关系复杂:

    服务划分过细,单个服务的复杂度确实下降了,但整个系统的复杂度却上升了,
    因为微服务将系统内的复杂度转移为系统间的复杂度了。
    
    从理论的角度来计算,n 个服务的复杂度是 n×(n-1)/2,
    整体系统的复杂度是随着微服务数量的增加呈指数级增加的。
    
https://img.zhaoweiguo.com/knowledge/images/architectures/expandabilitys/microservice1.png
  1. 服务数量太多,团队效率急剧下降:

    有的团队人员规模是 5 ~ 6 个人,然而却拆分出 30 多个微服务,平均每个人要维护 5 个以上的微服务
    
    a. 开发工程师: 要设计多个接口,打开多个工程,调试时要部署多个程序,提测时打多个包
    b. 测试工程师: 要部署多个环境,准备多个微服务的数据,测试多个接口
    c. 运维工程师: 每次上线都要操作多个微服务,并且微服务之间可能还有依赖关系
    
  2. 调用链太长,性能下降:

    一般线上的业务接口之间的调用,平均响应时间大约为 50 毫秒,
    若用户的请求要经过 6 次微服务调用,则性能消耗就是 300 毫秒,这在高性能业务场景下是难以满足需求的
    
  3. 调用链太长,问题定位困难:

    系统拆分为微服务后,一次用户请求需要多个微服务协同处理,任意微服务的故障都将导致整个业务失败。
    然而由于微服务数量较多,且故障存在扩散现象,快速定位到底是哪个微服务故障是一件复杂的事情。
    
https://img.zhaoweiguo.com/knowledge/images/architectures/expandabilitys/microservice2.png

在实际定位时是不会有样例图中这么清晰的:

最开始是用户报错,这时我们首先会去查 Service A
导致 Service A 故障的原因有很多,
我们可能要花半个小时甚至 1 个小时才能发现是 Service B 返回错误导致的。
于是我们又去查 Service B,这相当于重复 Service A 故障定位的步骤……

如此循环下去,最后可能花费了几个小时才能定位到是 Service C 的数据库慢查询导致了错误。
https://img.zhaoweiguo.com/knowledge/images/architectures/expandabilitys/microservice3.png

如果多个微服务同时发生不同类型的故障,则定位故障更加复杂。Service C 的数据库发生慢查询故障,同时 Service C 到 Service D 的网络出现故障,此时到底是哪个原因导致了 Service C 返回 Error 给 Service B,需要大量的信息和人力去排查。

  1. 没有自动化支撑,无法快速交付:

    如果没有相应的自动化系统进行支撑,都是靠人工去操作,
    那么微服务不但达不到快速交付的目的,甚至还不如一个大而全的系统效率高。
    
    例如:
      没有自动化测试支撑,每次测试时需要测试大量接口
      没有自动化部署支撑,每次部署 6 ~ 7 个服务,几十台机器,运维人员敲 shell 命令逐台部署,手都要敲麻
      没有自动化监控,每次故障定位都需要人工查几十台机器几百个微服务的各种状态和各种日志文件
    
  2. 没有服务治理,微服务数量多了后管理混乱:

    随着微服务种类和数量越来越多,
    如果没有服务治理系统进行支撑,微服务提倡的 lightweight 就会变成问题。
    
    主要问题有:
      a. 服务路由
          假设某个微服务有 60 个节点,部署在 20 台机器上,那么其他依赖的微服务如何知道这个部署情况呢?
      b. 服务故障隔离
          假设上述例子中的 60 个节点有 5 个节点发生故障了,依赖的微服务如何处理这种情况呢?
      c. 服务注册和发现
          从 60 个节点扩容到 80 个节点,或者将 60 个节点缩减为 40 个节点,
          新增或者减少的节点如何让依赖的服务知道呢?
    

备注

拆分服务同时要把自动化运维系统和多维度监控系统,包括问题定位跟踪系统建立起来,要不然拆了就是噩梦。

思考

好处,原来的单体应用都服务化了,扩容简单很多。功能隔离后之前一个 bug 导致系统挂掉的现象没了。问题责任定位划分的更清楚,比如之前大量慢 sql 无人管,现在通过监控快速找到开发责任人。 再说下坏处,1. 服务太多了,人不够啊。之前的架构师按照小的原则,把数据层,服务层,应用层严格拆分。一个人手上超过 10 几个服务… 2. 服务化不彻底,太多事手工干。服务监控只能监控一半指标,各种远程调用异常没人解决。运维只有打包发布做了自动化。可以想象下开发人员基本下改 bug 和发布的死循环中。服务网关没有,服务调用就是一张密密麻麻的网 3. 培训不到位,直接上阵,开发人员对微服务理解不到位,服务质量可想而知 4. 没有专职测试,自动化测试靠开发写脚本,谁有空啊,单元测试能写一个就算相当有觉悟了 总结下来,做服务化改造首先问自己这些问题,业务真的需要微服务来解决吗?真的所有模块的问题都要微服务来解决吗?技术人员的配置和水平达到要求了吗? 我这边团队刚好有人离职,现在几个人人手 70 + 个微服务,一团乱😓 采坑,一个人负责 20 多个微服务,前期底子是外包搭建的。。。MD 一个消息消费都 TM 给你整一个微服务,这 TM 按照项目收的外包费吗? 绝对的负面例子 一个人负责 10 个人服务算啥,我他妈一天在 5 个不同项目切换写代码过😹 你遇到过的我也遇到过,尤其是服务没有网关,自动化过程只有打包!发布靠开发人员自己上去发布!一个开发人员手上管理好几个微服务

个别服务没有熔断出来,出现过雪崩效应 2、服务拆分过细,服务调用链过长 3、开发人员能力不一样,代码水平不一样 4、没有监控措施 5、每个服务部署多台,日志查询就会死人的感觉 6、开发过程中经常出现访问不到该访问的接口,这是因为开发人员经常启动本地服务,就会导致 30% 的概率访问不到 7、使用了不合理的持久层框架,使用了 JPA 访问

我们目前全部微服务,踩坑踩了不少,拆分服务同时要把自动化运维系统和多维度监控系统,包括问题定位跟踪系统建立起来,要不然拆了就是噩梦。

1、没有一个统一的网关服务,前端请求后端服务都需要后端的服务 A 来充当安全校验,权限校验等,A 服务充当了多重职责,变的职责不明确了,后来抽出网关系统,负责平台统一的流量入口。在构建微服务网关系统是至关重要的。 2、监控系统不完善,调用链跟踪,异常报警都不完善,对微服务是巨坑,查找问题如同一场噩梦,调用链很长,一旦发生异常不知道到底哪里出现问题,得一个一个去找。后来慢慢完善,变得好了很多,问题很快定位。 3、加入网关后,没有一个统一的服务发现注册中心,网关的路由依靠人工手动配置,变的很麻烦,也很容易出错。后来引入 consul,得到改善。

第一个是到底满足什么条件才能算一个微服务,往往是为了拆分而拆分,比如将一个服务拆分成了 ABC 三个微服务,BC 仅仅是为 A 提供服务不具有独立的能力,接口也只是内部接口,对外完全不可见。 第二个是微服务内部是有状态的,这就导致了其它访问的时候必须请求到那个实例,对伸缩和可靠性带来了不利的影响。 第三个是扩缩容依赖,服务 A 需要扩充一倍,依赖的服务要扩充多少呢?这个往往很难评估准确,曾出现过由于扩充的比例不对导致现网紧急变更。 最后是分布式一致性,微服务之间往往需要互相配合来完成某个业务功能,但是由于网络故障或其它原因经常处理不一致。然后通过各种系统定时任务去清理或修正,系统越来越难以理解。

微服务架构实践方法

实施微服务需要避免踩的陷阱,简单提炼为:

1. 微服务拆分过细,过分强调 “small”。
2. 微服务基础设施不健全,忽略了 “automated”。
3. 微服务并不轻量级,规模大了后,“lightweight” 不再适应。

服务粒度

微服务拆分粒度的 “三个火枪手” 原则,即一个微服务三个人负责开发。当我们在实施微服务架构时,根据团队规模来划分微服务数量,如果业务规继续发展,团队规模扩大,我们再将已有的微服务进行拆分。“三个火枪手” 的原则主要应用于微服务设计和开发阶段,如果微服务经过一段时间发展后已经比较稳定,处于维护期了,无须太多的开发,那么平均 1 个人维护 1 个微服务甚至几个微服务都可以。

拆分方法

备注

一般微服务不建议按层来横向分,而是纵向按照业务功能或者业务域来分。

1. 基于业务逻辑拆分

备注

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

拆分方法:

根据前面介绍的 “三个火枪手” 的原则,计算一下大概的服务数量范围,
然后再确定合适的 “职责范围”,否则就可能出现划分过粗或者过细的情况。

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

2. 基于可扩展拆分

备注

将系统中的业务模块按照稳定性排序,将已经成熟和改动不大的服务拆分为稳定服务,将经常变化和迭代的服务拆分为变动服务。稳定的服务粒度可以粗一些,即使逻辑上没有强关联的服务,也可以放在同一个子系统中,例如将 “日志服务” 和 “升级服务” 放在同一个子系统中;不稳定的服务粒度可以细一些,但也不要太细,始终记住要控制服务的总数量。

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

3. 基于可靠性拆分

备注

将系统中的业务模块按照优先级排序,将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用。具体拆分的时候,核心服务可以是一个也可以是多个,只要最终的服务数量满足 “三个火枪手” 的原则就可以。

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

a. 避免非核心服务故障影响核心服务
    例如,日志上报一般都属于非核心服务,但是在某些场景下可能有大量的日志上报,
    如果系统没有拆分,那么日志上报可能导致核心服务故障;
    拆分后即使日志上报有问题,也不会影响核心服务。
b. 核心服务高可用方案可以更简单
    核心服务的功能逻辑更加简单,存储的数据可能更少,用到的组件也会更少,
    设计高可用方案大部分情况下要比不拆分简单很多。
c. 能够降低高可用成本
    将核心服务拆分出来后,核心服务占用的机器、带宽等资源比不拆分要少很多。
    因此,只针对核心服务做高可用方案,机器、带宽等成本比不拆分要节省较多。

4. 基于性能拆分

备注

基于性能拆分和基于可靠性拆分类似,将性能要求高或者性能压力大的模块拆分出来,避免性能压力大的服务影响其他服务。常见的拆分方式和具体的性能瓶颈有关,可以拆分 Web 服务、数据库、缓存等。例如电商的抢购,性能压力最大的是入口的排队功能,可以将排队功能独立为一个服务。

重要

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

5. 基于数据一致性拆分

实时一致性要求比较高的业务,尽量避免散落在太多服务中,增加分布式事务管理的复杂度。

基础设施

备注

大部分人主要关注的是微服务的 “small” 和 “lightweight” 特性,但实际上真正决定微服务成败的,恰恰是那个被大部分人都忽略的 “automated”。为何这样说呢?因为服务粒度即使划分不合理,实际落地后如果团队遇到麻烦,自然会想到拆服务或者合服务;如果 “automated” 相关的基础设施不健全,那微服务就是焦油坑,让研发、测试、运维陷入各种微服务陷阱中。

https://img.zhaoweiguo.com/knowledge/images/architectures/expandabilitys/microservice4.png

微服务基础设施

备注

虽然建设完善的微服务基础设施是一项庞大的工程,但也不用太过灰心,认为自己团队小或者公司规模不大就不能实施微服务了。第一个原因是已经有开源的微服务基础设施,涵盖了服务发现、服务路由、网关、配置中心等功能;第二个原因是如果微服务的数量并不是很多的话,并不是每个基础设施都是必须的。

基础设施搭建优先级:

1. 服务发现、服务路由、服务容错
    这是最基本的微服务基础设施。
2. 接口框架、API 网关
    主要是为了提升开发效率,
    接口框架是提升内部服务的开发效率,
    API 网关是为了提升与外部服务对接的效率。
3. 自动化部署、自动化测试、配置中心
    主要是为了提升测试和运维效率。
4. 服务监控、服务跟踪、服务安全
    主要是为了进一步提升运维效率。

备注

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

思考

微服务拆分,涉及到后台管理系统,是需要怎样处理?比如列表展示,甚至是夸库的操作:

1. 后台管理系统由程序聚合数据
2. 后台管理系统统一管理数据,然后推送给各个微服务

API 网关统一认证、限流,调用后面服务,服务间不存在互相调用:

这个算不上微服务,微服务是业务相关的,肯定有依赖关系和调用关系

配置中心主要作用由:

1. 避免手工修改,尤其是机器数量多了后
2. 配置自动校验,避免人工出错
3. 配置备份恢复,故障处理的时候很方便

微服务架构基础设施

1. 自动化测试

微服务将原本大一统的系统拆分为多个独立运行的 “微” 服务,微服务之间的接口数量大大增加,并且微服务提倡快速交付,版本周期短,版本更新频繁。

备注

如果每次更新都靠人工回归整个系统,则工作量大,效率低下,达不到 “快速交付” 的目的,因此必须通过自动化测试系统来完成绝大部分测试回归的工作。

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

2. 自动化部署

相比大一统的系统,微服务需要部署的节点增加了几倍甚至十几倍,微服务部署的频率也会大幅提升(例如,我们的业务系统 70% 的工作日都有部署操作),综合计算下来,微服务部署的次数是大一统系统部署次数的几十倍。这么大量的部署操作,如果继续采用人工手工处理,需要投入大量的人力,且容易出错,因此需要自动化部署的系统来完成部署操作。 自动化部署系统包括版本管理、资源管理(例如,机器管理、虚拟机管理)、部署操作、回退操作等功能。

3. 配置中心

微服务的节点数量非常多,通过人工登录每台机器手工修改,效率低,容易出错。特别是在部署或者排障时,需要快速增删改查配置,人工操作的方式显然是不行的。除此以外,有的运行期配置需要动态修改并且所有节点即时生效,人工操作是无法做到的。综合上面的分析,微服务需要一个统一的配置中心来管理所有微服务节点的配置。 配置中心包括配置版本管理(例如,同样的微服务,有 10 个节点是给移动用户服务的,有 20 个节点给联通用户服务的,配置项都一样,配置值不一样)、增删改查配置、节点管理、配置同步、配置推送等功能。

4. 接口框架

微服务提倡轻量级的通信方式,一般采用 HTTP/REST 或者 RPC 方式统一接口协议。但在实践过程中,光统一接口协议还不够,还需要统一接口传递的数据格式。例如,我们需要指定接口协议为 HTTP/REST,但这还不够,还需要指定 HTTP/REST 的数据格式采用 JSON,并且 JSON 的数据都遵循如下规范:

{
    "requestId": 10086,
    "time": "2021-07-15 00:00:00",
    "api": "get_money",
    "caller": "tencent",
    "param": {
        "uid": 1024
    },
    "sign": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

接口框架不是一个可运行的系统,一般以库或者包的形式提供给所有微服务调用。例如,针对上面的 JSON 样例,可以由某个基础技术团队提供多种不同语言的解析包(Java 包、Python 包、C 库等)。

5. API 网关

系统拆分为微服务后,内部的微服务之间是互联互通的,相互之间的访问都是点对点的。如果外部系统想调用系统的某个功能,也采取点对点的方式,则外部系统会非常 “头大”。因为在外部系统看来,它不需要也没办法理解这么多微服务的职责分工和边界,它只会关注它需要的能力,而不会关注这个能力应该由哪个微服务提供。 除此以外,外部系统访问系统还涉及安全和权限相关的限制,如果外部系统直接访问某个微服务,则意味着每个微服务都要自己实现安全和权限的功能,这样做不但工作量大,而且都是重复工作。 综合上面的分析,微服务需要一个统一的 API 网关,负责外部系统的访问操作。 API 网关是外部系统访问的接口,所有的外部系统接⼊系统都需要通过 API 网关,主要包括接入鉴权(是否允许接入)、权限控制(可以访问哪些功能)、传输加密、请求路由、流量控制等功能。

6. 服务发现

微服务种类和数量很多,如果这些信息全部通过手工配置的方式写入各个微服务节点,首先配置工作量很大,配置文件可能要配几百上千行,几十个节点加起来后配置项就是几万几十万行了,人工维护这么大数量的配置项是一项灾难;其次是微服务节点经常变化,可能是由于扩容导致节点增加,也可能是故障处理时隔离掉一部分节点,还可能是采用灰度升级,先将一部分节点升级到新版本,然后让新老版本同时运行。不管哪种情况,我们都希望节点的变化能够及时同步到所有其他依赖的微服务。如果采用手工配置,是不可能做到实时更改生效的。因此,需要一套服务发现的系统来支撑微服务的自动注册和发现。

备注

服务发现主要有两种实现方式:自理式和代理式。

a. 自理式

https://img.zhaoweiguo.com/knowledge/images/architectures/expandabilitys/microservice5-discovery1.png

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

备注

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

b. 代理式

https://img.zhaoweiguo.com/knowledge/images/architectures/expandabilitys/microservice5-discovery2.png

代理式结构就是指微服务之间有一个负载均衡系统

备注

代理式的方式看起来更加清晰,微服务本身的实现也简单了很多,但实际上这个方案风险较大。第一个风险是可用性风险,一旦 LOAD BALANCER 系统故障,就会影响所有微服务之间的调用;第二个风险是性能风险,所有的微服务之间的调用流量都要经过 LOAD BALANCER 系统,性能压力会随着微服务数量和流量增加而不断增加,最后成为性能瓶颈。因此 LOAD BALANCER 系统需要设计成集群的模式,但 LOAD BALANCER 集群的实现本身又增加了复杂性。

7. 服务路由

有了服务发现后,微服务之间能够方便地获取相关配置信息,但具体进行某次调用请求时,我们还需要从所有符合条件的可用微服务节点中挑选出一个具体的节点发起请求,这就是服务路由需要完成的功能。 服务路由和服务发现紧密相关,服务路由一般不会设计成一个独立运行的系统,通常情况下是和服务发现放在一起实现的。对于自理式服务发现,服务路由是微服务内部实现的;对于代理式服务发现,服务路由是由 LOAD BALANCER 系统实现的。

备注

无论放在哪里实现,服务路由核心的功能就是路由算法。常见的路由算法有:随机路由、轮询路由、最小压力路由、最小连接数路由等。

8. 服务容错

系统拆分为微服务后,单个微服务故障的概率变小,故障影响范围也减少,但是微服务的节点数量大大增加。从整体上来看,系统中某个微服务出故障的概率会大大增加。我在分析微服务陷阱时提到微服务具有故障扩散的特点,如果不及时处理故障,故障扩散开来就会导致看起来系统中很多服务节点都故障了,因此需要微服务能够自动应对这种出错场景,及时进行处理。否则,如果节点一故障就需要人工处理,投入人力大,处理速度慢;而一旦处理速度慢,则故障就很快扩散,所以我们需要服务容错的能力。

备注

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

9. 服务监控

系统拆分为微服务后,节点数量大大增加,导致需要监控的机器、网络、进程、接口调用数等监控对象的数量大大增加;同时,一旦发生故障,我们需要快速根据各类信息来定位故障。这两个目标如果靠人力去完成是不现实的。举个简单例子:我们收到用户投诉说业务有问题,如果此时采取人工的方式去搜集、分析信息,可能把几十个节点的日志打开一遍就需要十几分钟了,因此需要服务监控系统来完成微服务节点的监控。

服务监控的主要作用有:

1. 实时搜集信息并进行分析,避免故障后再来分析,减少了处理时间
2. 在实时分析的基础上进行预警,在问题萌芽的阶段发觉并预警,降低了问题影响的范围和时间

备注

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

10. 服务跟踪

服务监控可以做到微服务节点级的监控和信息收集,但如果我们需要跟踪某一个请求在微服务中的完整路径,服务监控是难以实现的。因为如果每个服务的完整请求链信息都实时发送给服务监控系统,数据量会大到无法处理。 服务监控和服务跟踪的区别可以简单概括为宏观和微观的区别。例如,A 服务通过 HTTP 协议请求 B 服务 10 次,B 通过 HTTP 返回 JSON 对象,服务监控会记录请求次数、响应时间平均值、响应时间最高值、错误码分布这些信息;而服务跟踪会记录其中某次请求的发起时间、响应时间、响应错误码、请求参数、返回的 JSON 对象等信息。

备注

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

11. 服务安全

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

备注

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

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

主页

索引

模块索引

搜索页面