微服务系列(一):从演进到全景

一、传统单体系统

在很多项目的业务初期阶段,高速迭代上线是首要考虑的事情,对后期的容量预估、可扩展性和系统健壮性、高可用一般没有那么重视。但随着业务的发展,用户量、请求量的暴增,发现原来的单体系统已经远远不满足需求了,特别是随着互联网整体的高速发展,对系统的要求越来越高。

但是物理服务器的CPU、内存、存储器、连接数等资源有限,单体系统能够承受的QPS也是有限的,某个时段大量连接同时执行操作,会导致web服务和数据库服务在处理上遇到性能瓶颈。

为了解决这个问题,前辈们发扬了分而治之的思想,对大数据库、大表进行分割,以便实施更好的控制和管理。同时创建多个服务实例,使用多台服务机进行CPU、内存、存储的分摊,提供更好的性能。

1.1 单体系统的问题

  1. 复杂性高:整个系统的模块耦合在一起,模块的边界比较模糊、依赖关系错综复杂。功能的调整,容易带来不可知的影响和潜在的bug风险。
  2. 服务性能问题:单体系统遇到性能瓶颈问题,只能横向扩展,增加服务实例,进行负载均衡分担压力。无法纵向扩展,做模块拆分。
  3. 扩缩容能力受限:单体应用只能作为一个整体进行扩展,影响范围大,无法根据业务模块的需要进行单个模块的伸缩。
  4. 无法做故障隔离:如果其中的某一个小的功能模块出现问题(如某个请求堵塞),那么都有可能会造成整个系统的崩溃。
  5. 发布的影响范围较大:每次发布都是整个系统进行发布,发布会导致整个系统的重启,对于大型的综合系统挑战比较大。

1.2 单体系统的优点

  1. 系统的简易性:系统语言风格、业务结构、接口格式均具有一致性,服务都是耦合在一起的,不存在各个业务通信问题。
  2. 易于测试:单体应用一旦部署,所有的服务或特性就都可以使用了,简化了测试过程,无需额外测试服务间的依赖。
  3. 易于部署与升级:只需将单个目录下的服务程序统一部署和升级。
  4. 较低的维护成本:只需维护单个系统即可。运维主要包括配置、部署、监控与告警和日志收集四大方面。

一句话总结:单体系统在早期开发效率高、维护简单,但随着业务增长,其耦合度高、扩展性差、故障隔离弱的问题会越来越突出。

二、微服务的核心特征

微服务是一种架构模式,是面向服务的体系结构(SOA)软件架构模式的一种演变,它提倡将单一应用程序划分成一组松散耦合的细粒度小型服务,辅助轻量级的协议,互相协调、互相配合,为用户提供最终价值。这些服务通常包含如下特点:

  • 单一职责:每个节点高度服务化,符合高内聚、低耦合原则以及单一职责原则的单元,包括数据库和数据模型;不同的服务通过"管道"的方式灵活组合,从而构建出庞大的系统。
  • 轻量级通信:通过REST API模式或者RPC框架,事件流和消息代理的组合相互通信,实现服务间互相协作的轻量级通信机制。
  • 独立性:每个服务都是独立的业务单元,与其他服务高度解耦,只需要改变当前服务本身,就可以完成独立的开发、测试、部署、运维。
  • 进程隔离:每个服务都是高度自治的独立业务实体,可以运行在独立的进程中,不同的服务能非常容易地部署到不同的主机上。进程的隔离,还能保证服务达到动态扩缩容的能力。
  • 混合技术栈和混合部署方式:团队可以为不同的服务组件使用不同的技术栈和不同的部署方式(公有云、私有云、混合云)。
  • 简化治理:组件可以彼此独立地进行扩缩容和治理,从而减少了因必须缩放整个应用程序而产生的浪费和成本。
  • 安全可靠,可维护:从架构上对运维提供友好的支撑,在安全、可维护的基础上规范化发布流程,支持数据存储容灾、业务模块隔离、访问权限控制、编码安全检测等。

三、微服务面临的挑战

3.1 分布式固有复杂性

微服务架构是基于分布式的系统,而构建分布式系统必然会带来额外的开销。

  • 性能:分布式系统是跨进程、跨网络的调用,受网络延迟和带宽的影响。
  • 可靠性:由于高度依赖于网络状况,任何一次的远程调用都有可能失败,随着服务的增多还会出现更多的潜在故障点。
  • 分布式通信:大大增加了功能实现的复杂度,并且伴随着定位难、调试难等问题。
  • 数据一致性:需要保证分布式系统的数据强一致性,即在 C(一致性)A(可用性)P(分区容错性)三者之间做出权衡。

3.2 服务的依赖管理和测试

在微服务架构中,服务数量众多,每个服务都是独立的业务单元,服务主要通过接口进行交互,如何保证它的正常,是测试面临的主要挑战。单元测试和单个服务链路的可用性非常重要。

3.3 有效的配置版本管理

分布式系统中需要统一进行配置管理,同一个服务在不同的场景下对配置的值要求还可能不一样,所以需要引入配置的版本管理、环境管理。

3.4 自动化的部署流程

每个服务都独立部署,交付周期短且频率高,人工部署已经无法适应业务的快速变化。有效地构建自动化部署体系,配合服务网格、容器技术,是微服务面临的另一个挑战。

3.5 对于DevOps更高的要求

开发者也将承担起整个服务的生命周期的责任,包括部署、链路追踪、监控;按需调整组织架构、构建全功能的团队,也是一个不小的挑战。

3.6 运维成本

微服务架构中,每个服务都需要独立地配置、部署、监控和收集日志,成本呈指数级增长。服务化粒度越细,运维成本越高。

一句话总结:微服务带来了灵活性和可扩展性,但也引入了分布式复杂性、运维成本飙升和组织协作等挑战,需要完善的基础设施来支撑。

四、微服务演进史

从2012年之后,微服务的发展有显著的发展趋势。

image_2_2.png

目前业内的微服务相关开发平台和框架还是比较多的,如 Spring Cloud Alibaba(Nacos + Sentinel + Dubbo)、阿里的 Dubbo、微软的 .NET 体系微服务框架 Service Fabric,以及进阶的服务网格(Service Mesh,如 Istio、Linkerd)。

下面的内容参考了 Phil Calcado 的文章 《Pattern: Service Mesh》,从开发者的视角,详细分析了从微服务到Service Mesh技术的演进过程。

4.1 第一阶:简单服务通信模块

最初开发人员想象的两个服务间简单的通信模式,两个服务之间直接进行通信:

image_2_3.png

4.2 第二阶:原始通信时代

实际情况远比想象的复杂很多,在TCP协议出现之前,服务需要自己处理网络通信所面临的丢包、错误、乱序、重试等一系列流控问题,因此服务实现中,除了业务逻辑外,还包含对网络传输问题的处理逻辑。

image_2_4.png

4.3 第三阶:TCP时代

TCP协议的出现,避免了每个服务自己实现一套相似的网络传输处理逻辑,解决网络传输中通用的流量控制问题。处理网络传输的能力下沉,从服务的实现中抽离出来,成为操作系统网络层的一部分。

image_2_5.png

4.4 第四阶:第一代微服务(Spring Cloud/RPC)

TCP出现之后,服务间的网络通信已经不是一个难题了,GFS/BigTable/MapReduce 为代表的分布式系统得到了蓬勃的发展。这时,分布式系统特有的通信语义又出现了,如服务注册与发现、负载均衡、熔断降级策略、认证和授权、端到端trace、日志与监控等。

image_2_6.png

4.5 第五阶:第二代微服务

为了避免每个服务都需要自己实现一套分布式系统通信的语义功能,一些面向微服务架构的通用开发框架出现了,如Twitter的 Finagle、Facebook的 Proxygen 以及Spring Cloud等。这些框架实现了分布式系统通信需要的各种通用语义功能,一定程度上屏蔽了通信细节。

image_2_7.png

4.6 第六阶:第一代Service Mesh

第二代微服务框架看着挺完美,但整套微服务框架其实是很复杂的。在实践过程中,会发现如下诸多问题:

  • 侵入性强:想要集成SDK的能力,除了需要添加相关依赖,业务层中入侵的代码、注解、配置,与治理层界限不清晰。
  • 升级成本高:每次升级都需要业务应用修改SDK版本,重新进行功能回归测试,并对每一台服务进行部署上线。
  • 版本碎片化严重:由于升级成本高,而中间件版本更新快,导致线上不同服务引用的SDK版本不统一、能力参差不齐。
  • 中间件演变困难:由于版本碎片化严重,导致中间件向前演进需要在代码中兼容各种各样的老版本逻辑。
  • 内容多、门槛高:依赖组件多,学习成本高,引入微服务框架并熟练使用需要花费巨大的精力。
  • 治理功能不全:诸如协议转换支持、多重授权机制、动态请求路由、故障注入、灰度发布等高级功能并没有覆盖到。
  • 无法实现真正意义上的语言无关性:提供的框架一般只支持一种或几种语言。

所以,第一代 Service Mesh 产生了。它作为一个基础设施层,能够与业务解耦,主要解决复杂网络拓扑下微服务与微服务之间的通信,其实现形态一般为轻量级网络代理,并与应用以边车代理(SideCar)模式部署,同时对业务应用透明。

image_2_8.png

SideCar将分布式服务的通信抽象为单独一层,需要和服务部署在一起,接管服务的流量,通过代理之间的通信间接完成服务之间的通信请求。在这一层中它能够实现负载均衡、服务发现、认证授权、监控追踪、流量控制等分布式系统所需要的功能。

image_2_9.png

从全局视角来看,绿色的为应用服务,蓝色的为SideCar,部署图如下:

image_2_10.png

省略去服务,只看Service Mesh的代理边车的网格:

image_2_11.png

流量经过的时候,会先被代理边车所劫持,然后再进入服务,所以它就是一个由若干服务代理所组成的错综复杂的网格。

4.7 第七阶:第二代Service Mesh

第一代Service Mesh由一系列独立运行的单机代理服务构成,为了提供统一的上层运维入口,演化出了集中式的控制面板,称之为控制面(control plane)。控制面和所有的数据面(data plane,即代理边车)进行交互,比如策略下发、数据采集等。这就是以Istio为代表的第二代Service Mesh。

2024+ 更新:Istio 经过多年发展已经非常成熟,成为 Service Mesh 领域事实上的标准方案;Linkerd 作为更轻量级的替代方案也有一定市场。此外,基于 eBPF 的方案(如 Cilium Service Mesh)正在成为新的趋势,它通过内核层面的流量拦截,避免了 Sidecar 带来的额外性能开销和运维复杂度。

image_2_12.png

只包含控制面和数据面的 Service Mesh 服务网格全局结构图如下:

image_2_13.png

Service Mesh 的基础设施层主要分为两部分:

控制平面的特点:

  • 不直接解析数据包。
  • 与数据平面中的代理通信,下发策略和配置。
  • 负责网络行为的可视化。
  • 通常提供 API 或者命令行工具可用于配置版本化管理,便于持续集成和部署。

数据平面的特点:

  • 通常是按照无状态目标设计的,但实际上为了提高流量转发性能,需要缓存一些数据。
  • 直接处理入站和出站数据包,转发、路由、健康检查、负载均衡、认证、鉴权、产生监控数据等。
  • 对应用来说透明,即可以做到无感知部署。

一句话总结:从简单通信到TCP、从第一代微服务框架到 Service Mesh,架构演进的核心趋势是将通信治理能力不断下沉,最终与业务逻辑彻底解耦。

五、微服务全景架构

image_1_1.png

微服务架构核心组件包括:

组件名
服务注册与发现
API 网关服务
分布式配置中心
服务通信
服务治理
服务监控
分布式服务追踪

5.1 服务注册与发现

原理图

image_1_2.png

服务注册与发现三要素:

  • Provider:服务的提供方
  • Consumer:调用远程服务的服务消费方
  • Registry:服务注册和发现的注册中心

注册中心的原理、流程

  1. Provider(服务提供者)绑定指定端口并启动服务
  2. 提供者连接注册中心,并发本机 IP、端口、应用信息和服务信息发送至注册中心存储
  3. Consumer(消费者),连接注册中心,并发送应用信息、所求服务信息至注册中心
  4. 注册中心根据消费者所求服务信息匹配对应的提供者列表发送至Consumer应用缓存
  5. Consumer 在发起远程调用时基于缓存的消费者列表择其一发起调用
  6. Provider 状态变更会实时通知注册中心、再由注册中心实时推送至Consumer
  7. 去中心化,双方不直接依赖注册中心,即使注册中心全部宕机短时间内也不会影响服务的调用(Consumer应用缓存中保留提供者列表)
  8. 服务提供者无状态,任意一台宕掉后,不影响使用

注册中心包含如下功能:服务注册和反注册、心跳监测与汇报、服务订阅、服务变更查询、集群部署、服务健康状态检测、服务状态变更通知等。

主流注册中心选型:国内生态首选 Nacos(阿里开源,同时支持 AP/CP 模式切换,兼具配置中心能力),国际生态首选 Consul(HashiCorp 出品,支持多数据中心和服务网格),云原生场景选 Etcd(Kubernetes 默认存储后端)。

Nacos Consul Etcd Zookeeper
CAP模型 AP/CP 可切换 CP CP CP
数据一致性算法 Distro+Raft Raft Raft ZAB
配置管理 内置 支持(KV Store) 支持(KV Store) 需第三方
多数据中心 支持 支持 不支持 不支持
Watch Long Polling + Push Long Polling Watch Watch
SpringCloud 支持 支持 支持 支持 支持

分布式系统中 CAP 模型三者不可兼得。由于网络分区不可避免,P 是必备的,意味着只能在 AP(可用性优先)和 CP(一致性优先)之间选择。Nacos 的优势在于可以根据业务场景灵活切换。

迁移建议:仍在使用 Eureka 的存量系统,建议逐步迁移到 Nacos。Spring Cloud 的服务治理抽象层使得切换注册中心对业务代码几乎无感,主要工作量在配置变更和运维体系适配。详见本系列第三篇。

5.2 API 网关服务

image_1_3.png

用户的请求经过统一的API网关来访问微服务里具体的服务颗粒,并且可能产生串联的链路服务调用。

主流 API 网关技术:

网关 特点 适用场景
Spring Cloud Gateway 基于 WebFlux 响应式架构,Spring 生态原生支持 Spring 技术栈首选
Apache APISIX 云原生、高性能,基于 Nginx/OpenResty,插件生态丰富 追求高性能和语言无关性
Kong 可扩展的 API Layer,插件生态丰富,企业级支持完善 企业级 API 管理

除了路由之外,API 网关服务还包含:认证和授权、重试、熔断、降级、负载均衡、日志、监控、链路追踪、灰度发布、ABTesting 等功能。各大云厂商也提供了云原生网关服务(如阿里云 MSE、AWS API Gateway),将网关能力与云基础设施深度集成。

迁移建议:仍在使用 Netflix Zuul 的系统,建议迁移到 Spring Cloud Gateway(同属 Spring 生态,迁移成本最低)。

5.3 配置中心

image_1_4.png

以携程开源配置中心Apollo系统的架构设计为例,从下往上分析:

  1. Config Service提供配置的读取、推送等功能,服务对象是Apollo客户端
  2. Admin Service提供配置的修改、发布等功能,服务对象是Apollo Portal(管理界面)
  3. Config Service和Admin Service都是多实例、无状态部署,所以需要将自己注册到Eureka中并保持心跳
  4. 在Eureka之上架了一层Meta Server用于封装Eureka的服务发现接口
  5. Client通过域名访问Meta Server获取Config Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Client侧做load balance、错误重试
  6. Portal通过域名访问Meta Server获取Admin Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Portal侧做load balance、错误重试
  7. 为了简化部署,实际上会把Config Service、Eureka和Meta Server三个逻辑角色部署在同一个JVM进程中

上面的架构体现了如下特点:

  • 高可用:配置服务为多实例部署,访问层保证 load balance、错误重试
  • 弱依赖:使用了Eureka来做配置中心的服务注册,如果出现问题或者网络出现问题的时候,服务可以依赖于它本身所缓存的配置来提供正常的服务

5.4 服务通信

分布式系统一般是由多个微服务颗粒组成的,微服务与微服务之间存在互相调用,甚至多个链路访问的情况,通信方式继承于SOA,包含同步与异步两种模式。

同步访问方式

  1. RPC 访问模式:Remote Procedure Call Protocol,远程过程调用协议。像调用本地函数一样,去调用一个远端服务。本质上是请求链的底层,维护同一个端口,进行socket通信。常见的RPC技术包含 gRPC、Dubbo、Thrift 等。

image_1_5.png

  1. REST 访问模式:通过一套统一风格的接口模式,为Web、iOS和Android等提供接口服务。

异步访问方式

消息中间件:RabbitMQ、Kafka、RocketMQ之类,对于实时性要求不那么严格的服务请求和计算。

5.5 服务治理

常见的服务治理手段有如下几种:

节点管理

服务调用失败时可能是服务提供者自身出现问题,也可能是网络发生故障,一般有两种处理手段:

  1. 注册中心主动摘除机制:要求服务提供者定时向注册中心汇报心跳,如果超时,就认为服务提供者出现问题,并将节点从服务列表中摘除。
  2. 服务消费者摘除机制:当服务提供者网络出现异常,服务消费者调用就会失败,如果持续错误就可以将它从服务提供者节点列表中移除。

负载均衡

常用的负载均衡算法:

  1. Random 随机算法:从可用的服务节点中随机选取一个节点,一般情况下是均匀的。
  2. Round Robin 轮询算法(加权重):按照固定的权重,对可用服务节点进行轮询。可以给性能较好的节点的权重调大些,充分发挥其性能优势。
  3. Least Conn 最少活跃调用算法:在服务消费者端动态维护着同每一个服务节点之间的连接数,选择连接数最小的节点发起调用。
  4. 一致性 Hash 算法:相同参数的请求总是发到同一服务节点。当某一个服务节点出现故障时,基于虚拟节点机制,平摊到其他节点上。

服务路由

通过一定的规则如条件表达式或者正则表达式来限定服务节点的选择范围。制定路由规则主要有两个原因:

  1. 灰度发布、多版本ABTesting的需求:功能逐步开放发布或者灰度测试的场景。
  2. 多机房就近访问的需求:一般可以通过 IP 段规则来控制访问,优先选择同一 IP 段的节点。

服务容错

常用的方式有以下几种:

  • FailOver 失败自动切换:调用失败或者超时后,自动从可用的服务节点列表中选择下一个节点重新发起调用。
  • FailBack 失败通知:调用失败或者超时后,不再重试,而是根据失败的详细信息,来决定后续的执行策略。
  • FailCache 失败缓存:调用失败或者超时后,不立即发起重试,而是隔一段时间后再次尝试发起调用。
  • FailFast 快速失败:调用一次失败后,不再重试。

服务治理的手段是从不同角度来确保服务调用的成功率。节点管理从服务节点健康状态角度考虑,负载均衡和服务路由从服务节点访问优先级角度考虑,服务容错从调用的健康状态角度考虑。

主流服务容错框架:Sentinel(阿里开源,提供流量控制、熔断降级、系统负载保护,国内生态首选)和 Resilience4j(轻量级、函数式风格,Spring Cloud 官方推荐)。Spring Cloud Circuit Breaker 提供了统一抽象层,底层可切换 Sentinel 或 Resilience4j。

迁移建议:仍在使用 Hystrix 的系统,建议迁移到 Sentinel(功能超集,迁移后能力更强)或 Resilience4j(更轻量,适合简单场景)。

5.6 服务监控

image_1_6.png

常见的监控报警技术有 ELK、InfluxData的TICK、Prometheus 等。

在分布式系统中,微服务一般都具有复杂的链路调用,对于链路之间的状态、服务可用性、调用情况的监控,需要一套完整的服务监控系统去保障。服务监控系统主要由如下几部分构成:

  1. 数据采集:包含性能指标信息、日志信息(一般是服务埋点日志或者sidecar的inbound、outbound信息)、端到端的Trace信息。
  2. 数据传输:采集上来的监控数据通过传输系统,或者使用消息中间件来异步传输,或者调用服务端接口推送监控数据,并把这些数据持久化到数据服务层中。
  3. 规则处理:制定一套规则,对于采集到的数据进行清理、计算、分级等,通过提前设置好的报警策略,来判断是否触发报警。
  4. 展示与告警:梳理完的数据可以进行查询展示、分级报警、分析趋势报表推送等。

5.7 服务追踪

服务追踪的原理主要包括两个关键点:

  1. Trace ID:当请求发送到分布式系统的入口端点时,服务跟踪框架为该请求创建一个唯一的跟踪标识,在分布式系统内部流转时始终保持传递该唯一标识。通过 Trace ID 的记录,就能将所有请求过程的日志关联起来。

  2. Span ID:当请求到达各个服务组件时,通过一个唯一标识来标记它的开始、具体过程以及结束。通过记录开始和结束 Span 的时间戳,就能统计出该 Span 的时间延迟,还可以包含事件名称、请求信息等元数据。

image_1_7.png

上图显示了Trace ID 和 Span ID 在链路中的传输过程,它把服务调用的一个时序结构给展现出来了。

常见的服务链路追踪技术有 Zipkin、Pinpoint、SkyWalking 等。

一句话总结:微服务全景架构围绕服务注册与发现、API网关、配置中心、服务通信、服务治理、监控与追踪等核心组件构建,每个组件解决分布式系统中的一类关键问题。

六、总结

image_2_1.png

微服务架构提倡将单一应用程序划分成一组松散耦合的细粒度小型服务,辅助轻量级的协议,互相协调、互相配合,实现高效的应用价值。从单体系统到微服务框架,再到 Service Mesh,架构演进的核心趋势是将通信治理能力不断下沉,最终与业务逻辑彻底解耦。

围绕微服务的核心模块 -- 服务注册与发现、API 网关服务、分布式配置中心、服务通信、服务治理、分布式服务追踪与监控等,从原理到实践,构成了完整的微服务全景图。


系列导航

加载导航中...

评论