图片

导语|春节期间腾讯大部分业务进入流量备战的紧张时刻。压测相比于监控而言,是更具主动性的筹备手段。通过高负载、真实流量的预演,探测系统的瓶颈和发现风险,是服务质量保障体系的重要一环。云压测主要聚焦在压测平台的发压端基础能力构建,本文作者张泽强分享云压测备战春节期间从压测模型选型、用例编写、测试数据构建到压测报表分析的压测方案。期望对你有帮助。

目录

1 背景与挑战

2 解决方案

2.1 压测模式选型

2.2 压测用例编写

2.3 测试数据构造

2.4 压测报表分析

3 实践案例

3.1 手Q春保活动

3.2 视频业务容灾演练

4 总结展望

01、背景与挑战

春节期间,腾讯大部分业务进入春保备战的紧张时刻。节假日高峰时间点上涨五倍十倍的用户流量,给业务稳定性带来不少的挑战。以各位熟知的QQ为例,QQ服务大规模的移动互联网用户,作为一个超大流量应用,面对逢年过节的流量洪峰是它不可忽视的问题。手Q业务每年元旦和春节的0点0分,都会有一波非常高的尖峰。读链路和写链路分不同命令字会数倍地流量飙升。而在线视频业务也面临同样的问题,通过做好容灾演习以验证在各种异常情况下的容灾容错能力,通过压测排查关键服务性能是否存在问题、找到链路性能瓶颈、明确链路服务扩容模型等任务迫在眉睫。

压测相比于监控而言,是更具主动性的防备手段。通过高负载、真实流量的预演,用于探测系统的瓶颈和发现风险,是服务保障体系的重要一环。整体来看,云压测的主要应用场景包括:

  • 第一,验证新功能上线的吞吐量预期,保障系统稳定性,避免服务上线的流量瞬时击穿
  • 第二,老旧服务的重构改造。在降本增效的背景下,如何降低机器的部署成本以带来显著收益,是个值得思考的问题。老旧服务大部分存在业务流量构造的难题,大部分也不存在存量的用例自动化验证机制,会带来比较大的重构挑战。
  • 第三,发现系统瓶颈缺陷以保障大型节点系统稳定性。春节、元旦、618、双11等大型活动预演,提前通过全链路压测发现系统瓶颈和缺陷,按照保障目标提前进行扩容、缩容。扩容是为了直接提高系统可处理的最大吞吐量,而缩容是为了验证该服务存在冗余的资源配额,在上游的处理能力跟不上的时候,该资源是浪费的。
  • 第四,验证后台服务降级、弹性策略。部分业务在服务启动时存在资源预热加载、CPU使用率飙升、OOM等问题。这类问题大部分发生在业务流量比较大的情况下,平时不容易模拟,通过压测将流量线上放大能够有效的复现该场景。

然而目前,大部分业务压测是单机器、单服务、单链路的流量模拟,在容量预估场景下容易出现偏差,主要问题如下:

  • 现有系统大部分是微服务体系,存在上下游的链路依赖,第三方的链路不能直接压测(支付、云厂商服务)。很多情况下整个服务的瓶颈,不在当前压测服务。而直接采用mockserver 来模拟耗时和返回业务数据,也会隐藏该服务短板。
  • 其次是流量构造失真问题。线上的用户量、关系链、请求参数的维度比较多,无法直接通过编写用例脚本(等价有限的参数构造逻辑)来模拟线上真实流量。固定化参数数据直接导致热点数据异常,也会导致压测失真,无法有效通过局部推算全局的表现。
  • 此外,数据规模没有达到预期。大部分服务属于I/O密集型服务,业务瓶颈都在存储层服务,例如mysql、redis、kafka等中间件,是由于持续量变导致的质变。数据规模在翻番的情况下,上下游链路的耗时表现可能呈现出雪崩效应。

因此要准确预估服务的容量,在高并发的场景下探测瓶颈和缺陷并不是简单的事情。工欲善其事,必先利其器。如何设计、实现一款好的压测工具并且给业务降低接入成本,是一件持之以恒的事情。

以前面提到的移动端QQ和在线视频为例。手Q在春节期间读链路和写链路分不同命令字会数倍的流量飙升。其中,针对只读链路可以通过集群流量调度做到读链路的压测,但是写链路的压测(主要包含 feed 发表、评论、点赞等交互)较缺乏,测试数据构造复杂,用例编写成本高,是一个亟需解决的问题;此外,云压测之前主要采用JS脚本进行场景编排,图文场景出现了内存占用高,二进制频繁进行数据深拷贝,也给压测机内存资源准备带来不少的压力。

下面章节我们会详细解析云压测的解决方案,最后分享其在发压端支持手Q和在线视频业务的实践案例。本文对云压测的思考和实践,供抛砖引玉。欢迎继续阅读。

02、解决方案

云压测服务的目标是模拟海量用户的真实场景,全方位验证系统可用性和稳定性。简化性能测试工具,让用户更加聚焦业务和性能问题本身。从用户的使用角度,常规的压测流程主要包括以下,其中定位和分析瓶颈是最具备价值一环:

图片

实施路径:

  • 通过全链路压测精准评估系统上下游服务容量进行扩容、缩容,减少机器部署配额;
  • 通过压测将性能测试左移,提前定位和分析性能瓶颈,保障服务稳定性。

2.1 压测模式选型

云压测提供并发模式和RPS (request per second)模式,无论哪种模式的目标都是给被压服务带来足够的吞吐量压力。RPS模式底层也依赖并发(Virtual User)。通常压测引擎通过多线程、协程模拟多个客户端同时请求,保障单位时间内的吞吐量稳定,通过梯度、手动调速调节目标流量压力。

接下来讲讲VU和RPS的简单换算公式。其中RPS表现跟接口耗时直接相关。假设接口耗时为100ms,1个VU平均一秒能够请求10次,那么在发压机、后台服务资源充裕的前提,VU和RPS是线性递增关系。

图片

  • 处在线性增长区时,响应时间(RT)基本稳定,吞吐量(RPS)随着并发用户数(VU)的增加而增加。
  • 三者关系符合Little定律:VU=RPS*RT。随着VU增大、系统的资源利用率饱和,系统到达拐点。
  • 若继续增大VU,响应时间开始增大,RPS开始下降。继续增加VU,系统超负荷、进入过饱和区,此时响应时间急剧增大、RPS急剧下降。

大部分后台服务压测适用于RPS模式。该服务或功能模块一般用于满足多少吞吐量的要求,因此主要观测后台服务的处理请求速率。支持用户自定义在报表页面进行手动调速,例如用户预期是1W RPS,可自定义初始1000RPS,按照一定的阶梯(1~2的系数递增)进行调速,这样通过观测服务本身的业务指标(吞吐量、时延、错误率)和饱和度(内存、CPU使用率)即可分析业务的瓶颈。针对秒杀、元旦、春节零点等活动,需要模拟多个用户的同时并发场景的话,则可以采用并发模式,例如支持10w人同时的活动抢购。

压测的并发调度下几种常见场景:

  • 场景一:接口耗时敏感,并发量小,时延失真

在接口耗时比较低的情况下,会出现80并发和250并发的吞吐量一致的情况,原因是发压机调度的机器规格一致并且CPU负载饱和。从下方的压测指标图可以看出,并发数逐渐增大。当并发达到80、达到吞吐量最大值,后续随着并发增大,响应耗时急剧增加,而吞吐量却没有明显变化。这其实意味当前这台发压机已经达到极限,cpu、内存资源水位位于高处。随着并发数增大,该机器没有更多的处理资源,因此协程频繁上下文切换带来发压机的请求耗时的增加。压测报告的latency耗时表现失真,而服务端的耗时表现正常。当发压机的资源达到瓶颈时或者CPU使用率超过 80%,指标采集线程、协程会出现频繁CPU时钟中断,导致指标埋点采集延迟。

压测报告表现:

图片

发压机负载表现:

图片

  • 场景二:压测机&被压服务资源充裕,RPS达不到预期目标

压测过程中,并发数配置比较充裕,发压机负载稳定,后台服务也没达到探测瓶颈,但是RPS一直上不去。值得注意的是,RPS并不等价TPS。作为发压侧,引擎能够保证每秒发出去的请求数,TPS可以理解为收到回包的时间点数据,随着不同接口的耗时变化,吞吐量抖动会比较明显,表现出来为用户设置的RPS和实际TPS有差异。

图片

因此TPS会随着耗时的变化而频繁抖动。以http协议为例,客户端完整的耗时路径包括DNS寻址->建立连接->请求包网络传输时间->服务端处理耗时->响应包网络传输耗时,因此吞吐量表现会比APM监控的耗时要长,针对高性能组件压测场景,网络往返耗时波动占整体耗时比例会更高,导致不同地域下的吞吐量表现可能相差一倍。

图片

2.2 压测用例编写

云压测主要面向专项测试人员(对外TOB交付压测报告)、后台研发人员(保障后台服务质量),技术运营开发者(产品上线吞吐量验收),这部分开发者的代码编写水平参差,无法通过一套方案来满足用户诉求。因此云压测提供了多种用例编写方式(低代码、JS、GO、xml),以满足不同用户、不同场景下的适配需求。下面将分别阐述3个模式。

2.2.1 模式一:JS脚本模式

JS脚本模式提供串联接口的编排模式,平台通过封装公司内的常用协议提供对应的脚本模板,业务可以基于该模板进行请求参数 DIY降低接入成本。JS是高级语言、相关开源社区活跃,语言本身解释能力比较强,针对后台单接口、通用协议具备一定的通用性,也是业内主流开源引k6主打的业务场景。其缺点也比较明显,开源协作的社区氛围下,大部分后台研发人员对JS语法和基础框架并不是特别熟悉,无法吸引更多的研发人员持续迭代。大量的私有协议适配、基础库封装会带来大量的适配工作量(goja的脚本映射),依赖平台方、需求方持续迭代,而这些私有场景的适配工作并不能直接复用提高 ROI,无法快速支持业务特定需求。

// Send a http get request

import http from 'pts/http'; // 协议适配模块

import { check, sleep } from 'pts'; // 常规编排流程封装

 

export default function () {

    // simple get request

    const resp1 = http.get('http://httpbin.org/get'); // 执行发包操作

    console.log(resp1.body); // 日志打印用于问题定位

    // if resp1.body is a json string, resp1.json() transfer json format body to a json object

    console.log(resp1.json());

    check('status is 200', () => resp1.statusCode === 200); // 通过断言集成业务指标

}

2.2.2 模式二:Go Plugin模式

它提供Go的脚本编写方案。该方案是Go官方支持的热加载机制,对第三方依赖具备较大的局限性(无法依赖不同版本,编译环境、执行环境需要统一,存在cgo依赖),因此业界并没有大规模使用。但是云压测场景下,可以满足用户go用例编写诉求,以及通过依赖倒置注入规避第三方版本问题,现有用户场景下使用也表现成熟。复杂编排、私有协议的支持给予了用户一定的空间,引擎本身只需要保证指标埋点、并发调度符合用户设置就能满足诉求。

由于历史包袱,protobuf协议很多单文件上千行,接口的参数构造复杂,存在公共协议之间的嵌套。用JS或python动态语言脚本编写用例,需要通过上传所有协议文件,用json来转换protobuf二进制数据,管理目录嵌套层级。这给用户带来很大的心理负担,同时也丧失stub代码的优势,减少数据频繁转换、构造参数。

// Init 从用户脚本注入底层实现,包括加载 metrics 上报插件&注入 otel 实现框架。
var Init = plugin.Init

// Run 核心脚本编写逻辑,引擎会按照压测模型执行该 Run 函数。
func Run(ctx context.Context) error {
    // 必须通过 NewRequestWithContext 传入 ctx 构造请求,否则无法展示 har 格式的采样数据
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://httpbin.org/get", nil)
    if err != nil {
        return err
    }
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    // 自定义断言,自动上报业务指标
    assert.True(ctx, "status code is 200", func() bool {
        return resp.StatusCode == http.StatusOK
    })    
    return nil
}

Go Plugin复杂场景下的优势:

  • 支持插桩代码引用,无需额外上传协议文件,减少频繁数据序列化操作带来的性能损耗;
  • 对后台开发者友好,基于go进行用例编写,可以更灵活地复杂场景编排,支持现有存量协议封装、工具库集成,提高代码复用效率;
  • 平台集成 HTTP、gRPC等协议指标采集,用户聚焦在用例编排,请求流量构造等场景,减少用户心智负担。

2.2.3 模式三:低代码、JMeter(GUI)的编写模式

云压测面向专项测试人员、非开发出身的从业者提供了UI拖拽的方案。基于har to js的模板映射框架,用户可自定义在简单模式、脚本模式进行切换,目前支持har->js的单向转换。针对http等通用标准协议,提供 request、header、config参数构造选项,降低用户的心智负担和使用云压测的门槛。而存量用例也是重要考量因素,JMeter在性能测试领域的市场占用率比较高,大部分的商业化测试软件也会支持jmx脚本压测,业务的存量测试资产转移到全新平台有脚本适配改造成本,整体的投入产出比不高。因此云压测也基于JMeter插件机制进行扩展,新增指标埋点、日志采样、线程组调度的能力,能够在一套调度平台(box、ship机制)执行不同的发压引擎,降低整体适配的复杂度。

图片

2.3 测试数据构造

测试数据构造是用例编写的核心环节。常规包括测试账号管理、鉴权,脚本的配置化参数(例如视频流压测包括是否开启视频转码、白名单、长短连接等),接口参数构造(直接跟业务逻辑强相关),固定化的数据会导致压测失真。因此云压测提供了线上流量录制转用例的能力,线上录制二进制包基于协议进行协议转换为云压测支持的流量存档格式,在123容器上的实施自动化程度较高。同时也支持CSV文件上传,支持脚本按照列名读取,多个CSV文件会进行merge,默认采用行数较多的作为基准文件,从头到尾轮询读取数据处理。

图片

在大容量、高并发场景下,如何保证整体的压测集群发送的流量是均匀且分散的呢?云压测提供了按照发压机进行切片的配置选项,默认会按照任务配置的pod数量进行文件分割,这样保证分配到每个发压机的测试数据不重复,引擎按照轮询机制读取数据,尽量保证不进行数据深拷贝,避免内存full gc导致抖动。

2.4 压测报表分析

压测报表分析是整个压测流程最具有价值的一环。为了让用户更加聚焦报表,云压测针对不同引擎、脚本提供了一致性的观测体验。基于opentelemetry标准来实现整体压测报表的数据透视,包括metrics、trace、log。

图片

首先通过metrics来判断客户端(压测报表)、服务端(APM监控框架)的接口成功率、时延、服务的饱和度是否符合预期,最佳方案是针对特殊的自定义状态码支持业务单独断言。定位到异常的metrics数据,根据错误码筛选请求日志查看完整日志,云压测基于http har进行gRPC等私有协议字段存档格式兼容,保证所有引擎的日志观测体验一致。针对异常的日志请求数据,自定义traceID埋点进行流量染色,结合服务端本身的trace监控能力进行链路耗时排查。

图片

采样策略符合以下的特性:

  • 合理选用gauge、counter、histogram(分bucket) 优化指标聚合效率;
  • log配合trace设置采样,保证脚本执行日志链路完整;
  • trace&log默认设置合理采样比例,减少日志上报带来性能损耗。

图片

业务自定义检查点,用于业务字段(例如业务特有bizCode等)断言:

图片

基于har存档日志进行请求日志采样,优先进行异常请求采样、按照请求比例(默认千千分之一)。按照trace链路进行采样,保证请求上下文完整性,用户可自定义脚本中的traceid(支持Trace Context)配合业务的APM进行异常链路耗时定位。

图片

用户日志、引擎日志打印输出,方便脚本调试、引擎问题排查定位。该日志采样策略有一定的频率限制,避免消耗过多的cpu资源,同时可以减少日志存储成本。从压测过程具备完整metrics、trace、log可观测性链路数据,针对一些bad case也有完整的排查路径。

图片

03、实践案例

3.1 手Q春保活动

3.1.1 背景

目前手Q业务每年元旦和春节的0点0分,都会有一波非常高的尖峰。读链路和写链路分不同命令字会数倍地流量飙升。业务架构保障高可用,大部分都是多地部署的,针对只读链路可以通过集群流量调度做到读链路的压测。但是写链路的压测(主要包含 feed 发表、评论、点赞等交互)较缺乏,测试数据构造复杂,用例编写成本高。云压测之前主要采用JS脚本进行场景编排,图文场景出现了内存占用高,二进制频繁进行数据深拷贝,给压测机内存资源准备带来不少的压力。

3.1.2 实施方案

接下来讲实施方案。2023年的春保采用Go Plugin进行用例重构,复用了现有的数据编解码组件能力,极大降低了适配成本,并且由于是原生go的协程调度执行,减少了大字符、字节码变量转换带来的性能损耗,同时也避免内存 gc 带来的流量抖动。支持复用存量的协议封装、鉴权接口,无需单独维护JS引擎的转换成本。相同分片上传的接口场景下,采用Go Plugin脚本相比JS脚本的 1000 并发吞吐量提升了90%的性能表现,有效的降低业务的硬件资源使用成本。

图片

3.1.3 业务落地效果

  • 在6-8倍的日常流量保障目标下,探测上下游的服务过载情况,提前进行扩容尽早干预;
  • 针对链路超时的现象,合理设置重试策略、超时时间,验证柔性策略是否生效,避免瞬时压力造成雪崩效应;
  • 支持上海、南京、广州等多个地域集群压测,最高达到10w并发数,目标RPS达到100w的吞吐量规模,支持100G级别带宽流量验证。

3.2 视频业务容灾演习

3.2.1 背景

演习和压测是首页链路各个服务重构后的一次整体摸底,也是2023年春节保障的提前演习。这里主要做3件事:

  • 容灾演习:为了验证首页接口在各种异常情况下的容灾容错能力,梳理容灾容错短板;
  • 压测:为了排查首页链路中的各个关键服务性能是否存在问题,找到链路性能瓶颈,明确链路服务扩容模型;
  • 接入层兜底能力摸底,当首页接口故障情况下,兜底能力能否达到预期的目标

3.2.2 实施方案

根据演习计划,关注告警信息、容器负载、主被调、失败率、平均耗时等指标。从接入层通过透传流量标识进行压测流量打标,上下游链路涉及RPC调用,缓存中间件、数据库中间件、消息中间件等,整体框架需要接入统一治理服务,保障数据隔离、服务隔离。压测过程中,云压测集成了被压测服务的SLA监控,根据服务的重要程度进行等级划分,并且通过告警收敛自动进行流量降级、熔断,特性环境压测有效的减少规避事故的爆炸范围。长时间的持续高负载也会带来服务的潜在的雪崩效应,如何兼顾压测流量的饱和度和业务流量安全,是需要持续进行策略迭代优化。

图片

服务容灾验证路径概述如下:

  • 混沌工程注入,验证接口的健壮性;
  • 兜底缓存策略的触发机制;
  • 验证服务降级、熔断策略的机制;
  • 验证服务的过载保护能力,具备柔性可用;
  • 校验业务安全管控策略、高可用。

3.2.3 业务落地成果

  • 验证业务的弹性伸缩能力,对降级、熔断、柔性服务进行可用性验证;
  • 通过 PTS 提供的 RPS 扩散模型,了解上下游服务的机器规格配比,为容量预估、机器扩容、缩容做好评估依据。

04、总结展望

云压测主要聚焦在压测平台的发压端基础能力构建,实时展示了客户端的性能指标趋势,包括并发数、RPS、latency、错误率,适配大部分HTTP、gRPC、websocket等协议,针对私有协议、信息流、视频流有go plugin的自定义埋点解决方案。目前对服务端的监控数据整合相对欠缺,用户需要频繁切换多个监控平台来观测服务的负载,针对各业务常用 APM 集成方案在规划迭代中。平台后续会围绕着性能测试自动化、智能化的目标会持续迭代。尽量减少用户手动操作的成本,通过相对自动化的解决方案来去定吞吐量、检测系统性能瓶颈,并且基于SLA标准进行流量降级、熔断能够有效的保障压测安全。

图片

以上是云压测百万级 QPS 压测解决方案和在它手Q、在线视频业务实践的全部内容,欢迎各位读者在评论区分享交流。

-End-

原创作者|张泽强

技术责编|张泽强、刘楚蓉

你可能感兴趣的腾讯工程师作品

| 腾讯云开发者2022年度热文盘点

| 企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

| 7天DAU超亿级,《羊了个羊》技术架构升级实战

国民级应用:微信是如何防止崩溃的?

技术盲盒:前端后端AI与算法运维|工程师文化


腾讯云开发者
21.9k 声望17.3k 粉丝