Service Mesh

Service Mesh的基本模式

sidercar模式

在ServiceMesh发展早期,Service Mesh以Sidecar的形式存在。Sidercar模式可以看作是第一代ServiceMesh,代表有早期的Linkerd和Envoy。Sidercar模式下,网络代理服务在微服务旁边,为微服务通信和链路治理功能。因此数据平面代理服务也被称为sidercar。

此时,只有数据平面的网络代理服务没有控制平面,和外部基础设施服务的交互直接在网络代理服务中进行。

第一代Service Mesh通过采用Siadercar模式,将通信和通信链路从为服务中剥离了出来,实现了通讯基础设施的下沉和服务化,这里也体现了解耦的思想,通过解耦减少了为服务的负担。

第二代Service Mesh模式

Sidercar模式的ServiceMesh有一个突出的问题,将通信和链路治理的所有功能都放到这个代理服务中,导致数据平面代理繁重,并且由于承载了太多的特性和功能,使得数据平面代理的更新和修改特别频繁,频繁的更新和升级会导致代理服务出问题的概率增大,影响服务的稳定性。同时ServiceMes模式下,数据平面代理承载了为服务通信的全部流量,对稳定性要求极高,这个服务任何故障斗士队整个系统稳定性产生很大的影响。为了解决上述频繁升级和稳定性之间的矛盾,将策略和配置决策逻辑从代理服务中脱离出来,形成了独立的控制平面,这就是第二代Service Mesh。

第二代Service Mesh最重要的标志就是控制平面和数据平面的分离。数据平面和控制平面不是新的概念,路由器、交换机等通信产品架构上,就有云星宇专门处理器上的控制平面和多个独立运行,用于路由或交换功能的数据平面。SDN(Software Defined NetWork,软件定义网络)将数据平面和控制平面分离,控制平面具有可编程性,使得网络更加智能灵活和易扩展。

第二代Service Mesh借鉴了SDN的思路,基于控制平面和数据平面分离的思想,有了完善的控制平面:

  1. 所有的代理服务都由控制平面掌控,因为控制平面可以控制整个系统,所以提供了强大的控制能力和策略能力;
  2. 将具体的控制逻辑从数据平面中移除,简化了数据平面的设计,数据平面不需要和外部系统进行交互,数据平面完全聚焦在变更频率更低的流量路由和转发逻辑上,提升了数据平面的稳定性。

数据平面

数据平面负责代理微服务之间的通信,具体包含RPC通信、服务发现、负载均衡、降

级熔断、限流容错等,数据平面可以认为是将Spring Cloud、Dubbo等语言相关的微服务

框架中通信和服务治理能力独立出来的一个语言无关的进程,并且更注重通用性和扩展

性。在Service Mesh中,不再将数据平面代理视为一个个孤立的组件,而是将这些代理连

接在一起形成一个全局的分布式网络。

控制平面

控制平面负责对数据平面进行管理,定义服务发现、路由、流量控制、遥测统计等策

略,这些策略可以是全局的,也可以通过配置某个数据平面节点单独指定。控制平面通过

一定的机制将策略下发到各个数据平面节点,数据平面节点在通信时会使用这些策略。

微服务治理

基础

在单体服务时代并没有服务治理的概念,随着单个服务慢慢演变和拆分为众多微服务组成的系统,服务提供者与服务使用者之间如果没有明确的契约和规范,开发、测试和运维过程中会带来很大的沟通成本。之前线上服务遇到过的一个真实的例子,服务代码中调用地理围栏服务,这块代码平常没什么变更,大家关注也不多,线上服务调用地理围栏突然发生故障,服务使用方联系之前负责对接的人,发现早已离职,并且这个服务多年没有修改,当前处于无人维护状态,导致出问题时无法及时响应的悲剧。

服务治理就是解决服务提供者和服务使用者之间的链路通信与治理,主要包括如下几个维度的目标。

  1. 服务定义和SLA

服务定义是向外界说明当前服务提供者具体提供了哪些能力,具体通过什么方式来使用服务,以及服务对外承诺的服务SLA。

  1. 服务注册中心

服务使用者使用服务之前需要知道服务提供方具体在什么地方,服务注册中心负责管理各个服务的地址信息,用于服务使用者查询。

  1. 服务生命周期管理

服务生命周期管理用于服务部署的整个生命周期进行管理,包含服务的上线和下线,服务的扩容和缩容等。

  1. 服务通信链路治理

服务通信和链路治理是服务提供方和使用方约定,为了达成SLA而采用的一些治理手段,比如超时重试、限流降级等

  1. 服务授权和安全通信

通信安全严格来说也是属于链路通信和治理的范畴,只不过由于安全特别重要,可以将这块单独提出来进行讲述。

工作范畴

基于上述服务治理目标,服务治理的实际工作包括俩部分:一站式的服务治理平台、普适性的服务开发框架。其中一站式的服务治理平台作为服务治理输出的根据地,最重要的作用是服务治理规范的固化。服务治理规范经验到wiki、从wiki平台的发展趋势也表明了服务治理规范是业务方和服务治理团队之间一个非常重要的桥梁。普适性的服务开发框架,是服务治理团队非常重要的一个抓手,平台能够做的事情是有限的,而广泛使用的开发框架则可以完成更多的事情。普适性是服务开发框架最重要的优点,如何和业务方结合、提升服务开发框架的普适性非常重要

服务治理具体工作范围可以概括如下。

  1. 系统可见

主要是服务治理平台的工作,具体包含服务静态描述,比如服务基本细信息、服务等级、全局依赖拓步、路由关系、IDL(SDK)、权限、流量控制策略等,服务治理平台需要有能力帮助我们梳理依赖、判定服务等级。

服务实时拓扑,具体包括流量拓扑、报表审计等,服务治理平台需要有能力展示实时流量状态,基于场景定位线上问题。

  1. 系统可控

打通服务治理平台和服务开发框架,为其他平台输出能力,具体包括路由切换,降级预案等

  1. 系统可追溯

将问题线程进行录制,如流量录制、日志录制等方式,然后使用相应的跟踪和追溯系统,对典型问题场景下的常见问题进行回放和溯源,做到一次录制,多次回放。基于追溯和回放机制,可以解决根因定位、场景分析等问题。

  1. 系统可量化、可预测

系统可预测,是指预测业务接下来一段时间的行为,比如流量、容器、安全等,同时通过各种工具和平台检测系统的潜在风险。以容量规划为例,需要能够遇见和判断业务接下来一段时间的增加趋势和运营活动,判断可能的流量峰值对系统容量的影响;接下来通过全链路压测、单系统压测等方式探测系统可能的容量风险。

  1. 立足全局 业务优先

服务治理的目的是解决业务的稳定性和效率问题,必须完全聚焦业务,从业务出发,重点解决业务当前的痛点问题。


服务治理本质上来说是解决微服务化后产生的一系列挑战,比如效率治理、稳定性治理、效果治理、性能成本治理等。这些治理举措一般作用于设计开发阶段,我们可以称之为“正向治理”,在服务上线后,可以基于运行时的各种数据,进行综合治理和反馈,从而构成一个治理闭环。

正向服务治理

正向治理是通过相应的治理手段,解决微服务改造后带来的一系列问题,接下来围绕效率、稳定性和效果方面的治理工作展开讨论。

效率治理

效率包括的范围很广,具体包括开发效率、测试效率、运维效率等。开发效率一般通过采用微服务框架的方式解决,运维效率放到稳定性治理里面讨论,接下来主要讨论测试效率相关的治理,包括测试环境构造,以及测试数据的获取。

  • 基于流量的录制和回放的测试:微服务化架构下,微服务自身依赖的第三方服务、数据越来越多,给传统的测试方式带来很多困难,如被依赖的线下服务不稳定;服务无法提供期望的响应数据;缺少测试场景构造标准等。针对上述微服务测试的不足,尝试推出了流量录制和回放解决方案,通过一定的方式将线上的真实流量录制下来,在线下进行回放。线下测试时,基于线上的回放流量可以灵活地测试服务的各自功能,还可以基于回放流量进行编辑修改,对服务依赖的第三方请求进行mock和定制,使代码开发变得简单方便,且可随改随测,解决RD开发不便、自测流程过长的问题。
  • 基于仿真环境的测试:作为在线服务,为了满足灰度发布、测试等多维度需求,一般需要支持灵活的分流策略,将流量调度到不同的环境中。为了支持惊喜的灰度发布策略,需要根据一定的规则将线上流量分流的小流量集群,为了支持使用真实流量进行高仿真测试,还需要根据策略规则将一定流量引到仿真环境。仿真环境分流的特点是发往仿真环境的流量只是一份“影子”流量,相当于Oneway请求(不需要应答消息),对业务处理过程没有任何影响,还是和没有仿真环境的情况下一样处理。

稳定性治理

稳定性治理是一个对故障进行管理的过程。从故障管理的视角来看,可以分为故障预防、故障发现、故障定位、故障止损以及故障恢复5个阶段。

容器最有革命性的创新是镜像技术,它将应用程序、基础库和环境等封装在一起,作为微服务封装和运行的基石。

效果治理

微服务架构的初衷是建立一个方便快速迭代开发和演进的架构,帮助业务实现快速落地和试错,因此在微服务研发流程上,需要重点关注俩个事情:如何支撑业务快速落地,如何建立业务效果闭环反馈机制。

配置化

配置本质上是一个软件对外提供的开放的能力,能够在不改动软件逻辑的情况下按照用户的设置执行特定的流程和功能。业务系统虽然从功能特性上看迭代很快,但一旦业务模式确定后主流程是基本不变的,变的是针对各种业务场景的一些定制。可以把业务系统比作一个软件,如何能够将一些业务通用功能抽离出来,把差异做成配置,以配置化的方式支持可能的扩展和定制,就可以大大加快业务迭代速度。

为了实现配置化,首先需要有一个通用的配置平台,在配置平台UI上进行配置信息的修改和查询;同时需要有多语言的配置SDK库,支持获取最新的配置信息。在配置发生变更时,配置平台能够把变更后的配置信息推送到业务服务所在的集群上,供多语言的配置SDK使用。配置平台的实现还是很复杂的,如果对具体的实现原理和实现细节感兴趣,可以研究下携程开源的分布式配置中心项目,地址:https://github.com/ctripcorp/apollo,适用于微服务配置管理场景,完成度和成熟度比较高。

配置化的本质是对业务进行建模,把每个对象模型化,对系统进行抽象和建模,是代码的另外一种写法,需要业务架构层面对业务逻辑进行合理的抽象,识别出厂检的业务变化,针对变化进行建模。

基于A/B测试的业务效果评估体系

在互联网企业中,在线实验特别是A/B测试在产品创新和企业发展的过程中扮演着非常重要的角色。我们通常需要利用A/B测试来验证一些新特性或者调整,并以此来支撑我们在产品开发过程的决策,这样才能真正做到数据驱动的业务决策,因此基于A/B测试建立一套完善的业务效果评估体系,可以对每个变更的效果都可以度量。

效果评估需要先建立完善的业务指标体系,比如业务的一级和二级指标有哪些,有了具体的业务指标,效果评估才有基础和依据。其次,需要有一个针对指标的效果评估平台,真正做到业务效果可视化、可量化、可优化。

可见可观测

服务可见性

服务元数据平台负责维护服务的相关元数据信息,包括服务层面,接口层面,拓扑层面这几个维度的元数据信息,通过服务元数据平台,可以对系统提供全方位的可见性。

服务层面,服务元数据平台汇聚了当前所有的服务列表,默认按照服务功能进行分类,业务需要某种功能的服务时就可以直接到服务商城浏览和查询,做到服务信息的透明化,减少重复建设。为了增强服务查询的灵活性,可以支持多种服务查询的方式,比如按照服务所属部门查询、按照服务所属属性查询等服务基本信息平台化等。服务基本信息如下。

  1. 服务描述:简要描述服务提供的基本能力,服务的使用场景等,供潜在服务使用者参考,必要时可以加上服务详细描述信息wiki,以及服务对应的邮件组和沟通群组。
  2. 服务所有权:服务当前所属部门、服务当前的owner等。
  3. 服务对外接口:服务接口定义,使用说明注意事项。
  4. 服务SLA:服务对外SLA承诺。
  5. 服务上下游的拓扑:服务商城中维护了每个服务的上下游依赖,基于上下游依赖,不仅可以查询上下游服务的使用方式和使用情况,同时也可以进行服务重大变更时的上下游服务通知。
  6. 服务变更:服务商城中维护服务每个重要变更的变更日志,重要变更时会通过相应机制知会上下游依赖,上下游会评估是否需要适配升级等,这样服务使用者可以从变更历史中了解到服务的整个发展脉络。
  7. 服务接入和资源配额管理:服务如何接入,资源配额如何申请等。
  8. 服务线上部署和线下测试环境信息:描述了服务线上和线下的部署信息,使用者直接基于平台给定的环境使用。

SLA 全称为 Service Level Agreement,即服务水平协议,是指在客户与服务供应商之间签订的协议,其中规定了服务供应商应当提供的服务水平和质量标准。该协议通常会规定服务提供商需要满足的最低服务水平标准,例如服务可用性、故障响应时间、服务质量等等。
SLA 可以是一份独立的协议,也可以是某个大型合同的一部分。SLA 通常是管理客户与服务供应商之间关系的重要工具,通过 SLA 客户可以确保服务商会提供可靠、高质量的服务。

变更可见性

变更是引发系统故障的主要因素,通过对各个维度的变更进行系统的梳理和记录,不仅方便出现故障时的追溯和定位,后续还可以基于完善的变更事件库,对这些变更的原因、质量以及影响进行全面的审计和分析,从中找到规律性的东西,建立相应的改进反馈闭环。

服务变更是变更的最重要来源,常见的服务变更方式有应用变更、配置变更、数据变更以及预案变更等。对于微服务架构来说,除了关注当前服务的变更之外,还需要关注上下游依赖服务的变更,以及部署层面关联服务的变更,比如和当前服务混部在同一台物理机上的其他服务变更。

除了服务变更,还需要对服务周边的各个环境变更进行记录,比如网络变更、机器变更、机房变更、交换机变更,这些变更都可能对服务的正常运行产生影响。

可观测性

微服务架构下,各个微服务采用分布式部署,并且通过网络进行分布式通信,随着微服务个数和集群规模的扩大,系统中会产生各种形式的故障,并且很多故障是无法事先预测的,因此需要有完善的平台和工具体系,能够对微服务的运行状态进行全方位多维度的监控,使得我们可以随时掌握微服务当前的运行状态,并且针对运行状态不正常的场景进行及时处理。

微服务架构下,各个微服务采用分布式部署,并且通过网络进行分布式通信,随着微服务个数和集群规模的扩大,系统中会产生各种形式的故障,并且很多故障是无法事先预测的,因此需要有完善的平台和工具体系,能够对微服务的运行状态进行全方位多维度的监控,使得我们可以随时掌握微服务当前的运行状态,并且针对运行状态不正常的场景进行及时处理。

Logging系统

日志用于对系统中离散的事件进行记录,比如服务的调试信息和错误信息,日志是系统监控的基石,也是服务状态监控和问题诊断的第一个抓手,通过日志可以大体判断系统的运行状态是否正常。

日志是最常见、最通用的监控手段,但服务的日志监控和告警一般都需要人工添加,不仅效率低,也很容易遗漏。同时不同服务的日志格式和日志信息可能都会有差异,不太方便进行标准化,不仅日志收集、处理和展示比较麻烦,有太多个性化的需求,而且每个服务的日志都不统一,基于日志的全系统问题定位也非常麻烦。

Metric系统

为了提高日志和监控的标准化,引入了Metric的概念,Metric就是将日志中可以聚合的部分通过标准化的协议进行处理,Metric定义一套完善的日志收集、传输和处理标准,通过Metric可以实现日志和监控的标准化,同时基于Metric的日志聚合特性,聚合后的日志会小很多,减少日志系统的成本开销。

Tracing系统

Tracing用于记录请求级别的信息,它会跟踪整个链路的执行过程和各阶段耗时信息,基于Tracing,可以定位请求性能问题和跨服务交互相关问题。

量化分析体系

稳定性风险度量

稳定性工作包含的内容很多,在稳定性建设过程中,经常会遇到这样一个问题——服务当前的稳定性现状,不太好衡量和判断。比如,服务变更一直以来是影响服务稳定性的重要因素,由于没有明确的衡量标准和规范,很难说清楚服务变更做到什么程度才算是符合稳定性要求的变更,这样会导致执行服务变更的同学很难发现自己平常工作的不足,同时团队的管理者层面看不到自己团队变更风险的全貌和严重程度。服务变更只是稳定性建设的一个缩影,稳定性建设的很多维度和服务变更类似。

为了掌握稳定性建设的真实情况,同时引导和规范业务同学在稳定性建设时的做法,针对稳定性的一些重要环节,需要制订一定的度量标准,对业务同学日常的稳定性建设进行度量,明确告诉大家当前的稳定性工作处于什么水准,具体哪些地方需要改进。

针对稳定性建设的其他方面,我们也可以按照上述变更信用分的思路,建立相应的度量标准。比如监控告警,针对基础监控是否有遗漏配置、上下游依赖健康是否完备、告警策略是否符合要求等情况,可以推出监控健康分,量化服务监控告警的完备情况,引导用户进行监控告警的完善工作。比如预案建设方面,可以推出预案健康分,量化一个服务的预案建设情况,涉及降级限流等预案是否完备,预案执行是否符合灰度要求,预案是否可以回滚等。

基于对维度监控的故障定位

线上服务出现问题时,当有足够多的监控信息时,才能直观地定位问题。但随着业务规模变大,微服务的个数越来越多,链路、拓扑、网络越来越复杂,相应的监控事件越来越多。当出现故障,可能瞬间出现大量的报警信息,从众多告警中快速找到故障原因,确定相应的止损预案,是一个非常重要且有挑战性的事情。

出现故障时,首先需要确定故障的影响面,可以基于场景和分布式跟踪拓扑将业务组=织成一个全局的“灭火图”,灭火图中包括所有核心服务的可用性指标,比如错误率、QPS、耗时等,出现故障时,先从灭火图中看出故障影响的业务和服务,接下来确定故障定位的范围。

为了从纷繁复杂的众多事件中定位具体的原因,可以将各维度的监控报警、各种变更事件以事件的方式,按照时间轴整合成一个时间线,有了事件时间线,我们就可以将关注焦点放到故障时间前一段时间内的监控告警事件以及变更事件上,从而根据具体的事件类型确定相应的预案和止损措施。

风险分析

基于线上实时的可观测数据,以及研发全流程的变更和操作数据,我们可以得出很多维度的报表和趋势数据。这些维度可以涵盖服务治理的各个环节,比如链路SLA、超时重试、容量管理、强弱依赖关系等,这些数据可以作为接下来分析的基础。

同时,根据之前的风险分析以及一些静态的服务元数据信息,会形成一个和当前实时治理数据对应的历史基准库,将当前数据和历史基准库进行比较,从中找到趋势和规律,进而发现潜在的风险。

实时治理数据分析后一些有价值的东西可以沉淀到历史基准库,作为后续风险分析的基础,进而形成一个风险分析的闭环机制。在基于风险分析的在线上治理和线下治理中,会结合具体的场景和实例进行全面的剖析。

线上治理

线上治理是根据量化分析的结果,通过相应的预案对线上服务的运行状况进行调整,保证线上服务正常运行,接下来讨论线上服务常见的预案,以及如何保证预案的自动触发和自动调整。

线上预案体系

故障快速定位和止损的理想处理方式是将故障定位和预案执行打通,当出现故障时,能够判断出故障的大小类型以及对应的预案,并触发预案的自动执行。

当然实际的故障处理过程中有很多地方需要考虑,虽然并不是所有故障都能提前建立相应的预案,但我们可以根据历史故障和一些先验知识将故障进行归类,建立相应的预案。另外,建立预案时应尽量方便执行和触发,如果不方便执行,很难短时间内处理故障,最关键的问题是判断预案触发的时机,以及当前是否应该执行预案。

基于Metric的预案自动触发

对于降级、限流和冗余切流这几类比较明确的场景,可以基于Metric进行故障判断,并且和预案自动打通,以依赖服务故障为例,可以根据Metric成功率指标同环比变化,判断依赖服务是否有异常,如果通过Metric发现当前确实有异常,先查询变更管理平台,依赖服务当前是否有相关的变更操作。如果有变更,建议依赖服务接口人立即进行变更回滚操作;如果没有变更操作,再判断当前对依赖服务的调用是强依赖还是弱依赖,如果是弱依赖,可以启动自动降级预案对依赖服务进行降级,如果是强依赖,降级肯定不能解决问题,可以通过预先制定好的冗余切换预案,启动服务级、集群级或者机房级别的流量切换。

基于Metric的场景和预案打通,目标是朝着故障定位自动化和智能化的方向演进,但需要根据实际情况逐步推进,对于一些不太容易判断的场景,建议谨慎操作,避免可能的误判,同时要定期对预案进行演练,保障预案触发的有效性。

治理参数动态调整

限流降级和切流是服务治理的3个利器,分别解决上游、下游和服务自身故障问题。这些服务治理特性和实现本身并不复杂,使用时的关键和难点在于触发的具体时机,比如什么时候启动限流、什么情况下启动降级等。

思路如下

(1)带反馈机制的动态限流

设计思路:任何接口在极限流量下,只要超出处理能力,就一定会出现接口超时,但因为有突发流量的控制,对外吞吐能力是稳定的。只要超时率超过一定比例,就启动阈值的上升和下降,控制进入系统的请求包数量。

(2)CPU限制

为了控制程序对CPU的使用,留给其他系统进程(如各种Agent),同时为了防止通过流量阈值一次进入太多的请求,我们控制最多起40个Golang协程用于处理CPU消耗型的接口(这也是我们的突发限流)。其他请求列队等待。这里面需要注意的是,出队时要注意时间,如果请求已经超时需要直接抛弃,但这个时间不需要很长,同时需要结合SLA,考虑在秒级平缓瞬时请求流量。

(3)过载保护

过载保护,即实际采用方案的连接数超过一定阈值就主动拒绝请求。这很容易理解,但里面有个小细节是拒绝的方式:不能简单地忽略请求,而应该快速断开连接或者回复服务器当前太忙消息给调用方。

线下治理

线下治理是根据量化分析结果,对稳定性治理、架构治理等进行调整。下面从链路治理和架构治理相关的实例出发,看看如何通过量化分析指导线下治理工作。

链路稳定性治理

链路风险分析

链路风险分析就是从链路通信的历史数据出发,分析出链路当前存在的风险,减少链路通信的隐患,提高系统的整体稳定性。链路风险分析可以解决的问题域很多,如超时时间设置是否合理、重试次数设置是否合理、服务SLA指标设置是否合理、服务的强弱依赖关系是否符合预期等,服务通信相关的一大部分故障是由于链路风险导致,可以通过链路风险分析的方式提前发现和解决,避免故障的发生。链路风险分析的基础是链路实时拓扑图和Metric指标,下面会针对几种典型的链路风险,进行详细的分析。

超时和SLA风险

客户端访问服务端的超时时间配置,和实际访问情况不符,是非常常见的一种链路风险。上游服务的超时配置过小,会导致一些本来可以正常返回的请求超时,影响服务的SLA和正常的服务体验;超时配置过大,会导致下游服务故障时上游服务超时等待的时间太长,严重时会把整个系统拖垮。因此,超时设置直接关系到系统的稳定性,需要有相应的机制指导服务的超时设置,并及时发现线上系统中的超时配置隐患。

强弱依赖或重试风险

微服务之间通信,如果链路失败会导致整个请求处理失败,一般将这俩个微服务之间的关系称之为强依赖,反之称之为弱依赖。我们可以给予服务的强弱依赖关系,进行降级、熔断等处理。

强依赖风险缝隙可以采用故障注入的思路,但与直线在线上系统进行故障注入和演练不同,为了不影响线上服务的稳定性,可以听你刚刚线下环境进行强弱依赖风险检测。具体思路是采用故障注入配合自动化测试,通过service mesh获取到系统的拓扑关系图,自然就可以得到所有的调用链路以及具体的链路关系,这个可以称之为基准链路关系表。针对每个调用链路,通过故障注入的方式让链路调用返回失败,构造一个测试用例,在该测试用例基础上执行正常的自动化测试。如果自动化测试成功,证明该链路失败对整个流程没有影响,说明该链路是强依赖,反之是弱依赖。通过这种方式,可以判断出每个链路最新的链路关系,然后和基准链路关系进行比较,如果有差异,说明链路关系发生了变化,产生了新的链路依赖风险。

集群拓扑风险

集群或拓扑风险是风险分析的一个主要来源,之前的工作中遇到过不少集群或拓扑风险的真实案例。比如,某线上集群的一些机器因为保修临时下线一段时间,但机器修复好之后遗漏挂载,导致部分机器白白闲置;服务A调用服务B本来是同机房调用,由于故障或者流量切换演练等原因临时将调用关系切换到调用其他机房的服务B服务,但事后没有切回来,导致服务A调用服务B一直是跨机房访问,影响用户体验和系统稳定性;某服务S线上部署时没有考虑到地理位置因素,将过多的服务节点部署到同一个交换机下,交换机故障时导致服务同时有多个节点不可用,可用节点数不足导致服务雪崩。

集群部署风险或者拓扑风险没有通用的解决思路,需要针对具体的风险增加相应的风险应对措施,以上述列举的服务下的多个节点部署在同一个交换机下为例,一般在同一个交换机下的机器,IP网段信息差不多,可以根据类似的信息,通过程序定期扫描所有的线上集群,根据集群节点的地址信息,判断是否存在拓扑部署风险。

链路调用风险

链路实时拓扑数据是一个宝藏,可以从中慢慢挖掘出链路调用层面的很多风险。比如,当前服务调用20个下游服务,删除过大,不符合微服务的设计准则,可以考虑是否有必要进行进一步的拆分。

微服务架构中,单个请求的链路特别长时,会带来一定的性能问题,因此可以从全局链路拓扑中将TOP10长的链路,或者链路深度超过6的链路列出来,反馈给业务人员,看是否有必要进行架构层面的调整。

微服务拆分和设计过程中,建议不要出现两个微服务相互依赖的场景,可以通过链路拓扑图查找当前是否存在成环的链路,如果成环,说明服务之间产生了相互依赖,可以将类似风险反馈给业务人员进行整改。

扇入指该模块在结构图中的直接上层模块数,扇出是该模块在结构图的直接下层模块数。

善如越多说明模块的复用率越高,扇出越多说明结构复杂。

较良好的软件结构通常是 顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入到公共模块中。

我架构与资源治理

业务迭代过程中,可能会出现很多临时性的调用关系,调用关系使用一段时间后不再使用,长时间积累下去,可能会导致服务存在不少冗余代码,影响服务的可维护性。我们可以通过实时拓扑图和静态的基准拓扑图进行比较,如果出现基准拓扑图中存在,而实时拓扑不存在的服务调用链路,说明当前链路不再使用,基于实时拓扑和基准拓扑的差异比较,可以考虑将这种冗余调用代码去除。

为了保证变更的质量,服务在新增特性时通常会给它配置相应的特性开关,通过开关来区别不同版本的代码,随着架构的迭代升级,可能会导致不少版本的代码和分支不再使用,我们可以采用和上述冗余链路检测类似的方式。当检测到特性开关判断总是返回特定值时,说明其他分支当前已经不再使用,可以提示相关人员,这类无用代码是否可以移除。

我们也可以从资源调用和使用报表中发现当前服务资源是否有使用不当的地方,比如缓存资源遗漏设置过期时间,缓存资源设置的过期时间过大等;如果存储资源出现过大的对象,或者长期不再使用的数据,也可以反馈给业务方相关人员,以确定使用上是否需要进行调整。

服务治理的演进

远程Proxy方式

微服务下的服务框架基本解决了微服务开发和测试的效率问题,业务人员可以把精力聚焦投入到业务需求上面。多语言服务化背景下,服务治理的基础设施的每一次改动和升级,需要多语言框架的同时升级,这里多语言服务框架的开发、测试和维护的开销有很大的工作量。

由于服务框架以Lib的形式存在,和业务代码存在于一个服务里面,会导致做业务需求的业务人员和负责服务治理的基础设施人员之间仍然会有很多沟通和交互,随着组织和团队的扩大,受康威定律的影响,这个沟通成本会变得不可控。

如何减少多语言接入场景下服务治理基础设施的维护开销,同时减少服务治理基础设施升级对业务的影响,尽量将服务治理和业务解耦,这是微服务架构面前的一个课题。

客户端和服务端通信,客户端和服务端都需要有不少服务治理相关的考虑,比如客户端访问服务端时需要考虑服务发现、流量路由、负载均衡、调用熔断、请求降级、mock支持、请求跟踪等诸多特性,服务端也需要考虑限流、降级、安全、统计等。随着业务迭代发展,客户端和服务端需要支持的服务治理特性越来越多,这会影响业务迭代效率。架构设计上有个不成文的说法,任何软件工程遇到的问题都可以通过增加一个中间层来解决。按照这个说法,可以很自然地想到,微服务架构下,是否可以在客户端和服务端中间增加一个中间层,避免两者直接通信,同时把服务治理相关功能从客户端和服务端代码解耦,移到中间层来,这样服务治理特性的变化收敛在中间代理层,对客户端和服务端透明。

客户端和服务端通信,客户端和服务端都需要有不少服务治理相关的考虑,比如客户端访问服务端时需要考虑服务发现、流量路由、负载均衡、调用熔断、请求降级、mock支持、请求跟踪等诸多特性,服务端也需要考虑限流、降级、安全、统计等。随着业务迭代发展,客户端和服务端需要支持的服务治理特性越来越多,这会影响业务迭代效率。架构设计上有个不成文的说法,任何软件工程遇到的问题都可以通过增加一个中间层来解决。按照这个说法,可以很自然地想到,微服务架构下,是否可以在客户端和服务端中间增加一个中间层,避免两者直接通信,同时把服务治理相关功能从客户端和服务端代码解耦,移到中间层来,这样服务治理特性的变化收敛在中间代理层,对客户端和服务端透明。

PAI网关就是用来解决上述微服务治理的效率问题。API网关封装了服务访问和服务治理相关的具体逻辑,客户端只需要使用简单的访问方式,统一访问API网关,由API网关来代理对后端服务的访问,同时由于服务治理特性统一放到API网关上面,服务治理特性的变更可以做到对客户端的透明,一定程度上实现了服务治理等基础特性和业务服务的解耦,服务治理特性的升级也比较容易实现。

为了简化客户端的访问方式,对调用方屏蔽集群的复杂度,API网关一般会提供一个VIP,调用方直接使用VIP进行访问,由负载均衡设备负责VIP到API网关地址的映射。

API网关和直接使用服务框架相比,优点是业务使用起来很简单,没有什么入门成本,非常容易上手,对业务来说,简单和效率往往是第一位的;同时API网关也可以屏蔽多语言调用方使用方式的差异,避免了微服务框架语言级别的限制,多语言调用方均可以使用简单一致的调用方式访问后端服务,基本解决了多语言的服务治理问题。

当然API网关在提供便捷的服务访问和服务治理能力的同时,相应地也会有一些问题。

首先,引入API网关,通信层面会增加一跳;如果是采用简单易用的VIP访问方式,还需要加上用于VIP解析的负载均衡服务这一跳,所以通信层面上比原来直接访问后端服务增加了二跳,性能上肯定会有一定的损耗。

其次,通信链路上多一跳就会多一个故障点,会对系统的整体稳定性和可用性有一定的影响。

最后,API网关简单易用的同时,灵活性和定制化支持不太好,很难支持复杂的服务治理定制化需求,比如服务如果想支持动态路由切换,API网关支持起来就有点力不从心。

因此,在业务相对简单,或业务线上部署结构不太复杂时,使用API网关是一个很好的选择,可以简化微服务的开发和运维,提高业务迭代的效率。但业务如果发展到比较复杂时,比如生产环境有多个机房,或者同一个机房内部有全流量环境、小流量环境等多套环境,就会有比较多的路由策略需求和动态路由切换需求,这种情况下就需要探索服务层面是否可以有一种对效率和扩展性更好的平衡方式。

基于客户端的服务框架

和远程Proxy不同,基于智能客户端的服务框架采用和服务端直连的方式,客户端和服务端直接通信,中间不经过任何节点,不仅性能提升,也增强了稳定性。服务框架需要支持完善的流量调度和容错设计,同时需要支持常见的服务治理策略,对技术的要求相对较高,对于中小公司来说,开发或维护一款完善的服务框架的开销都是不小的。

除了开发维护成本高之外,服务框架作为Lib和客户端服务绑定在一起,因此是语言相关的,所以每个语言均需要相应的服务框架,这会带来很高的多语言服务治理成本。

另外,由于和客户端服务绑定在一起部署,服务框架的迭代升级相对麻烦,当服务框架进行升级时,使用框架的服务均需要进行升级,对于广泛使用的服务框架来说,这是笔不小的开销,框架和微服务的绑定和耦合,会影响框架自身的迭代和创新。(SpringCloud?)

本地proxy

API网关把服务治理相关特性从服务框架中解耦出来,可以提高效率,但灵活性和扩展性稍差;服务框架可以自如地支持各种服务治理需求,但多语言服务治理支持方面会有很大的开销,同时服务治理特性的升级和维护比较困难。因此,是否有一种机制或方式,能兼顾API网关和服务框架各自的优点,在提高业务开发效率的同时,也能很好地支持微服务治理的扩展性和定制化需求,本地网关应运而生。

本地网关又称为Sidercar,通过在每个服务实例对应的本地机器上实现服务治理功能的代理,来实现服务治理能力的快速落地。最有代表性的微服务中间件prana,初衷是将Netflix OSS微服务治理套件的能力,通过HTTP API接口的方,赋能给非java语言使用,以很小的开销获得Netflix OSS体系强大的服务治理能力。收到NetflixPrana的启发,SpringCloud也推出了SpringCloudNetflix Sidercar。

和API网关相比,本地网关的访问方式更简单。

  1. 实现给本地网关绑定固定的端口号,业务服务通过localhost:本地端口号的方式即可访问,虽然和直接访问服务端相比多了一跳,由于是本地访问,API网关性能损失要更小。
  2. 由于本地网关每个机器上都会有部署,因此API网关单个节点故障对系统稳定性的影响相对比较小。
  3. 本地网关和服务部署在一起,具有本地部署环境的感知能力,方便一些业务或者环境相关的服务治理特性的落地,比如机房级别的动态路由调整等。

从效率和扩展性上看,本地网关和API网关相比,都有着不少优势。那么是否意味着本地网关就是一个没有问题的完美方案呢,其实本地网关最大的问题是运维上的复杂度比较高,本地网关节点很多,并且业务服务对本地网关也是强依赖,因此需要对本地网关的部署、监控、升级、高可用保障等,都有一套完善的机制来保障。由于本地网关对运维的要求比较高,需要有一套完善的运维工具体系支撑,之前很长一段时间,这种模式在一些互联网公司中得到采用,如Netfilx、Airbnb等,并没有大规模应用起来

ServiceMesh发展历程

linkerd

介绍Linkerd之前先介绍一下和Linkerd渊源颇深的Fingle。Fingle是Twitter开源的支持高并发、高可用的服务治理框架,Finagle除了支持广泛应用的基于请求/答复的RPC协议之外,还集成了如Thrift、Redis、Memcached、MySQL等多种协议,Finagle聚焦统一的、协议无关的服务集成和服务治理功能集成。Fingle经过长期的生产环境验证,已经非常成熟稳定,在JVM系得到广泛应用,基于Finagle可以方便快捷地搭建微服务。

Linkerd作为下一代云应用的基础网络层,通过自动化负载均衡、服务发现和运行时恢复能力等诸多特性,使得企业能够在不牺牲可靠性的情况下将其计算架构平滑地从单体服务转移到微服务架构,Linkerd提供如下主要特性。

  1. 高性能、高扩展性的集群通信能力。每秒以最小的时延及负载处理万级数量的请求,Linkerd 2.0的数据平面当前大小仅为10MB左右,并且99.9%的请求耗时小于1ms;部署方便,业务代码和配置不需要任何修改,特别方便水平扩展。
  2. 支持多种服务发现方式,并且扩展非常方便。支持各种服务发现机制,如基于文件(File-based)、ZooKeeper、Consul及Kubernetes;增加自定义的服务发现机制比较方便,扩展性强。
  3. 强大的负载均衡算法支持。推荐使用基于感知时延的负载均衡算法,通过实时统计数据RPC延迟、要处理请求队列大小决定如何分发请求,反馈实时性强,能够根据当前的实时压力情况调整负载;内置多种负载均衡算法,可以根据实际场景灵活选用。
  4. 完善的多协议支持。支持HTTP 2.0、HTTP 1.1、gRPC、Thrift多种协议。
  5. 动态路由。针对HTTP协议,支持多种路由策略;支持动态路由机制,可以通过动态修改路由规则实现蓝绿部署、金丝雀部署、流量迁移等;
  6. 内置完善的log/Metric/Trace。Linkerd通过收集服务间通信的各种统计数据,构建起强大的仪表盘,具体包含每个服务当前的运行状态,请求的成功率,系统的实时拓扑等。
  7. 多平台支持。Linkerd通过收集服务间通信的各种统计数据,构建起强大的仪表盘,具体包含每个服务当前的运行状态,请求的成功率,系统的实时拓扑等。

Linkerd诞生后,以其简单易用的使用方式和强大的服务治理能力,一段时间内成为Service Mesh技术领域的旗帜和标杆,当然Linkerd自身也有不少缺点,其中最突出的问题就是:Linkerd的部署模型过重,设计过程中很少考虑有限资源情况下基于Sidecar模式的Kubernetes部署方式,制约了Linkerd的进一步发展;加上面对后起之秀Lstio/Envoy强大的竞争压力,Buoyant给出的答案是利用Linkerd已经超过18个月的生产级Service Mesh经验,快速推出功能完备又可以在低资源环境下运行的Service Mesh,为了不给Linkerd生产环境下的众多用户带来困扰,Buoyant决定启动一个和Linkerd并列的全新项目,这就是Conduit。

Conduit的目标是成为最快、最轻、最简单并且最安全的Service Mesh。Conduit使用Rust构建了快速、安全的数据平面,用Go开发了简单强大的控制平面,总体设计围绕着性能、安全性和可用性进行。同时与Linkerd支持Kubernetes、Mesos等多个调度平台不同,Conduit更为聚焦,只提供对Kubernetes的支持。

当前,Linkerd项目以Linkerd 1.x和Linkerd 2.x的方式存在,并行开发,Linkerd 1.x相当于Conduit诞生之前的Linkerd;Linkerd 2.x继续承接类似Conduit的角色,只不过换了个名字。Buoyant正在和社区制定相应的迁移路线图,逐步将Linkerd 1.x的存量用户迁移到Linkerd 2.x。

Conduit是为了对抗Istio而诞生,为了构建和Istio相抗衡的Service Mesh生态体系,Conduit退出舞台也标志着Buoyant退出了Service Mesh生态的想法,后续Service Mesh生态只有Istio一家独大了。后续Linkerd的定位会朝Service Ops方向发展,提供Service Mesh生态体系中一些具体的解决方案。

Linkerd 2.0推出了一个重要的功能,就是Linkerd以“Service Sidecar”的方式独立部署,无须集群范围的安装,即可增强单个服务的集群管理和服务治理功能,并且整个过程对服务没有任何侵入。

从当前看,Linkerd后续可能会两条路线并存。一条路线是提供包含数据平面和控制平面在内的全套轻量级Service Mesh解决方案,这个过程中不做平台,不再强调可扩展性,直接提供用户需要的功能,解决用户的痛点问题,并且特别轻量,不需要用户过多地选择,卖点就是轻量;另一条路线是融入Istio生态体系中,仅提供数据平面。


路由机制

路由机制(见图3-2)是Linkerd的核心,Linkerd的主要工作就是收到一个请求消息,然后将该请求消息转发到合适的目的节点,这个过程中主要由服务识别(Identification)、绑定(Binding)、解析(Resolution)和负载均衡4个主要步骤组成,其中识别、绑定、解析属于路由管理的范畴。

识别是指根据请求确定请求调用服务的过程,识别阶段的输出是service name,识别过程是根据识别类型,从请求消息生成一个特定的字符串,这个字符串与随后和这个请求关联,Linkerd路由会有一个默认前缀/svc,Linkerd会将默认前缀加上这个字符串作为本次请求的路由标识往后传递,对于HTTP请求来说,Linkerd默认情况下使用一个io.l5d.header.token类型的标识,它使用HTTP请求头中的Host字段作为标识,比如curlH"Host:data"http://example/hello,路由标识时被转换为/svr/data。

同时,识别是一个可扩展的插件,用户可以使用自定义的识别逻辑,将请求按照自己的意愿转换到相应的识别。通过识别阶段确定了service name之后,由dtab组件根据service name绑定,确定目标服务的集群名称。绑定阶段输出的是client name,client name是目标集群的名称,类似于DNS的域名,和service name只包含目标服务信息不同,client name中还包含集群、区域以及环境等信息。

dtab负责对绑定规则进行管理,比如dtab规则配置如下:/svc=>/#/io.l5d.fs,我们的/svc/test被转换为:/#/io.l5d.fs/test,client name一般以“/$”或者“/#”开头。

绑定是Linkerd中变化非常多的一个阶段,Linkerd中的很多场景需求,比如蓝绿测试、小流量灰度等均通过绑定来实现。

解析是服务发现的过程,通过named组件将目标集群名称client name转换成一组可用的节点,Linkerd会通过配置文件配置当前使用的服务发现方式,确定服务发现方式后,就可以根据client name,从服务发现中获取节点信息。

回顾上述Linkerd路由查找的整个过程,就是一个根据请求获取被调用服务名,然后根据路由规则从服务名获取对应的服务集群名,最后根据服务发现获取可用节点的过程。其中dtab和named组件分别负责路由规则和服务发现的管理。上面的讨论中,dtab与named和linkerd集成在一个进程中,默认情况下,配置在Linkerd中的dtab路由规则是Linkerd启动时解析,运行时无法动态修改

为了解决dtab无法动态修改的问题,更好地进行路由规则管理,Linkerd将dtab从linkerd代理中剥离出来,形成一个独立的服务named,named服务独立部署,灵活支持各种路由规则的动态修改和下发。

linkerd2.0核心架构

架构层面,Linkerd也是分为控制平面和数据平面,其中控制平面由下面几个部分组成

  1. Controller控制器:控制器由负责不同控制功能的多个容器组成,主要包括public-api、proxy-api、destination和tap,其中public-api负责Linkerd对外的API交互;
  2. Web:Web是Linkerd提供的仪表板;
  3. Prometheus:Prometheus负责Linkerd Metric信息的收集和存储;
  4. Grafana:Grafana负责Linkered的可视化。

Linkered的数据平面采用的是Rust语言实现的轻量级高性能Proxy代理,通过配置Iptables,它可以透明地管理Kubernetes Pod的入口和出口流量,甚至可以对一个正在运行中的服务透明无感知地添加Linkered代理。Linkered支持HTTP、HTTP/2和TCP协议,通过按需诊断tap API,Linkered拥有强大的可视化监控和诊断能力。

从Linkered的整体架构看,Linkered已经完全舍弃了打造Service Mesh平台的想法,内置具体的统计和可视化组件,聚焦为用户提供开箱即可的Service Mesh能力,当前Linkered确实已经非常轻量,对于没有特别要求的通用场景,Linkered还是一个不错的选择。

Envoy

Envoy是一个C++编写的云原生边缘代理、中间代理和服务代理,作为专门为微服务架构设计的通信总线,定位是作为Service Mesh的数据平面,接管微服务通信的全部流量,对应用程序屏蔽网络和通信的复杂性。

Envoy作为一个独立的进程,设计与应用程序一起运行,所有Envoy形成一个透明的通信网络,每个应用程序发送消息到本地的Envoy代理,或从本地的Envoy代理接收消息,但不需要知道具体的网络拓步。

Envoy的核心设计理念是网络应该对应用服务透明,职责划分上比较明确,当网络和应用程序出现故障时,可以很容易定位问题的具体根源。

架构上Envoy内部可以分为数据平面,控制平面和管理平面三个部分。

控制平面用于对流量路由和转发相关策略与配置进行管理;控制平面通过标准API获取最新的流量配置信息;数据平面的流量转发就是基于控制平面下的规则进行。

为了方便对Envoy运行状态进行监控和管理,Envoy内置一个HTTP Server,作为Envoy的管理平面,HTTP Server会注册一系列的Handler,对外暴露管理平面的API,用于外界查询当前Envoy各个维度的状态,比如外界可以通过管理平面API查询Envoy当前的集群和路由配置、当前的统计信息等。

Envoy接收到Downstream客户端发过来的请求时,一般先经过类似Iptables的透明拦截机制,然后将流量交给Envoy。Envoy获取请求对应的原生目的地址,根据目的地址信息获取对应的网络过滤链配置信息,根据网络过滤链配置建立新的连接,并通过网络过滤链对应的协议对请求进行解码和路由,选择合适的Upstream节点,将请求发送出去,同时建立Downstream连接和Upstream连接的对应关系,将Upstream返回的响应消息发送给Downstream客户端。

Envoy之所以在如此短的时间内,在Service Mesh数据平面中脱颖而出,是和它优雅的架构设计密切相关,主要体现在如下方面。

扩展性

Envoy扩展性通过插件机制实现,Envoy内部当前已经支持众多扩展点,通过网络协议插件和HTTP处理扩展插件,分别从通信协议和链路治理俩个层面进行扩展。Envoy插件机制是ServiceMEsh数据转发和数据处理的基石,基于插件扩展性机制进行扩展和定制开发非常方便,可以建立起强大的ServiceMesh生态。

配置动态化

配置动态变化是Envoy架构层面最优雅的设计。基于配置动态化设计,Envoy集群管理与流量转发相关的所有配置均可以通过XDS协议动态下发生效,不仅减少了运维负责度,同时借助动态配置化能力,方便通过一定机制发现Envoy运行时的发现并快速调整,提高了Envoy的整体稳定性。

性能

Envoy支持如此众多特性的同时,仍然可以提供优秀的性能指标,得益于良好的架构设计,Envoy架构设计对性能的考虑随处可见。比如数据转发层面,Envoy采用异步事件驱动的方式,并且保证一个请求只会在一个线程内处理,减少了请求在线程间切换的开销;此外通过数据平面和控制平面分离,配置变更时,通过无锁机制保证数据转发的性能不受任何影响。

此外Envoy对Service Mesh的一大贡献是第一个提出了通用数据平面API的概念,通过通用数据平面API,建立了数据平面和控制平面之间交互的标准,实现了数据平面和控制平面通信的标准化。只要基于数据平面API实现,可以根据需要方便地对数据平面或控制平面进行替换,有利于Service Mesh生态体系的建设。

Istio

Istio是Google、IBM和Lyft联合推出的开源微服务Service Mesh框架,旨在解决大量微服务的发现、连接、管理、监控以及安全等问题,Istio综合了Google、IBM和Lyft在微服务领域的强大积累和经验,以Envoy为基础,将Envoy作为默认的数据平面,同时提供强大的控制平面能力。一经推出在Service Mesh领域就引起巨大的反响,当前已成为ServiceMesh事实上的平台和标准。

Envoy已经可以很出色地完成流量路由和转发工作,Istio在Envoy的基础上,提供如下几方面的核心能力。

  1. 动态配置管理和下发

Istio通过通用的配置管理能力,保证对不同平台,不同环境下的配置均能通过一致的方式对Envoy进行配置和下发,Envoy屏蔽了配置管理的复杂性。这个工作具体是由Pilot组件负责完成。

  1. Envoy生命周期管理和运行状态监控

Istio负责Envoy的生命周期管理,启动Envoy,然后监控Envoy的运行状态,如果运行由异常,Istio会尝试对Envoy进行重启,重启仍然不能成功时,Istio会将当前Envoy实例调度到其他节点上运行。这个工作由Istio的Pilot组件负责完成。

  1. 通信安全

通信安全对于微服务来说是一个非常重要的基础能力,Istio通过托管身份验证、授权和服务之间通信的双向TLS加密,保障通信安全,这个工作的具体由Istio的security组件负责完成。

  1. 策略控制

对于一些核心资源,需要通过一定的策略,在不同消费服务以及服务的不同实例中进行分配,保证资源能够按照预期进行管理,这个工作具体由Istio的Mixer组件负责完成。

  1. 遥测统计

Envoy自身也能进行一些遥测统计相关的工作,但如果需要支持不同遥测统计后端基础设施,可以将这部分工作转移到控制平面上,保证Envoy自身架构的简洁性和稳定性,通过遥测统计相关的数据和指标,可以对服务进行全方位监控。这个工作具体由Istio的Mixer组件完成。

Last modification:December 5, 2023
如果觉得我的文章对你有用,请随意赞赏