文章目录
为什么要做压测
互联网人做系统多多少少都听说过”全链路压测“一词,之前多是在网上看过,自己不曾实际操作、经历过。2020年底恰逢一次系统故障(流量突增导致系统异常),领导便问到:明年要开城了,系统能不能抗住现在5倍的流量?好家伙,这一问给我问住了,现在系统是跑的好好的,但是假设开城后(或者有大促啥的活动),流量变成现在5-10倍,你敢拍着脑袋说系统没问题?
做开发这行的大多讲究眼见为实,告诉我你行,不如证明给我看你行,全链路压测就是证明你的系统调用链路稳定性的关键方法,因此首先要明确一点:压测的目的是在确保系统在当前流量N倍情况下是能够正常、稳定运行的
我不敢拍着脑袋说流量N倍的情况下系统没问题,但是我敢肯定的是,流量放大N倍,系统一定会出问题。流量放大N倍的情况下,系统CPU空闲率、线程池、RT、存储层Mysql耗时,Rpc请求错误率多多少少都会出现异常,这是很正常的情况。稳定性是什么?按我的理解讲:稳定性就是系统在运行时没有表现出明显问题。那么压测的根本目的也很显然了,压测的根本目的就是发现高流量水位下的系统问题,并解决这些不稳定性,同时摸清系统的极限流量水位
压测要做什么
明确压测目标
首先我们要明确一点,核心链路压测不是针对一个系统的发压测试,他是针对某个特定功能点的发压测试。至于要压的链路是什么链路,这得根据你的业务情况具体而定。但有一个大原则是不变的:优先压测核心接口与核心链路。试想假设你此时此刻负责的是整个淘宝商城的应用稳定性,你要做压测,你是会先压下单链路的稳定性,还是会先压退款链路的稳定性?
但是同时也得明确一点,核心链路压测并非是针对整个系统的压测,因此无法很好的评估整个系统的承载能力,但在做针对整个系统的全链路压测之前,核心链路的压测必不可少,你还想一步登天不成?
核心链路梳理
明确你要做压测的核心链路后是不是就可以开始制定压测方案了?不是的,还得明确一点:压测不是一个人的事,也不是一个系统的事。一个需要压测的核心链路一定是足够复杂,足够长的,如果这个链路只在自己系统内部就可以闭环结束的话,那我理解这个链路是没有必要做压测的。
压测这块砖,一个人是搬不动的。一个核心调用链路往往和涉及到多个上下游服务、其由不同团队分别维护。因此,你要做的首先应该是梳理你的核心链路,至少如下几点你得了然于胸
- 明确链路中涉及了哪些上下游接口调用。其负责人分别是谁?(想一下,压测过程中把下游给压挂了,这个锅你背的起?)。明确有哪些接口依赖也有助于我们在压测时针对性的Mock掉接口的返回值,毕竟是你要做压测对不对,不是所有下游调用方都是能支持压测的
- 明确调用链路涉及了哪些中间件。通信是Rpc调用?Http调用?或者是Mq?存储层用的是redis?还是mysql?了解依赖链路时使用的中间件有助于我们在压测改造时针对压测流量做隔离,在存储层针对压测数据做数据隔离。压测的大前提是保证稳定!脏数据是很重要的一点,压测至少不能让脏数据流入正常系统。
- 核心监控梳理完善。如果链路中涉及的机器监控、接口调用监控,存储层的监控都是缺失的,压测的时候出了问题你如何感知?同时监控告警的完善,也有助于我们配置一个合理的发压范围,比如链路入口当前QPS是500(没有监控你怎么知道是500?),那么我们就可以按5倍(2500QPS),或者10倍(5000QPS)的量来做压测。监控帮助我们明确压测范围,告警帮助我们明确什么时候停止压测。
最终针对这条核心链路,需要给出你的期望压测指标,即
- 压测期望到达的 QPS
- 压测期望平均响应时间
压测方案
压测整体方案
如图所示是一种常见的下单链路服务调用链路,涉及订单服务、商品服务、库存服务
整个压测思路及流程如下,其核心是影子表方案的应用以及各个中间件针对压测流量的区分
- 压测数据构造。创建虚拟商品、库存、订单号及测试买家账号等信息,尽量要在数据量上保证也是N倍于现有业务,尽量逼近实际业务情况
- 流量入口是发压工具,大公司一般会有自己的压测平台来发压,如果开源的话可以考虑使用Jmeter,也有一些在线形式的发压网站,具体并发数、Qps需要结合业务实际来决定,并逐渐增加压力,避免系统异常
- 发压时需要针对压测流量做特殊标识。如果入口请求是Http,可以考虑在http请求头中带上特殊压测标识,比如fullLinkPressureFlag = true。前面已经说了压测流量一定不能影响线上正常业务,因此各个中间件层面需要针对压测流量做区分(这是有技术改造成本的第一个地方),下面会具体介绍各个中间件做流量区分的实现方式
- 存储层(redis、mysql、mongo等)需要做数据隔离,如果压测链路中涉及到一些数据写入(redis、mysql、甚至是日志写入),那么这些无用的测试脏数据最终存到哪呢?必然是不能落到正常业务表里的,常见的一种方案就是新建一个一模一样的影子表(表结构完全与正常业务表一致),并将压测数据最终落到影子表中,这是需要技术改造的第二个点
- 依赖接口Mock处理。调用链路多多少少会碰到部分下游接口是无法支持你的压测的,如果把你的压测流量打过去,同时他们又没做对应改造和区分,下游服务可能会被直接打挂,同时也可能产生大量脏数据,因此在前面提到的核心链路梳理阶段,需要整理出依赖了哪些下游接口,并针对性的做出Mock处理,最好借助于Mock工具,MockServer方便后续可拓展,这是需要技术改造的第三个点
- 观察核心监控,根据服务的内存使用率,IO情况,Cpu情况,服务响应时长信息等情况来判断是否达到系统极限,预估系统能承载的流量水位,若有异常,及时停止压测
当然,压测时间得选择在业务低谷期,选择高峰期压测是想挑战自己?
压测环境可以考虑直接使用线上业务所在的真实环境,毕竟重新搭建一套环境成本较大,也不能完全保持和线上一致,难以达到仿真效果
压测前-压测改造
整体压测改造的思路说完了,作为开发来说,最关注的其实还是其中的技术改造怎么做,下面具体介绍下各个技术改造点的实现方案
流量区分
压测流量区分的关键点有几个
- 入口流量一定要携带压测标识
- 单个服务中压测标识的透传使用线程上下文实现
- 不同服务之间的压测标识透传需要针对相关中间件做一定改造,以此来将压测标识在多个服务间透传
常见的中间件改造方案如下
中间件/框架层 | 实现方案 |
---|---|
http | 请求头中携带压测标识 |
dubbo | 1、压测标识通过RpcContext上下文携带 2、封装压测提供者过滤器,用于识别上游传递的RpcContext中压测上下文,同时将压测标识设置进入线程上下文 3、封装压测消费者过滤器,用于识别本系统中线程上下文中携带的压测标识,设置进入RpcContext传输给下游服务 |
mq | 1、可以考虑在消息体中写入压测标识,下游识别消息体中压测标识做针对性处理 2、可以考虑将压测消息,直接发送到一个新的topic中(比如带shadow后缀的用于测试的topic) |
mybatis | 利用Interceptor机制实现压测Interceptor,判断系统上下文中是否存在压测标识,若是,则改写sql,将表名改写为带shadow后缀的影子表 |
线程池 | 可以利用阿里开源TransmittableThreadLocal来做父子线程之间传递参数,避免压测标识丢失 |
数据隔离
做压测流量区分的根本目的是在数据的持久化、查询时能做区分,即不让压测数据落入正常的业务存储介质中,常见的存储层数据隔离方案如下
存储层 | 实现方案 |
---|---|
redis | 针对key做特殊处理,加上特殊后缀,比如加上_shadow后缀,过期时间设置的短一些,避免redis容量暴涨影响正常业务 |
mysql 、mongo | 针对业务表加上_shadow后缀 |
日志 | 压测流量日志全部写入单独日志文件 |
当然一些针对查询场景的压测,可以考虑存储层不做该改造,直接让压测请求落到正常的业务存储表中,反正查询不会产生脏数据
接口分级、限流/降级
经过上述两步改造后,理论上压测链路就可以正常跑通。但压测的目的同时也是为了摸清系统极限处理能力。
在一个较高流量水位情况下,复杂的调用链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用,引起线上故障。因此我们需要对不稳定的弱依赖服务进行自动熔断,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩,因此有必要引入限流降级策略对自己系统,对下游系统做出一定保护,在必要时拒绝掉部分流量。
市面上常见的限流降级中间件有Sentinel、hystrix等,有兴趣的可以自己去了解下,但无论采用采用何种中间件做限流降级,其核心思路是不变的:那就是找到核心链路中的弱依赖服务,也就是要做好接口分级
举个例子,比如下单链路中有扣库存的接口调用,也有发短信提醒的接口调用,你会优先针对哪个接口做限流降级?很明显发短信这种调用是可有可无的,在系统压力较大时是可以被熔断降级的。同样的,双十一时,你会发现淘宝的退款退货服务可能要在双十一的第二天才能正常使用,这也是一样的道理。
做好接口分级后,再结合监控,针对不同接口配置不同的限流阈值,熔断阈值,即可达到在高流量水位下对系统的保护
压测中-关注核心监控、告警
压测是在线上进行的,因此我们有必要关注线上的核心监控指标,且监控的粒度最好能细到秒级,方便开发及时发现线上的异常
常见的一些核心指标如下
- 线程池情况(Dubbo线程池、业务自定义线程池等等)
- 服务CPU情况
- 服务内存使用率
- 接口响应时长、错误率
- 接口Qps
- JVM情况(fgc次数,ygc次数,fgc时长等等)
- 网络IO情况
- 服务错误日志数
- redis/mysql等存储层的qps、cpu、耗时情况、连接数等等
压测中-问题记录、及时止损
压测中碰到问题,往往都是由监考告警发现的(比如接口时长上升、qps上升都是正常的现象)。此时证明你的系统已经接近瓶颈了,要及时记录问题。如果问题比较严重(如服务CPU直接打满,接口耗时、错误率上升比较明显),此时要及时停止压测,避免对线上业务产生影响
压测后-问题改进
压测后自然是要产出压测报告,记录压测过程中发现的问题,记录系统各项核心指标情况,以及最重要的给出当前链路的预估最大Qps
针对压测中发现的问题,要进行优化、发现问题确又不优化,那又有什么意义。至于怎么优化,其中门道就多了,比如Jvm层面的优化,sql耗时优化,业务逻辑、流程上的优化,IO优化等等,都是对自己技术能力的挑战
实在优化不了的服务,或者来不及优化的服务,则可以考虑进行扩容处理,服务稳定性与成本对比来说还是更重要的
最后需要明确一点:压测的数据、报告是有时效性的,这个月你的业务可能支持了500QPS,下个月你的系统可能已经支持了2000QPS,随时业务不断发展,每一次的压测数据其实都是会快速过期的,每一次的压测最终都应该成为流程化的操作,方便后续不断进行日常压测,让压测成为发现问题、优化系统的工具
总结
压测是一个核心系统发展过程中必不可少的一步,盗用网上美团技术团队的一个压测落地流程图,来回顾整个链路压测的实施过程
其技术改造过程虽然复杂,但收益也是很明显的,压测能帮助我们走在业务发展之前发现一些系统层面的不稳定性因素,最终确保业务稳定发展。
文中提到的核心线路改造及落地流程可以说只是全链路压测的第一步,理想的全链路压测理应覆盖到整个业务系统中N条核心链路,同时其中的一些技术改造最好能够封装在相应中间件中,避免重复技术改造,整个压测方案,从压测数据模拟,到发压,到监控都应该成为一个自动化的流程,最终每个月、甚至每周进行压测,持续发现问题,持续迭代优化
希望个人对压测的浅见能够帮助到各位