超时类告警降低99%,携程微服务治理与优化实践

HongLiang 2023-05-06 10:02:01
作者介绍

HongLiang,携程高级技术专家,专注系统性能、稳定性、承载能力和交易质量,在技术架构演进、高并发等领域有丰富的实践经验。

 

 

一、背景

 

微服务架构在中大型互联网公司中被广泛应用,随着业务的发展,应用数越来越多、调用关系也越来越复杂。中台化后,交易系统要支持业务线多,系统复杂性高,原系统虽然能支撑业务量的持续增长,但在稳定性、吞吐力和资源利用率上面,还存在优化空间。

 

分享的目的

 

本文站在业务开发角度介绍开发在微服务架构下遇到的相关问题(微服务架构的优缺点这里不再赘述),以门票活动预订流程查询引擎为例,分享微服务治理的实战经验,希望能给遇到同样问题的同学提供一些借鉴思路。

 

如下图所示,蓝色部分为本文的重点:

 

图片

图1 微服务架构关注点

 

在微服务治理之前,我们先简单了解一下微服务历史和陷阱。

 

二、微服务简史

 

微服务概念在2005年被提出,2011年用来代表架构风格。

 

定义:微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成,每个微服务仅关注于完成一件任务。

 

 
1、微服务与SOA的关系

 

大家在使用SOA (Service-Oriented Architecture)的时候往往分不清和微服务有什么关系,总结如下:

 

  • 微服务是SOA的实现方式

  • 微服务是去掉ESB后的SOA

  • 微服务是一种和SOA相似但是两种不同的架构设计风格

 

图片

图2 微服务与SOA的关系

 

了解微服务与SOA关系后,再对比一下功能差异:

 

图片

表1 微服务与SOA对比

 

 
2、携程SOA从1.0到 2.0演进

 

图片

图3 携程SOA演进

 

携程微服务:携程SOA2.0是微服务架构,推荐单机、单应用、单服务。

 

三、微服务的陷阱

 

微服务这个话术会将关注点错误的聚焦在“微”上,大家会误以为服务越小越好,实际上大小并不是第一考虑因素。接下来我们来看看开发微服务应用的时候容易踩到的陷阱。

 

下图可以看出,服务拆分越细,调用关系越复杂。

 

调用链路理论上有 n * (n-1) 条:

 

图片

图4 服务粒度越细调用关系越复杂

 

应用粒度拆分过细容易带来以下几个问题:

 

 
1、重复调用

 

调用路径 C - >D ->E 和 C ->E, 对于E的一次请求,可能会被调用了多次。

 

图片

图5 一次请求中服务E被重复调用

 

 
2、循环依赖

 

一条链路出问题,导致其他链路故障。当服务B1或B2 性能变差时,最终导致链路A/B都会被影响,严重情况下导致宕机。

 

图片

图6 循环依赖

 

 
3、链路太长

 

服务层级过深,一次请求链路太长会导致性能下降,每层网络延时和序列化反序列化时间都有性能损失,层级越深,下游性能越差。

 

链路太长,定位问题困难(效率低),当服务F出现故障时,下游A~E 应用 owner 需要排查原因。

 

 

图片

图7 链路越长,性能损失越大

 

以上这些问题,在日常开发中容易遇到,下面我们看看怎么解决这些问题。

 

四、微服务治理

 

从下图中可以看到应用之间调用关系复杂,并且有严重的循环依赖问题。

 

图片

图8 应用调用关系图(双黄线表示循环依赖)

 

循环依赖是微服务里面容易忽视的问题,系统稳定的情况下不会出现问题,由于某些原因,当系统从稳定变成非稳定状态时,循环依赖容易导致更严重的故障。我们先看1个生产案例:

 

案例:发布过程中下游超时,订单下跌

 

刚接入流量的机器因线程初始化、类加载锁、JIT等会产生慢请求。

 

图片

图9 发布过程中的慢请求

 

当流量接入时,请求在刚拉入的机器中多次来回调用,因多次慢请求叠加,导致接口越来越慢,机器资源耗尽,一台一台被拖垮,最终整个服务不可用,产生雪崩(如下图)。

 

图片

图10 发布过程中循环依赖导致应用雪崩

 

当然如果应用间循环依赖QPS很小,例如单机QPS在10以内,少量慢请求无法将资源耗尽,一般不导致故障,但是这种“坏味道”会给系统埋下隐患,严重的时候会演变为接口级的循环依赖,导致死循环,并且这种死循环可能在测试环境由于命中缓存没有被发现,发布到生产后有些缓存穿透的请求就会导致循环调用,直到超时。

 

如果单机QPS上百,产生的慢请求短时间内耗尽资源,阻塞后续请求,导致性能下降,产生故障。

 

故障恢复期间,由于调用关系复杂,分不清上下游关系,无法根据调用关系来限流,导致定位困难,恢复时间长。

 

上述案例主要是由循环依赖引起,像一颗炸弹,为系统埋下隐患。

 

除了循环依赖,还有下面几类问题可以优化:

 

  • 层级太深:

 

  • 透传字段要改多个应用,需求迭代效率低

  • 每层网络延时、序列化和反序列化都有性能损失,导致终端体验差

 

  • 重复缓存:同一个DB不同应用重复构建缓存

 

  • 流量大:

 

  • 重复调用,直接调用或者间接调用,末尾服务压力大

  • 离线任务峰值波动太大

 

  • 未隔离:核心、非核心流量未隔离

 

  • 效能低:人均应用多/资源使用率低

 

针对上面的几类问题,我们制定了微服务治理目标、原则和治理策略。

 

 
 1、治理目标

 

1)稳定:故障隔离,提升系统稳定性

 

2)交付:独立迭代、独立扩展、快速交付

 

  • 横向拆分:减少耦合,独立迭代。

  • 纵向拆分:减少应用层级,提高开发效率,缩短交付周期。

 

 

3)重用:相同功能复用

 

不同系统重复功能复用,减少重复开发,提升一致性。

 

 
 2、治理原则

 

1)避免跨团队维护一套代码。

 

2)服务粒度要与团队规模匹配,人均应用数在3个以内。

 

根据历史经验,一个人在超过3个应用之间来回切换开发,开发效率会降低,日常处理告警繁琐,业务和性能优化也无法聚焦。

 

3)应用分层:上一层可以依赖任意下一层级(不可反向依赖)。

 

4)层级深度:垂直域/小组内,应用层级控制在5层以内。

 

这里的“5层”是我们根据实际业务实际情况来定的。一个垂直域/小组内应用层级超过5层,一个需求上下游依赖太多,开发效率会降低。

 

 
 3、构建原则

 

1)业务领域拆分:单一职责,业务建模(对人员要求高)

 

2)数据存储:独立的数据读写API

 

3)复用性:功能复用(比如基础数据提供能力,提供给不同小组使用)

 

  • 可靠性

  • 核心与非核心隔离

 

4)稳定规则与易变动规则隔离

 

5)快速失败:设置合理的熔断规则

 

6)异步通信:将与此次请求无关的操作/调用异步化

 

 
4、治理策略

 

1)去除循环依赖

 

问题:服务B和服务C 循环依赖

 

策略:

  • 应用分层与定位:第一步划分应用层级(分层工具有传统三层架构、泛领域分层等),将应用定位划分到不同的层级。

     

  • 确认依赖关系:每一层内如果有多个应用,确认上下游关系。这个根据业务场景来,根据父子关系,包含关系,依赖关系,确认每一层内的依赖关系和应用职责。

 

图片

图11 循环依赖治理

 

2)缩短调用链路

 

问题:服务BCD 链路太长(垂直域/小组内)

 

策略:

 

  • 领域细分:将粗粒度的应用按照业务领域垂直划分,不同层级负责不同的职责,让系统更独立。

  • 减少透传:每个层级职责清晰,减少不必要的透传,让开发效率更高。

 

图片

图12 缩短调用链路

 

3)复用性

 

问题:服务BCD 对相同数据重复缓存(存在一致性问题)

 

策略:

 

  • 下沉基础服务,提供基础数据:将相同的功能下沉为基础服务,例如:基础数据服务提供缓存,翻译等功能。避免不同的使用方重复缓存,重复接入翻译。

 

图片

图13 重复功能下沉

 

效果:下沉基础数据服务,统一缓存,翻译等功能,提供给不同的开发组使用。

 

4)流量治理

 

 ① 重复调用

 

问题:一次请求,服务C同一个接口被重复调用

 

策略:

 

功能内聚:将同一个功能对下游的依赖放到同一个服务内调用。由于系统自身迭代导致的不合理调用,可以按照上述方法优化。如果为了解耦将功能拆开,可以根据实际情况评估影响和收益。

 

图片

图14 功能内聚合并重复调用

 

效果:功能内聚,多次调用合并为一次调用。

 

 ② 降低调用量

 

问题:一个服务中,不同的接口功能拆分太细,下游使用的时候都需要调用多个接口组装结果。例如:一次请求服务B的a、b、c、d、e接口都被调用,下游为实现一个功能,需要调用太多小接口。

 

策略:

 

合并服务B中同一领域功能:将相同的功能合并到一个接口,减少调用量。

 

一个接口提供模块参数,按需调用:

 

  • 支持按需使用,对不同业务场景非必须的功能,提供模块参数,按需传参。

  • 对于独立的前端页面接口,对外透明,内部封装对应场景需要的模块参数,例如前端首屏请求。

     

 

图片

图15 请求合并

 

效果:聚合相同功能,合并小接口,多次调用合并为一次调用。

 

 ③流量隔离

 

问题:非核心流量(例如:Job调度)大于用户流量

 

策略:

 

流量隔离:一套代码,隔离部署,将核心和非核心流量隔离。核心流量承载用户请求,保证交易的稳定性,非核心流量承载离线任务调度和非核心场景调用。

 

图片

图16 流量隔离

 

效果:总成本不变,核心链路稳定性得到提升,非核心链路CPU使用率得到提升。

 

 ④离线调度流量消峰

 

问题:单位时间内调度过于集中(Job)

 

策略:

 

合理的延长调度时间:适当延迟调度时间,降低每分钟的调用峰值,让每分钟内调用量更加平稳。

 

图片

图17 离线调度流量消峰

 

效果:调度总时间在可接受范围内,调度时间拉长,单位时间内调用总量降低,降低服务端峰值压力。

 

问题:每秒内调度不均衡(Job),导致服务稳定性差或为了能承载请求需要冗余更多服务器资源。

 

图片

图18 客户端调度QPS不均衡

 

策略:

 

客户端削峰填谷:调度波动太大,会导致请求到了服务端被限流或者服务端扩缩容。对于调度不均衡的离线任务,我们在客户端控制每秒内发送的请求量,让每秒内请求更加平稳,任务调度总时间不变。

 

图片

图19 客户端调度从不均衡变为均衡

 

效果:分钟内总的调用量不变,服务端调用量从波动变为平稳。

 

 ⑤降低人均应用数/提升CPU使用率

 

问题:

 

  • 人均应用过多,开发效率降低

  • CPU使用率6%以下应用数占比超过50% 且总核数占比超过30%

 

策略:

 

  • 短期:缩容,将单边服务器数缩容到SRE标准最小配置。

  • 长期:合并拆分过细的应用,参考历史、现状和将来的规划,将拆分过细、CPU使用率长期小于6%的应用做合并。

 

图片

图20 应用合并

 

五、实施效果

 

1、循环依赖(应用分层,解除应用间循环依赖)

 

  • 去掉65条循环依赖链路,消除雪崩的风险

  • 超时类告警降低99%

  • 排障效率提升至分钟级别

 

2、链路长(减少应用层级):调用链深度缩短 40%

 

3、复用性(下沉基础数据服务,减少重复功能)

 

  • 新增基础数据服务,缓存统一,解决一致性问题

  • 缓存容量减少60%

 

4、流量治理(降低水位线)

 

  • 重复调用:功能内聚,去除重复调用

  • 调用量大:合并小接口、消除调用峰值;离线任务削峰填谷,降低峰值调用量

  • 核心应用调用量减少73%,核心系统峰值降低50%

 

5、开发效率(解耦&减少中间层)

 

  • 水平拆分独立功能,减少耦合,独立开发

  • 垂直领域减少3层,开发效率提升

 

6、查询引擎性能提升65%,QPS从8w提升至24w

 

  • 减少了系统不稳定导致的服务变慢

  • 领域划分,垂直优化系统,专注用户端到底层的优化

 

7、人均应用:人均应用数控制在2个以内

 

8、资源使用率(应用合并,提升CPU使用率)

 

  • 40+个应用CPU使用率(加权平均)从18%提升至32%

  • 治理前后查询引擎链路对比:

 

图片

图21 门票活动查询引擎微服务治理前后对比

 

六、总结

 

微服务架构下服务拆分越细,调用关系越复杂,层级越深,性能损耗越大,开发效率越低(垂直域/小组内),所以服务不是越小越好,而是“合适的大小”。

 

在构建微服务的时候,要根据业务体量、团队规模、成本等因素综合考虑,按照合理的原则,构建出适合的大小,以达到预期的目标。

 

服务治理是一个长期的过程,制定目标持续优化,让系统更快更稳定,为业务赋能。

 

作者丨HongLiang
来源丨公众号:携程技术(ID:ctriptech
dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn
最新评论
访客 2023年08月20日

230721

访客 2023年08月16日

1、导入Mongo Monitor监控工具表结构(mongo_monitor…

访客 2023年08月04日

上面提到: 在问题描述的架构图中我们可以看到,Click…

访客 2023年07月19日

PMM不香吗?

访客 2023年06月20日

如今看都很棒

活动预告