全链路压测平台主要有两个核心的也是最顶级的要求:全业务,全链路。这导致了,必须线上搞压测,必须用线上的真实数据搞压测。那么线上搞就容易搞出事情,所以技术含量还是要有的,还是很高的。
一、压测核心技术 1.1 业务模型梳理首先应该明确的是:全链路压测针对的是现代越来越复杂的业务场景和全链路的系统依赖。所以首先应该将核心业务和非核心业务进行拆分,确认流量高峰针对的是哪些业务场景和模块,针对性的进行扩容准备,而不是为了解决海量流量冲击而所有的系统服务集群扩容几十倍,这样会造成不必要的成本投入。
1.2 数据模型构建数据构建和准备,应该考虑这几点问题:
1、数据的真实性和可用性可以从线上环境(生产环境)完全移植一份当量的数据包,作为压测的基础数据,然后基于基础数据,通过分析历史数据增长趋势,预估当前可能的数据量;
据说美团的做法是:
- http服务的,通过nginx日志把请求内容导出来,处理一下,放到数据库池子里。
- rpc服务的,通过美团内部已有的rpc框架录制功能,把请求数据导出来,处理一下,放到数据库池子里
然后压测流量从数据库池子里来
2、数据脱敏(数据处理)基于生产环境的全链路压测,必须考虑的一点是不能产生脏数据,以免对生产造成影响,影响用户体验等,因此在数据准备时需要进行数据脱敏。
3、数据隔离同样,为了避免造成脏数据写入,可以考虑通过压测数据隔离处理(根据测试标识区分),落入影子库,mock对象等手段,来防止数据污染;另外比如http请求,可以直接将测试标识加在header里(不污染代码,仅测试引擎添加即可),这样可以把隔离出来的(带测试标识的)流量,只访问隔离出来的服务器,这样就不会污染整个线上服务器集群。
二、为什么需要容量规划?容量规划的目的在于让每一个业务系统能够清晰地知道:什么时候应该加机器、什么时候应该减机器?双 11 等大促场景需要准备多少机器,既能保障系统稳定性、又能节约成本。
容量规划一般分为四个阶段:
-
业务流量预估阶段:通过历史数据分析未来某一个时间点业务的访问量会有多大;(通过预测系统来实现)
-
系统容量评估阶段:初步计算每一个系统需要分配多少机器;
-
容量的精调阶段:通过全链路压测来模拟大促时刻的用户行为,在验证站点能力的同时对整个站点的容量水位进行精细调整;
-
流量控制阶段:对系统配置限流阈值等系统保护措施,防止实际的业务流量超过预估业务流量的情况下,系统无法提供正常服务。
通过合适的预测算法和丰富的历史数据,通常能够比较准确地预估业务的访问量。即使在第一阶段预估的业务访问量跟实际的存在误差,通过第四阶段的流量控制也能够确保站点始终处于良好的服务状态。做完业务访问量的预估之后,容量规划进入第二阶段,为系统进行容量的初步评估。如何通过精准的容量评估,用最小的成本来支撑好预估的业务量是这个阶段的核心问题。要计算一个系统需要多少台机器,除了需要知道未来的业务调用量之外,还有一个更重要的变量,就是单台机器的服务能力。获取单台机器的服务能力在公司是通过单机压测的方式来获取。单机压测既需要保证环境的真实性,又要保证流量的真实性,否则获取到的单台机器服务能力值将会有比较大的误差,影响到整个容量规划的准确性。
生产环境进行单台机器压力测试的方式主要分为 4 种:
1、模拟请求:通过对生产环境的一台机器发起模拟请求调用来达到压力测试的目的。
模拟请求的实现比较简单,也有非常多的开源或者商业工具可以来做请求模拟,比如 apache ab、webbench、httpload、jmeter、loadrunner。通场情况下,新系统上线或者访问量不大的系统采用这种方式来进行单机压测。模拟请求的缺点在于,模拟请求和真实业务请求之间存在的差异,会对压力测试的结构造成影响。模拟请求的另一个缺点在于写请求的处理比较麻烦,因为写请求可能会对业务数据造成污染,这个污染要么接受、要么需要做特殊的处理(比如将压测产生的数据进行隔离)
2、复制请求:通过将一台机器的请求复制多份发送到指定的压测机器
为了使得压测的请求跟真实的业务请求更加接近,在压测请求的来源方式上,我们尝试从真实的业务流量进行录制和回放,采用请求复制的方式来进行压力测试。请求复制的方式比请求模拟请求方式的准确性更高,因为业务的请求更加真实了。从不足上来看,请求复制同样也面临着处理写请求脏数据的问题,此外复制的请求必须要将响应拦截下来,所以被压测的这台机器需要单独提供,且不能提供正常的服务。请求复制的压力测试方式,主要用于系统调用量比较小的场景。
3、请求转发:将分布式环境中多台机器的请求转发到一台机器上
对于系统调用量比较大的场景,我们有更好的处理办法。其中的一种做法我们称为请求的引流转发,公司系统基本上都是分布式的,通过将多台机器的请求转发到一台机器上,让一台机器承受更大的流量,从而达到压力测试的目的。请求的引流转发方式不仅压测结果非常精准、不会产生脏数据、而且操作起来也非常方便快捷,在大公司也是用的非常广泛的一种单机压测方式。当然,这种压测方式也有一个前提条件就是系统的调用量需要足够大,如果系统的调用量非常小,即使把所有的流量都引到一台机器,还是无法压测到瓶颈。
4、调整负载均衡:修改负载均衡设备的权重,让压测的机器分配更多的请求
与请求引流转发的方式类似,最后一种压测方式同样是让分布式环境下的某一台机器分配更多的请求。不同的地方在于采用的方式是通过去调整负载均衡设备的权重。调整负载均衡方式活的的压测结果非常准确、并且不会产生脏数据。前提条件也需要分布式系统的调用量足够大。
有了预估的业务访问量,也知道了系统单台机器的服务能力,粗略的要计算需要多少台机器就非常简单了。
最小机器数 = 预估的业务访问量 / 单机能力。
三、压测核心技术全链路压测基本上和Jmeter没关系了,因为Jmeter用的是BIO,像美团用的是NIO,阿里的PTS也用的NIO,好处不解释了。 采用的方式有:
- 使用一些脚本语言如:Python、Ruby 等,读取线上日志构建请求,用多线程模拟用户请求进行压测
- 类似Netty NIO 框架 + apache ab 组合模式
- 采用Twitter/iago、 Gatling、Grinder、Locust等开源压测工具
- 大厂都会自主研发压测平台,避开了开源工具的一些问题
一般会采用 InfluxDB 来完成数据的聚合工作,所有聚合指标都是一行 SQL 搞定,非常快速。或基于开源二次开发的监控,比如CAT,Falcon,Skywalking等。
大型公司在有支付场景,也就是支付相关的各个服务(订单,派送,入库出库等很多),相连密切,这样的场景比较复杂,还是高并发,自然对性能测试有高要求。同时支付场景,是会改变库的数据的,这也要求线上测试必须做数据隔离,这也是全链路压测的核心。
五、压测实战全链路压测模拟大促真实流量,串联线上全部系统,让核心系统同时达到流量峰值:通过全链路压测这一手段,对线上系统进行最真实的大促演练,获取系统在大压力时的表现情况,进而准确评估线上整个系统集群的性能和容量水平。全链路压测系统中对于性能测试主要有线下单系统单接口、线上单系统以及线上全链路压测等手段,通过不同维度和颗粒度对接口、系统、集群层面进行性能测试,最终保障系统的稳定性通过全链路压测。
- 验证大促峰值流量下系统稳定性
- 容量规划
- 进行强弱依赖的划分
- 降级、报警、容灾、限流等演练
- …………
说到全链路压测,业内的淘宝、京东都都已有很成熟的技术,主要就是压测流量的制造、压测数据的构造、压测流量的识别以及压测数据流向的处理;直接看下有赞压测的整体设计:
大流量下发器:其实就是模拟海量的用户去使用我们的系统,提供压测的流量,产生大促时的场景和流量;
数据工厂:构造压测链路中用户请求的数据,以及压测铺底的数据、数据清洗、脱敏等工作;
压测平台负责管理压测脚本和压测请求数据,从数据工厂获取压测数据集,分发到每一个压测 agent 机器上,agent 根据压测脚本和压力目标对线上机器发起请求流量,模拟用户的查看商品-添加购物车-下单-支付等行为,线上服务集群识别出压测的流量,对于存储的读写走影子存储。这里就要说到,线上压测很重要的一点就是不能污染线上的数据,不能让买卖家有感知,比如压测后,卖家发现多了好多订单,买家发现钱少了。所以,线上服务需要能够隔离出压测的流量,存储也需要识别出压测的数据,下面看一下压测流量和压测数据存储的隔离方案。
5.2 流量识别要想压测的流量和数据不影响线上真实的生产数据,就需要线上的集群能识别出压测的流量,只要能识别出压测请求的流量,那么流量触发的读写操作就很好统一去做隔离了,压测流量的识别:
全链路压测发起的都是Http的请求,只需要要请求头上添加统一的压测请求头,具体表现形式为: Header Name:X-Service-Chain; Header Value:{"zan_test": true}
Dubbo协议的服务调用,通过隐式参数在服务消费方和提供方进行参数的隐式传递,表现形式为: Attachments:X-Service-Chain; Attachments Value:{"zan_test": true}
通过在请求协议中添加压测请求的标识,在不同服务的相互调用时,一路透传下去,这样每一个服务都能识别出压测的请求流量,这样做的好处是与业务完全的解耦,只需要应用框架进行感知,对业务方代码无侵入。
5.4 中间件- NSQ:通过NSQMessage中添加jsonExtHeader的KV拓展信息,让消息可以从Producer透传到Consumer端,具体表现形式为:Key:zan_test;Value:true
- Wagon:对来自影子库的binlog通过拓展消息命令(PUBEXT)带上压测标记{"zantest": true}
异步调用标识透传问题,可以自行定制 Runnable 或者定制线程池再或者业务方自行定制实现。
5.6 数据隔离通过流量识别的改造,各个服务都已经能够识别出压测的请求流量了,那么如何做到压测数据不污染线上数据,对于不同的存储做到压测数据和真实的隔离呢,主要有客户端 Client 和 Proxy 访问代理的方式实现,下面就看一下数据隔离方案:
Proxy 访问代理隔离
针对业务方和数据存储服务间已有 Proxy 代理的情况,可以直接升级改造 Proxy 层,存储使用方完全无感知,无侵入,下面已 MySQL 为例,说明 Proxy 访问代理对于压测数据隔离的方案;
业务方应用读写 DB 时,统一与 RDS-Proxy(介于 MySQL 服务器与 MySQLClient 之间的中间件)交互,调用 RDS-Proxy 时会透传压测的标记,RDS 识别出压测请求后,读写 DB 表时,自动替换成对应的影子表,达到压测数据和真实的生产数据隔离的目的。ElasticSearch、KV 对于压测的支持也是通过 Proxy 访问代理的方式实现的。
客户端 SDK 隔离
业务应用通过 Client 调用存储服务时,Client 会识别出压测的流量,将需要读写的 Table 自动替换为影子表,这样就可以达到影子流量,读写到影子存储的目的。Hbase、Redis等采用此方案实现。
数据偏移隔离
推动框架、中间件升级、业务方改造,难免会有遗漏之处,所以对于压测的数据统一做了偏移,确保买卖家 ID 与线上已有数据隔离开,这样即使压测数据由于某种原因写入了真实的生产库,也不会影响到线上买卖家相关的数据,让用户无感知;
这里要说一下特殊的周期扫表应用的改造,周期性扫表应用由于没有外部触发,所有无法扫描影子表的数据,如何让这样的 job 集群整体来看既扫描生产库,也扫描影子库呢?
解决思路是,部署一些新的 job 实例,它们只扫描影子库,消息处理逻辑不变;老的 job 实例保持不变(只扫生产库)。
七、压测实施流程要想模拟大促时线上真实的流量情况,首先需要确认的就是压测场景、链路,压测的目录,以及压测的流量漏斗模型:
- 压测的链路:根据公司具体业务具体分析,有赞属于电商类型公司,大促时候的峰值流量基本都是由于买家的购买行为导致的,从宏观上看,这样的购买行为主要是店铺首页-商品详情页-下单-支付-支付成功,我们把这一条骨干的链路称为核心链路,其实大促时主要就是保证核心链路的稳定性
- 压测链路的漏斗模型:线上真实的场景案例是,100 个人进入了商家的店铺首页,可能有 50 个人直接退出了,有 50 个人最终点击进入了商品详情页面,最终只有 10 个人下了单,5 个人真正付款了,这就是压测链路的漏斗模型,也就是不同的接口的真实调用比例;实际的模型制定会根据近7天线上真实用户的行为数据分型分析建模,以及往期同类型活动线上的流量分布情况,构建压测链路的漏斗模型
压测的场景:根据运营报备的商家大促活动的计划,制定大促的压测场景(比如秒杀、抽奖等),再结合近七天线上流量的场景情况,综合确定压测的场景
压测目标:根据运营报备的商家大促预估的PV和转换率情况,结合去年同期线上流量情况和公司业务的增长速率,取大值作为压测的目标。
7.2 数据工厂前面我们已经介绍了如何确定压测的目标、场景、链路,那么压测的数据怎么来尼,这就是数据工厂登场的时候了,下面就介绍一下压测的数据工厂
有赞的压测数据工厂主要负责,压测铺底数据的准备、压测请求数据块的生成;
- 数据导入 从生产数据库按需过滤,导入压测铺底需要的数据到大数据集群的hive表中。
- 数据处理 在hive表中,对导入的数据进行脱敏和清洗,清洗的目的是保证压测的数据可用性,比如保证铺底商品库存最大、营销活动时间无限、店铺正常营业等。
- 数据导出 对hive标中已经处理完成的数据,导出到已经创建好的影子库中,需要注意的是同一实例写入数据的控制(因为影子库和生产库同实例),写入数据的binlog过滤。
gatling 原生支持 json、csv、DB 等方式的数据源载入,我们采用的压测数据源是 json 格式的,那么如此海量的压测源数据,是通过什么方式生成和存储的尼,我们的实现还是依托于 DP 大数据平台,通过 MapReduce 任务的方式,按需生成我们压测请求需要的数据集
- 从各个业务线的表中获取压测场景整个链路所以接口请求需要的参数字段,存到一张创建好的压测数据源宽表中
- 编写MapReduce任务代码,读取压测数据源宽表数据,按压测的接口请求参数情况,生成目标json格式的压测请求数据块文件到HDFS
- 压测时,压测引擎自动从HDFS上拉取压测的请求数据块
压测工具的实现的功能:
- 测试脚本:负责测试脚本的管理
- 数据集:负责压测请求数据集的管理,目前主要有两种数据集上传模式:直接上传数据包和 hadoop 集群数据源路径上传。第二种上传模式,只需要填写数据源所在的 hadoop 集群的路径,maxim 平台会自动去所在路径获取压测数据集文件
- 测试 job:负责测试任务的管理,指定压测 job 的脚本和脚本入口,以及压测数据集等
- 压测注入器:负责展示压测注入机器的相关信息
- 压测报告生成:压测报告的生成,直接用的 gatling 原生的报告生成功能
ApacheBench:Apache服务器自带,简单易用,但不支持场景编排、不支持分布式,二次开发难度较大 JMeter:JMeter支持上述很多特性,如分布式、良好的压测报告等,但其基于GUlI的使用方式,使得当我们的压测场景非常复杂并包含很多请求时,使用上不够灵活;此外在流量控制方面的支持也一般 nGrinder:基于Grinder二次开发的开源项目,支持分布式,测试报告良好,但和JMeter一样,在场景编排和流量控制方面支持─般 Gatling:支持场景编排、流量控制、压力控制,测试报告良好,且提供了强大的DSL(领域特定语言)方便编写压测脚本,但不支持分布式,且使用Scala开发,有一定开发成本。
博文参考