vlambda博客
学习文章列表

全链路压测:系统整体容量保障的“核武器”

       你可能会问,到底什么是全链路压测?实施全链路压测要经过几个步骤?有哪些难点坑点?接下来我会把具体的压测工作,把全链路压测的建设过程完整展示给你。

      在全链路压测诞生前,双 11 的容量保障工作主要是对业务目标进行拆分,对各个服务链路单独进行压测,虽然也进行了大量的压测工作,但实际发生流量洪峰的时候,很多系统的容量还是会有问题。为什么单链路压测无法排除系统整体容量风险呢,因为整体系统的容量不是由多条“单链路”的容量简单相加而得的。我们看一下下面这张图,它表达的含义是,应用服务的容量除了受自身影响,还受依赖服务的影响,而依赖服务又可能有其他调用方,甚至是一些外部服务,这些影响经过几层累积后,最终的影响面极难判断。

      而全链路压测直接从全局视角出发,它的本质是基于线上真实环境和实际业务场景,通过模拟海量的用户请求,来对整个系统的容量进行评估的手段。当然,罗马不是一天建成的,全链路压测也不是一蹴而就的,有很多重点问题需要解决,我们可以从两方面思考。首先,全链路压测本质上也是一种容量测试的手段,因此容量测试需要解决的问题也是全链路压测面对的重点问题,比如数据隔离、压测场景置信度、对正常业务的影响等。

       其次,全链路压测的特点决定了它会存在一些独有的重点问题,由于全链路压测遵循与用户访问相同的流量轨迹,因此会涉及大量的服务链路,我们需要保证压测流量在这些链路中流动时的完整性和可识别性。此外,有别于单链路压测只需要制造局部流量,全链路压测需要制造大规模的整体流量,这也是需要重点考虑的。


全链路压测:系统整体容量保障的“核武器”

       可以说,解决了上面这些问题,就具备了全链路压测实施的基本条件,下面我就来具体展开讲解一下全链路压测的建设过程,其中包含三项改造工作和两项压测工作。三项改造工作包括:数据隔离、中间件改造和应用服务改造,这些改造工作致力于解决上面提到的压测流量的完整性和可识别性、数据隔离以及对正常业务的影响这些重点问题。而两项压测工作则包括:压测模型构建和压测流量构造,对应上面提到的场景置信度和大规模流量制造这两个重点问题。


全链路压测应该如何建设


三项改造之数据隔离

       为了避免环境不对等所造成的压测结果失真,全链路压测一般都是在生产环境展开,而线上压测就一定会涉及到数据隔离的问题,压测数据需要与真实数据有所区分,确保不影响真实用户的体验。因此,数据隔离是全链路压测建设过程中最重要,也是最必不可少的工作。常见的数据隔离方式有两种,分别是:逻辑隔离和物理隔离。

      逻辑隔离是指通过数据打标的方式区分真实数据和压测数据,在各实体(如用户、商户、订单等)中添加压测类型标识。比如针对用户这个实体,可以设置一个用户类型字段,其他系统或服务可以根据这个字段硬编码走相应的隔离逻辑。

enumUserType {

    NORMAL = 0, # 普通用户 

    PERF = 1, # 压测用户

}

       逻辑隔离实现简单,容易理解,但难以标准化,因为具体的字段设置是由业务技术方决定的。比如上面举例的用户数据中的压测标识字段是 UserType,但商户数据中的压测标识字段可能就变成了 ShopType,上游系统如果需要同时识别多种数据的压测标识时,会比较困扰。

       第二种数据隔离方式是物理隔离,它的做法是先通过在压测流量中进行打标的方式,区分真实请求和压测请求,再将压测请求所涉及的数据存储到另一个与真实表数据结构对等的表中(俗称影子表),使压测数据在物理上与真实数据隔离。这就涉及两个重点工作,一是在压测流量中打标,二是建立影子表。

      在压测流量中进行打标该如何做呢?通常情况下,流量入口大多是 HTTP 请求,压测流量标识可以置于 HTTP Header 中,进入内网后,相关中间件需要将 HTTP Header 中的压测流量标识转移至内部请求,如 RPC 请求的上下文中,如遇异步组件,如消息队列,也需要做特殊处理,具体我会在下文“中间件改造”那部分展开。这个过程要确保压测流量标识能够一路透传(传输过程中不对标识进行改变)至数据层不丢失;最后,数据层,如数据库中间件通过对压测流量标识的识别,将数据写入影子表,完成整个物理隔离的全过程。

全链路压测:系统整体容量保障的“核武器”

       值得一提的是,物理隔离还有一个额外的好处,由于实现物理隔离必须构建统一的压测流量标识,那么分布式链路跟踪系统也可以根据该标识,直观地展示压测链路,这样比较方便监控和排查问题。那么,影子表如何建立呢?为了使全链路压测对于数据库容量的评估准确,我们需要保证影子表的数据内容和规模与真实表一致,但又不能与真实表数据冲突,你可以参考以下过程建立影子表:

       1. 针对某张真实表建立相应的影子表,表名可以通过增加前缀或后缀区分,比如原表名为 User,影子表名可以设定为 User_T,其他影子表也都基于 _T 这个后缀建立。

       2. 将真实表的数据进行脱敏,部分 id 类字段需要进行偏移,以免增长后与真实表冲突,比如真实的订单号都是以 1 开头,那么影子表中的订单号可以偏移为以 9 开头。

       3. 脱敏和偏移后的数据导入到影子表中。

       4. 进行完整性检查(数据量、表结构等内容),确保数据无误。

全链路压测:系统整体容量保障的“核武器”

      逻辑隔离和物理隔离各有千秋,两者对比如下表所示。我的经验是,如果你公司的技术体系比较成熟,技术栈统一,也有固定的中间件团队支持,那么可以尝试使用影子表的方式进行数据的物理隔离;反之,如果技术体系不完整,或大量使用开源框架,二次开发成本高,那么可以采用逻辑隔离的方式。

全链路压测:系统整体容量保障的“核武器”


三项改造之中间件改造

      在数据隔离环节我已经提到,压测流量标识透传以及数据的物理隔离等工作,都需要对中间件进行改造,比较典型的有数据库中间件对影子表的支持,消息队列对压测流量标识传递的支持,等等。

      数据库中间件对影子表的支持比较简单,上面也已经提到过,实现的机制是基于压测流量标识,数据库中间件接收到某个带有压测流量标识的请求需要操作数据库时,将这个操作的目的地重定向到影子表上。这些影子表的信息,以及与真实表的映射关系,可以维护在一个统一的地方做管控,同时供数据库中间件调用。

       另外一个典型的中间件,消息队列,它是微服务体系中常见的中间件。消息队列对压测流量标识传递的支持,主要目标是保证在生产和消费数据时,压测流量标识不能丢失。具体来说,当有压测请求作为生产者将数据写入消息队列后,这个生产者的使命就完成了,当消费者去消息队列中消费出这条消息时(相当于另一次请求),我们已经无从知晓数据是压测数据还是真实数据了,这破坏了压测流量的可识别性。

       因此,对消息队列需要进行改造,当接收到带有压测请求标识的生产者推送消息时,需要将压测标识转存至数据中,以免丢失,当异步服务消费数据时,再将该标识恢复至请求体或上下文中继续传递。

全链路压测:系统整体容量保障的“核武器”

       通过以上两个例子,你可能也发现了,中间件需要改造的地方基本上都是与压测数据打交道的地方,所以在中间件改造方案的制定中,我们需要对数据特别敏感,涉及到数据的地方可能都是潜在的改造点。


三项改造之应用服务改造

       除了中间件改造,应用服务也需要进行一定的改造,确保压测请求能被反复执行,并且不影响真实场景,常见的应用服务改造点有:

       绕开限制逻辑:比如系统针对短时间内反复下单的用户将进行限制,这个逻辑针对压测流量需要放开。

       数据隔离前置:数据隔离前置能够减轻其他应用服务改造的工作量,比如大数据报表中需要对压测数据进行剔除,如果这些报表的数据源都在数据仓库里,那么我们完全可以在压测数据生成时就隔离掉不进数据仓库,这样大数据服务就无需改造了。

       Mock 逻辑:有些对外交互的服务是不太方便发起大量真实请求的,比如支付和清结算等,这些功能可以在识别到压测流量后走 Mock 服务,也就是模拟一个正常的返回,而不是直接调用真实服务。

      除此之外,还有一些如风控拦截等安全性方面的限制也需要适当放开,以便压测请求能够重复执行。总之,所有可能会限制或阻碍压测请求流动的地方,都需要打通,如果无法打通,那就 Mock。好了,到这里,数据隔离、中间件改造和应用服务改造都完成后,我们已经初步具备了实施全链路压测的条件。接下来就是压测实施层面的工作了。


两项压测工作:压测模型构建

       构建压测模型的重点是准确度,如果模型与真实场景相差过大,那么压测结果的可参考性将会大打折扣,下面是一些典型的由于压测模型不准确导致压测结果无效的反面教材:

        1. 下单链路中,压测用户没有使用红包,导致对营销服务的压测结果偏优。

        2. 压测用户数据未考虑 sharding 分布,导致数据库单片过热。

        3. 压测用户数量过少,使用有限的压测用户反复下单后,导致单个用户订单量过多。

        4. 压测商户数量过少,压测时针对单个商户的操作过于密集,导致菜品扣减库存的锁争抢激烈。

      压测模型包含业务模型和数据两部分,我再来通过几个实例讲解一下如何构建尽可能真实的场景


实例一: 读请求

读请求由于不会对数据造成污染,因此可以直接使用真实请求和数据进行回放。

        1. 压测场景:商家列表及关键词查询。

        2. 业务模型:拉取线上日志,根据真实接口比例关系进行回放。

        3. 数据:拉取线上日志,使用真实数据。


实例二: 写请求

写请求一般需要单独构造压测模型,并做好数据隔离和清理工作。

       1. 压测场景:用户下单。

       2. 业务模型:根据生产监控或日志,获取下单场景的链路信息,观察接口调用情况和上下游依赖,当然,你也 可以写一个系统帮你做这个事。产品、研发和测试共同评审链路的完整性。另外,评估业务改造点,比如需要对支付和短信等环节进行 Mock。

      3. 数据:构建测试用户、测试商户、测试菜品等数据,数量上与线上真实情况等比例缩放;及时对压测数据进行清理,或使用影子表。

      归纳一下,压测模型构建的核心要点是,要利用好生产环境的各种信息来帮助我们构建贴近真实业务的压测模型。生产环境是个聚宝盆,请求的依赖关系、调用比例、数据特征都是我们构建压测模型的素材,将这些数据抽取出来再进行精加工,即可得到贴合实际的压测模型。


两项压测工作:压测流量构造

      有了压测模型和数据,最后临门一脚就是构造压测流量进行施压。全链路压测对于压测流量构造的技术选型主要取决于流量的规模,如果规模不大,传统的压测工具是可以支持的,如 JMeter、Locust、nGrinder 等;如果是大规模流量乃至超大规模流量(百万请求量级),成本就会比较高。对于后者,可以考虑自研一套压测平台,这也是很多大厂的做法,我在下一讲会专门展开这部分内容,敬请关注。我们来总结一下,全链路压测的建设过程可以归纳为两个重点:首先,通过中间件改造和应用服务改造,保证压测流量的完整性和可识别性,并保证压测数据与真实数据隔离开;其次,利用生产环境的各类信息,构建贴近真实场景的压测模型,并通过构造大规模压测流量实施全链路压测。这些工作都完成后,全链路压测在技术层面的建设就基本告一段落了。


全链路压测的组织协调和运营工作

      说了那么多,也许你会觉得建设全链路压测的技术难度还是挺高的,但我想告诉你的是,除了技术工作,组织协调和运营工作其实更难。这就好比新冠肺炎疫情的防控,全世界都知道中国的成功经验,但有几个国家能成功复制中国的防疫举措呢?

      我认为,推动全链路压测这样的“航空母舰”项目,是需要自上而下的,但不一定非要强推。我在工作时,技术团队建立了 “Program 机制”,这是一种针对跨团队大型项目的推动机制,由 CTO 直接牵头和授权,对公司内部需要推动的技术改造类项目,进行必要性和优先级评定。组织协调很难,另一个更有难度的问题是,在全链路压测建设完成后,如何将其有效地运营起来,明确每个参与团队要做什么事,做这些事的规范是什么,做的不好的后果是什么等等,这样才能将全链路压测的价值最大化地固化下来。

      我在做全链路压测运营工作时,建立了两项规范,首先是全链路压测的常态化执行制度,每周三晚间低峰期执行全链路压测,核心链路的技术人员和运维人员必须现场值守,其余技术人员可以远程值守。值守人员需要严密关注业务指标,如果出现服务可用性问题或资损问题,及时报告压测团队暂停压测。如果压测过程中出现服务瓶颈,我们有时候会执行一些降级操作以观察效果,这时候值守人员也应配合操作,如果因为未有效值守导致线上问题,需要承担连带责任。

       此外,全链路压测能够暴露出整体系统的容量隐患,但仅仅将问题暴露出来还是不够的,我们需要确认这些问题得到重视和解决,才是真真正正地消除了容量风险,我建立的第二项规范,就是用来驱动全链路压测时所发现问题的及时改进,称之为容量问题分级规范。这项规范根据容量风险的严重程度划定了不同的等级,每个等级对应不同的解决时限要求,越严重的风险,越是需要快速解决,或至少有临时措施。我们会定期统计问题解决的时长达标率,以此作为所有技术团队绩效考评的一个参考标准。

      总结一下,推动全链路压测的落地,不仅仅是一项技术工作,组织协调和运营工作同样重要,否则还是很容易失败的。我比较倡导通过建立机制和流程规范的方式,自上而下去联动和管理多个团队之间的工作,定好的规范要及时跟进并督促执行,尽早暴露风险。

      最后,河有两岸,事有两面,全链路压测也不是银弹,无法解决所有问题,将所有容量问题全部交给全链路压测兜底,不再做单链路压测或单服务压测,是错误的实践。全链路压测的实施成本较高,因此其实施频率一般是远远低于业务变更频率的。全链路压测的擅长点是定期摸底系统整体容量,而常态的容量保障工作应当覆盖每个业务各个接口,这些毛细血管依然需要单链路压测和单服务压测去保障。