中通快递作为国内知名综合物流服务企业,已连续5年稳坐行业市场份额榜首。受双11、618等大促活动影响,井喷式的业务流量对中通的系统稳定性提出了更高的要求,过去的压测方案已经无法满足业务发展的需求。测试环境等比缩放导致压测失真、庞大且复杂的系统链路梳理等都是棘手的问题,让我们一起看看中通是如何利用大促系统稳定性保障利器Takin来完成这项艰巨的任务的。
背景
目前在中通性能测试主要分为线上和线下压测两种方案,在反复实践过程中我们渐渐发现这两种方案都有着各自不足之处,且为压测工作带来了很多不便。以下就线上和线下压测的不足分析,谈谈中通是如何一步步改进压测方案并解决问题。
- 线下压测方案中的不足
在线下压测试过程中,为了减少与真实环境物理资源上的差异,公司采取的是针对CPU与内存进行等比缩放的策略,如果是计算密集型应用,线上环境总的CPU核数为80,线下压测环境总CPU核数为8核,则为10:1的线上线下等比缩放策略,如果是IO(目前只看内存)密集型应用,线上内存总量如果为80G,线下压测环境内存总量为8G,也同样是10:1的线上线下等比缩放策略。
很明显这种等比缩放策略在高TPS目标的压测场景下会失真,TPS目标越高,则失真越严重,因为我们并不能对网络、中间件、数据库等一系列的因子也同样做出等比缩放。
- 线上压测方案中的不足
在线上压测时,多以读接口为主,只有相当少量的写接口会做线上压测,这少量的写接口通常也需要被压应用的开发人员进行代码改造,以避免大量的压测数据对业务数据造成污染。
所以这种线上压测方式无法大范围应用,同时这种对代码硬改造的方式,也增加了压测成本与风险,导致大家通常不愿意面对线上压测,更不用提联合上下游一起进行线上全链路压测了,而这种不敢在线上大量压测的思维,也导致更多压测被放在线下以等比缩放的方式进行,其后果则是压测结果的失真,在2012年,某厂正是因为测试环境等比缩放压测,导致网络流量数据失真,引发了线上故障,才促使其下决心走上了线上全链路压测的道路。
- 引入技术解决方案
因为有上述问题,公司引入了线上全链路压测产品,其特点是使用压测JavaAgent探针以字节码方式植入应用,这样业务应用则无须硬编码,就可以自动识别和兼容压测流量,并进行分流,将缓存和存储数据存储到影子区域,实现物理隔离压测数据,避免造成生产数据污染,就技术方案上看,线上全链路压测产品最核心的功能其实就是两点:流量染色与保障数据安全,示意图如下:
全链路压测部署&核心配置
线上全链路压测agent安装部署
- 将pradar-agent.zip包上传到需要接入应用所对应的服务器/home/admin目录下,并直接解压.
- 修改对应应用的启动脚本( 通常在发布平台中修改),将修改后的后面这个命令添加到java -jar xxx.jar 的-jar之前( -javaagent:/home/admin/pradar-agent/agent/pradar-core-ext-bootstrap-1.0.0.jar -Dpradar.project.name=该应用应用名 )
- 重新启动应用
在pradar-web操作页面的应用管理页,查看是否成功上报:
agent安装完全成后,在具体实施时,如果压测入口是http接口,则在请求头中带上“User-Agent:PerfomanceTest”,如果入口是dubbo接口,则在AttachementArgs中带上“p-pradar-cluster-test:true”,agent会将这类请求自动识别为压测流量,它会将压测标识向下游应用传递,并将数据分流到应用所配置的影子资源中,例如redis的影子key、影子数据库(表)、rocketmq上配置的影子TOPIC及消费组等等,由此将压测数据与正式数据进行隔离,避免了压测数据对正式业务数据的污染。
- 主要影子资源的配置
缓存redis的影子资源,在探针识别为压测流量时,会自动对要写入或者查询的key前加上"PT_"前缀来进行数据隔离,而MQ的TOPIC与消费组影子资源,需要在公司的ZMS配置中心,按业务TOPIC与消费组名称,去新增出带"PT_"前缀的TOPIC与消费组名即可。数据库资源的配置会复杂一些,下面单独说明。
影子库配置如下图所示:
影子表配置如下图所示,都是把业务表名前加上“PT_”来表示为影子表,多个表使用逗号分隔:
- 挡板的配置
在线上压测时,有可能会触发到资金扣款或者短信发送等敏感方法,如果大量的压测触发了这类方法,轻则造成骚扰,重则发生严重的资损,类似这样的方法,我们则需要在梳理压测链路时进行识别,并为此类方法加上挡板(Mock),如下图示例,如此当压测探针识别到压测请求(有压测标)时,则会执行我们针对此方法所配置的Mock代码,如果是正式的业务流量(无压测标),则仍然会执行原来的短信发送方法而不受影响。
链路接入与压测流程
做线上全链路压测,很多人担心的一个问题就是,线上生产环境就这么直接压,不怕出问题么,那么除了进行错峰压测以外,中通压测团队为了安全有序的进行线上全链路压测,经过两期接入项目的摸索,已经形成了一整套保障安全压测的实施流程,流程图如下:
全链路压测可以大致分为三个阶段:
1.需求定义与链路梳理阶段;
2.测试环境部署及测试阶段;
3.线上压测及结果产出阶段。
- 需求定义与链路梳理
需求定义
- 明确压测的具体业务链路与范围边界
- 明确压测的目的,是达到指定性能指标,还是摸高进行容量规划
- 具体的性能指标,tps(每秒请求数)/rt(响应时间)/成功率/SA(以99%为例,指99%的请求响应时间都在设置的rt范围之类,也就是RT的达标率)
链路梳理
链路梳理是全链路压测最为重要也是最核心的环节,通常这个环节的质量,将影响全链路压测的整体实施效率。接下来详细说一下,链路梳理的目的步骤与需要拿到的关键信息
链路大图
首先需要梳理出链路调用大图,刚开始不需要太细,但需要对入口/出口/应用名/数据库/缓存/中间件/资金影响/邮件/短信等,类似这样的一些关键信息能梳理到,因为保密手册的原因,具体的链路图,这里就不放出了,可以用本文最上面的《压测流量链路示意图》进行脑补。
详细信息收集操作手册
根据上面梳理的梳路大图,进一步明确具体细节,需要收集如下信息:
- 应用基础信息与部署信息
- 链路模块-指的是这次需求所确定的压测链路
- 应用名-应用名称,在jvm中设置时-Dpradar.project.name的value内容
- 应用负责人-一般为应用的主开发人员
- 运维-可以进行agent安装包上传与安装,并查看agent相关日志的系统运维人员
- 测试负责人-此应用的测试人员
- DBA-可以进行数据铺底,影子库表创建,数据库性能监控的DBA人员
- 性能指标-本次压测的目标
- 应用的调用链类型与接口-指的是在全链路压测中,本应用在整个链路调用中所经过的接口方法名,以及对应的接口类型,在了解这个信息时,应该要了解清楚这些接口方法的作用与逻辑,以此判断出是否需要对其加挡板(mock),是否需要进行一些测试数据初始化的准备工作。
- 启动容器-例如tomcat或者jar方式直接启动
- 实例数量-主要是对测试环境与线上环境的实例实进行统计,需要所有实例全部安装agent,不然可能导致压测数据流入到正式的数据库表中。
- 应用入口信息与相关依赖信息
- 入口-压测的入口
- 数据库jdbc连接信息-主要是数据库的类型,连接的域名(或者IP),端口,数据库名称,用户名与密码等相关的信息。
- 用到的表-发起压测后,调用流经到该数据库时,会读哪些表,会写哪些表,数据逻辑是什么,都需要搞清楚,以方便判断怎么造出测试数据,是用影子库方式还是用影子表方式。
- 文件路径-是否会在读写文件的相关信息.
- redis预设值-发起压测后,调用流经redis的业务与数据逻辑,比如面单的单号是从redis中读取的,则我们可以根据压测量,在单号存放的redis中预设(铺底)一批测试单号数据,注意对于redis中预设测试数据,需要考虑过期时间,另外测试数据的key键是在正式的key值前加上PT_来作为影子key。
- mq的topic-如果涉及到mq,则需要建立影子topic与影子消费组,方法都是在正式的topic与消费组前增加PT_来作为影子topic与影子消费组,需要特别注意的是,影子topic/影子消费组与正式的topic/消费组都需要在同一个集群。
- es索引-在正式的es索引前,加上PT_作为影子索引.
- hbase-正式表前加上PT_作为影子表.
- 不良影响-在明细信息收集过程中,需要梳理出此应用是否会有产生实际的资金/电话短信邮件/网安数据上报等一系列,有可能因压测而造成的不良影响,针对这些不良影响的调用方法,则需要以加挡板mock的方式绕开。
至此,整个链路的业务,技术,数据信息都已经了解得基本清楚了,那么在这个基础上,则可以参考上一节中《全链路压测部署&配置》相关的内容,在测试环境将整个全链路压测环境给部署与配置妥当。
- 测试环境调试
全链路压测,向上追述,一般总是能找到一个页面或者APP入口,那么必然对应着一个http的接口,所以为了表示这个请求是全链路压测的影子请求,需要在http头中增加User-Agent:PerfomanceTest,如果入口就直接是一个dubbo入口的话,则在dubbo的Attachment Args里增加key:value为: p-pradar-cluster-test:true
当我们在测试环境观察到压测流量都按我们的预期,落入了相关的影子资源,而没有发生数据落入正式资源的情况后,我们可以在测试环境进行少量数据的压测,如果一切正常,我们就可以开始着手进入线上环境的压测流程了。
- 线上压测及结果产出阶段
准备阶段
- 提前准备线上必须的影子库表,铺底数据,影子topic/影子消费组建立等需要DBA与运维部门支撑的前期事项。
- 提前在pradar-web操作页面的应用管理页对应用的相关配置进行配置操作。
- 按照《中通-全链路压测上线计划模板.xlsx》编写上线计划,并召集相关人员(运维,DBA,开发,测试,项目PM)评审,提前约定灰度上线,全量上线,试跑压测用例,正式压测的相关时间节点。
灰度验证
将agent安装包上传到相关应用的其中一台机器上,如果有预发机,最好是上传到预发机器,然后由开发在发布平台中修改jvm配置,配置好agent相关的参数,重新启动灰度机器,观察12小时以上日志,是否正常。
全量上线与试跑
如果灰度没有问题,则通知运维,将agent安装在应用的所有机器,全量重启目标机器。
如果一切正常,则可以使用压测脚本进行线上试跑了,试跑方案应在上线计划中提前规划好:
正式压测
- 压测场景配置
注:压测试场景配置最好在灰度发布后,就开始进行
在pradar-web操作页面的系统流程中创建一个流程(一个入口只需要创建一次):
在操作页面的“业务活动”中创建一个业务活动,并与上面的“系统流程”进行关联
在“压测管理”->“压测场景”中,创建一个压测场景,在业务活动中,将上一步中创建的业务活动增加进去,可以增加多个业务活动,以表明同时压测多个活动的场景,如果有数据文件且数据不可以重复使用的情况,可以选择多个IP后,对此csv数据文件勾选“拆分”操作,最后还需要关注的是正式压测,需要使用“阶梯递增”模式,则您的Jmeter脚本中需要以"bzm-Concurrency Thread Group"方式创建线程组。
- 压测执行
确认了压测时间与相关人员后,编写压测计划,并通知到相关人员按计划执行,同时要特别注意压测入口域名是否受到CDN与防火墙的流量限制,如果有,需要提前找运维与网络的支撑人员将压力机IP加入白名单。
一切准备就绪,则按计划执行压测即可,在“压测场景”中点“启动”,正式发起压测。
漏数检测
除了一般性能测试都要进行的监控以外,进行全链路线上压测试时,最大的区别是我们大量使用了影子数据库表,影子数据库表用于与正式数据库表进行测试数据的隔离,且压测数据我们都会加上识别标识,比如PT开头的订单号都是压测数据,但因为各种原因,大量的压测数据可能会导致部份或者全部压测数据被错误的写入了正式数据库表,从而污染了真实环境的数据,导致各种生产故障,因此有必要实时的检测是否有测试数据被错误的写入了正式数据库表,以便及时的停止压测行为,并快速对进入正式库的错误数据进行清洗纠正,将损失降到最低。为此,我们自己基于对数据库binlog的监听,设计了一套能实时监控压测数据对生产数据造成影响的工具,原理图如下:
全链路压测实践的思考
使用压测探针方式进行线上压测以来,我们已经在订单,运单,面单等多个业务共62个应用中进行了接入,成功支持了双11&618大促与淘宝&拼多多等大流量联合线上压测的场景,虽然初步能解决原来压测中存在的问题,但也引入了一些新的问题。
- 组织与工作模式问题
先看看某大厂BU进行全链路线上压测的简化版组织及工作模式架构图:
全链路压测系统接入几乎牵扯到整个产研团队的各个方面,需要开发、测试、运维及供应商等团队充分配合协同工作。
图一中某大厂由于订单全链路压测属于公司级重点项目,由上至下的推动相关系统改造和统一协调资源,各项目由开发负责人挂帅,开发、测试、运维相互配合,性能团队属于支撑团队,负责压测方案评审、工具支持、压测问题记录与答疑。
图二中我们的模式,全链路压测属于部门级项目,由性能测试团队负责主导,对接各方推动接入工作,其他相关方属于配合工作人员,性能测试团队需要协调各方资源,工作难度较高。
- 其它问题
手工操作过多,自动化程度太低,比如探针的版本控制与部署,施压机的自动创建与分配等。
流程推进线下化,没有形成统一管理的配置项、检查项、评审等流程在线化推进。
测试脚本与测试数据在线化统一管理及可复用程度底。基本靠压测人员自行维护。
压测所积累的结果数据,无法在线形成压测基线自动化对比,无法达成压测结果在时间线上的可视化统计与分析。
最后
中通通过引入全链路压测,的确解决了原来压测环境等比缩放压测的失真问题,但是,在面对整个在订单,运单,面单等多个业务共62个应用的压测,单从上下游数据层面交互就是一项复杂的工作,另外还需要各个环节的人员协作等、工作量及复杂度是可想而知的。因此,此项工程并非一天两天能全部解决的,路漫漫其修远兮,后面我们还将通过发起性能专项创新活动,将公司性能测试总体价值推向更高阶的层次。