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

目录

微服务的主要收益是围绕业务边界独立交付和扩展;服务发现、治理与可观测性用于承担拆分后的运行成本。本文先说明为何拆分,再梳理这些能力如何协作。

一、传统单体系统

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

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

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

1.1 单体系统的问题

  1. 复杂性高:整个系统的模块耦合在一起,模块的边界比较模糊、依赖关系错综复杂。功能的调整,容易带来不可知的影响和潜在的bug风险。
  2. 服务性能问题:单体既可以纵向增加单机资源,也可以横向复制实例;主要限制是部署单元较大,难以只扩展其中一个模块。模块拆分属于职责划分,不是纵向扩展的定义。
  3. 扩缩容能力受限:单体应用只能作为一个整体进行扩展,影响范围大,无法根据业务模块的需要进行单个模块的伸缩。
  4. 进程内隔离受限:如果其中的某一个小的功能模块出现问题(如某个请求堵塞),那么都有可能会造成整个系统的崩溃。
  5. 发布的影响范围较大:每次发布都是整个系统进行发布,每个实例需要更新,但多实例滚动发布可以避免整体同时重启,对于大型的综合系统挑战比较大。

1.2 单体系统的优点

  1. 系统的简易性:系统语言风格、业务结构、接口格式均具有一致性,服务都是耦合在一起的,减少了内部跨网络调用,仍需管理模块与外部依赖。
  2. 易于测试:单体应用一旦部署,所有的服务或特性就都可以使用了,简化了测试过程,仍需测试模块协作与数据库等外部依赖。
  3. 易于部署与升级:只需将单个目录下的服务程序统一部署和升级。
  4. 较低的维护成本:只需维护单个系统即可。运维主要包括配置、部署、监控与告警和日志收集四大方面。

二、微服务的核心特征

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

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

三、微服务面临的挑战

3.1 分布式固有复杂性

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

  • 性能:分布式系统是跨进程、跨网络的调用,受网络延迟和带宽的影响。
  • 可靠性:由于高度依赖于网络状况,任何一次的远程调用都有可能失败,随着服务的增多还会出现更多的潜在故障点。
  • 分布式通信:大大增加了功能实现的复杂度,并且伴随着定位难、调试难等问题。
  • 数据一致性:需要按操作定义一致性和失败语义;CAP 讨论网络分区条件下线性一致性与可用性的冲突,不能代替跨服务业务事务设计。

3.2 服务的依赖管理和测试

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

3.3 有效的配置版本管理

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

3.4 自动化的部署流程

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

3.5 对于DevOps更高的要求

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

3.6 运维成本

微服务架构中,每个服务都需要独立地配置、部署、监控和收集日志,运维和协调成本会增加,增幅取决于依赖关系和自动化程度。服务化粒度越细,运维成本越高。

四、微服务演进史

以下按通信治理能力放置的位置梳理设计思路,是便于理解的演进示意,不是所有项目必须经历的版本阶梯。

目前业内的微服务相关开发平台和框架还是比较多的,如 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。

服务网格不只有逐 Pod 的 Sidecar 形态。Cilium 将 eBPF 与 Envoy 等组件结合:eBPF 可以承担部分网络处理,L7 能力仍会使用代理,不能据此声称没有代理开销或运维成本。Cilium Service Mesh 文档

image_2_12.png

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

image_2_13.png

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

控制平面的特点:

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

数据平面的特点:

  • 通常是按照无状态目标设计的,但实际上为了提高流量转发性能,需要缓存一些数据。
  • 直接处理入站和出站数据包,转发、路由、健康检查、负载均衡、认证、鉴权、产生监控数据等。
  • 尽量减少应用改造,但完整链路追踪、业务身份与重试语义仍需要应用协作。

五、微服务全景架构

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 注册发现与配置管理并存,临时实例、持久实例和配置数据的实现不能混为一种协议
Consul 结合目录、健康检查与多数据中心能力,读一致性受查询模式影响
Kubernetes Service 通过平台的 Service、EndpointSlice 和 DNS 提供发现;应用通常不直接接入 etcd
ZooKeeper 提供会话、节点和 Watch 等协调能力,需要相应客户端构建服务发现
Eureka 使用实例注册、续约与客户端缓存;官方仍有版本发布,不能因早期 Eureka 2 项目停更就判断整个项目停维

具体版本与接入细节见本系列第三篇。维护状态可查 Eureka 官方发布记录,平台发现机制见 Kubernetes Service。

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),将网关能力与云基础设施深度集成。

网关迁移应核对过滤器、协议、超时和安全行为。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,远程过程调用。客户端通过桩代码等方式调用远端过程;它是一种交互方式,不是单一协议,也可以提供异步调用。常见的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 Connections 最少连接算法:在服务消费者端动态维护着同每一个服务节点之间的连接数,选择连接数最小的节点发起调用。
  4. 一致性 Hash 算法:相同参数的请求总是发到同一服务节点。当某一个服务节点出现故障时,基于虚拟节点机制,平摊到其他节点上。

服务路由

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

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

服务容错

常用的方式有以下几种:

  • FailOver 失败自动切换:调用失败或者超时后,自动从可用的服务节点列表中选择下一个节点重新发起调用。
  • Failback 失败重试:例如 Dubbo 会记录失败请求,在后台定时重试,需确认业务允许重复调用。
  • Failsafe 失败忽略:例如 Dubbo 会忽略异常,只适用于允许丢失该次结果的场景。
  • FailFast 快速失败:调用一次失败后,不再重试。

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

Sentinel 和 Resilience4j 提供不同的流控、熔断和隔离能力。框架抽象可以减少接入差异,但线程模型、异常统计与降级行为仍需验证。Dubbo 的容错名称和语义可对照官方说明。

替换 Hystrix 等既有组件时,应逐项映射线程池隔离、超时、中断、熔断和监控语义,不能把另一框架视为自动兼容的功能超集。

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 等。

六、总结

image_2_1.png

微服务架构提倡将单一应用程序划分成一组松散耦合的细粒度小型服务,辅助轻量级的协议,互相协调、互相配合,实现高效的应用价值。从单体系统到微服务框架,再到 Service Mesh,架构演进的核心趋势是将通信治理能力不断下沉,减少业务代码中的通用治理逻辑,同时保留明确的应用协作契约。

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

评论