欢迎关注公众号 【11来了】 ,持续 中间件源码、系统设计、面试进阶相关内容
在我后台回复 「资料」 可领取
编程高频电子书
!
在我后台回复「面试」可领取硬核面试笔记
!文章导读地址:点击查看文章导读!
感谢你的关注!
线上全链路压测
为什么需要全链路压测?
在大促、双十一开展之前,需要对系统进行 压测 ,来观测系统瓶颈,掌握系统的最大可承载压力,避免在活动开始时,由于性能问题导致系统无法承受大流量的冲击,导致服务崩溃,带给用户体验不佳
在压测的时候,如果只是对单接口进行压测,无法更真实地模拟真实场景,在活动真正开始之后,活动相关接口都会同时收到大量请求,在其他接口的影响下,单接口的 QPS 势必会下降
因此,为确保更好的模拟真实场景,在大促前都会进行全链路压测,对活动相关接口统一压测,观测系统可承载最大压力,保证服务核心链路的稳定
读完之后可以了解:
- 什么是全链路压测?
- 为什么要进行线上的全链路压测?
- 如何区分压测流量和线上流量?
- 什么情况下会导致压测流量标记的中断?
- 如何在不影响线上服务的前提下,完成压测?
为什么要线上全链路压测?
为了更好测量 线上服务可承受的最大压力 ,需要在 线上 完成全链路压测
因为在测试环境,无法做到完全与线上服务相同的机器数量、机器配置,并且有极大可能出现线上、线下的某个服务配置不同,因此线下的压测结果无法完全体现出线上服务的承载能力
线上全链路压测难点?
线上全链路压测要在 线上环境 进行,因此面临着几个问题需要解决:
- 如何区分压测流量与线上真实流量?
- 如何隔离压测数据与线上数据?
- 压测数据如何构造?
接下来针对这几个问题,逐个进行介绍
1、如何区分压测流量与线上流量?
区分方式: 通过 URL 或者在 Header 中添加上压测标记即可区分
区分方式很简单,但是在业务系统中,多个系统之间互相调用,因此在调用的时候,也要保证 压测标记不中断 ,一旦压测标记传递中断,就会导致线上数据被污染,后果较严重
压测流量区分具体实现:
对于压测流量的区分、标记传递的处理,一般有两种方式:
- 业务系统改造: 在业务系统中,添加 if 判断,对标记进行传递和区分。该方式业务侵入性较大
- 中间件改造: 在中间件中,当业务系统收到 HTTP 请求后,负责压测流量区分的中间件内部的拦截器先对请求进行拦截,拿到请求中的压测标记,放在 ThreadLocal 中,在进行其他服务调用时,将压测标记放入 HTTP 的 header 或者 Dubbo 的 RpcInvocation 来传递压测标记
由于业务系统改造成本大,并且出现改造遗漏的可能性大,因此一般不会选择该方式,而是会对中间件进行改造,完成压测流量的区分和压测标记的传递
压测标记传递过程中的问题:
在压测标记传递过程中,可能会出现标记传递失败的问题,因为标记是存储在 ThreadLocal 中,直接使用 JDK 原生的 ThreadLocal 的话,子线程无法继承父线程的变量,因此会出现压测标记丢失的情况,有两种解决方案:
- InheritableThreadLocal: 当使用
new Thread()
方式创建线程时,使用 InheritableThreadLocal 可以让子线程继承父线程的变量 - TransmittableThreadLocal: 当使用 线程池 的方式执行异步任务时,使用 TransmittableThreadLocal 来完成父子线程的变量继承
其中 TransmittableThreadLocal 是 Alibaba 开源的,由于 InheritableThreadLocal 无法处理线程池的场景,因此 TransmittableThreadLocal 作为 InheritableThreadLocal 的扩展,可以处理线程池场景下的变量复制问题
工作原理为:
TransmittableThreadLocal
通过拦截线程池中的任务提交和执行过程,在任务执行前,复制父线程的上下文变量到线程池中的工作线程
2、如何隔离压测数据与线上数据?
隔离压测数据与线上数据,分为几种情况:
- 落盘数据的隔离
- 中间数据(Redis、MQ)的隔离
- 第三方接口调用的隔离
落盘数据和中间数据的隔离:
对于落盘数据来说,隔离分为 物理隔离 和 逻辑隔离
如果对于所有存储服务都使用物理隔离比较占用机器资源,成本大,因此不作考虑
逻辑隔离成本低,因此可以选用逻辑隔离,并对少量需要落盘的数据使用物理隔离,减少机器使用成本,逻辑隔离与物理隔离相结合
业务系统中的数据可以分为真正落盘数据和中间数据:
- 真正落盘数据:需要存储在 DB 中的数据,使用 物理隔离 ,建立 MySQL 的影子库来存储压测数据,不会对线上数据造成影响。通过改写访问 MySQL 的代理层,来区分压测还是线上请求,分别将流量分发到线上 MySQL 和影子库中
- 中间数据:在 Redis、MQ 中的数据,可以设置较短的过期时间,自动失效
第三方接口调用的隔离:
在压测中,不需要真正去调用第三方接口,比如短信接口需要付费,在压测时,没有必要真正去调用,同样可以通过检查压测标记,来判断请求是打在真实第三方接口还是请求返回 Mock 数据
这里 Mock 数据的返回有两种方式:
- 创建单独 Mock 服务:该方式下,由于压测请求量大,可能 Mock 服务也需要进行扩容,较为麻烦
- Mock 数据的判断逻辑放在业务系统内部:将 Mock 数据的返回判断逻辑直接包含在业务系统内部,这样就不需要考虑 Mock 服务的性能问题
3、压测数据如何构造?
压测数据的构造有两种方式:
- 手动构造:适合数据量小,项目发展初期,但是手动构造数据较为复杂
- 数据构造平台:适合数据量大,提升数据构造效率,减少错误率,但是数据构造平台的研发需要成本
如果成本允许,可以自研数据构造平台
通过数据构造平台,可以对线上数据进行脱敏,并且 Dump 到数据池中,当需要进行压测时,将压测数据导入影子库,并导出压测所需参数文件,通过执行引擎来完成执行压测
4、压测的告警
对于压测的告警和干预,有手动和自动干预两种方式,当系统压力达到一定水位之后,就需要干预压测流量,避免线上服务压力过大,影响正常用户使用
手动干预往往存在 滞后性 ,因此可以通过定义压测阈值,达到阈值后,自动进行干预,避免影响正常线上服务