诞生背景
伴随着京东业务的不断扩张,研发体系的系统也随之增加,各核心系统环环相扣,尤其是强依赖系统,上下游关系等紧密结合,其中一个系统出现瓶颈问题,会影响整个系统链路的处理性能,直接影响用户购物体验。
往年的618、双11大促备战至少提前3个月时间准备,投入大量的人力物力去做独立系统的线上压力评测,带来的问题就是各个性能压测团队工作量非常大,导致压测任务排期,压测的数据跟线上对比不够准确,各个强依赖系统上下游需要在压测中紧密配合,一不小心就会影响线上,有的在线下测试环境压测,压测出的数据更是跟线上差距太大,只能作为参考。
更重要的一个问题是各系统容量规划,每次大促前备战会必不可少的讨论话题就是服务器资源申请扩容问题,各团队基本都是依据往年经验和线上资源使用率给出评估量,提出一个扩容量需求,导致各个业务系统每次促销扩容量非常大。为了解决以上各种苦恼,2016年基础平台部整体牵头启动了ForceBot全链路压测(备战常态化)这个项目,此项目牵扯到所有京东研发体系团队,各系统必须改造识别压测过来的流量和线上正式流量进行区分标记特殊处理,不能因为压测流量影响正常用户体验和污染线上数据等工作,由于跨团队协作之多、跨系统协调改造等工作量非常大,挑战性可想而知!
能做什么
2016年主要实现了订单前的所有黄金链路流程高并发压测用户行为模拟,包括模拟用户操作:首页、登陆、搜索、列表、频道、产品详情、购物车、结算页、京东支付等。在黄金链路中有各种用户行为场景,比如一般用户首先访问首页,在首页搜索想要产品,翻页浏览,加入购物车、凑单、修改收货地址、选择自提等等。
各系统压测量依据往年双11峰值作为基础量,在此基础上动态增加并发压力;同时要区分对待两个大的场景,日常流量和大促流量。大促场景下抢购活动集中,交易中心写库压力最大,另外用户行为和日常有很大的反差,比如用户会提前加入购物车,选择满减凑单,集中下单等等场景。
2016年启用的链路较短,在2017年将实现订单后生产的全链路。
价值体现
ForceBot在2016年双11替代往年各系统独自优化、性能压测备战状态,目前所有的备战数据和各系统性能承载能力、资源规划等都由ForceBot给出直接数据作为依据,在军演压测过程中,秒级监控到压测源、压测中、京东所有的黄金链路系统、接口响应时间、TPS、TP99等数据,军演完成后提供丰富的压测报告,准确的找到各系统并发瓶颈。
同时也承担了内网单一系统的日常压测任务,开放给研发和压测团队,支撑京东所有的压测场景统一压测平台,对公司内压测资源的整合和提高利用率。
ForceBot技术架构
工欲善其事,必先利其器。全链路压测必须要有一套功能强大的军演平台,来实现自动化、全链路、强压力的核心目标。
第一代性能测试平台
基于Ngrinder做定制开发。Ngrinder是一个java语言开源的、分布式性能测试平台。它由一个controller、多个agent组成,在controller建立测试场景,分发到agent进行压力测试,压力源将压测数据上报给controller。Ngrinder基于开源的java负载测试框架grinder实现,并对其测试引擎做了功能提升,支持python脚本和groovy脚本;同时提供了易用的控制台功能,包括脚本管理、测试计划和压测结果的历史记录、定时执行、递增加压等功能。
我们根据京东的业务场景对Ngrinder进行了优化,以满足我们的功能需求。比如:提升agent压力,优化controller集群模式,持久化层的改造,管理页面交互提升等。Ngrinder能胜任单业务压测,但很难胜任全链路军演压测。分析其原因是controller功能耦合过重,能管理的agent数目有限。原因如下:
- controller与agent通讯是bio模式,数据传输速度不会很快;
- controller是单点,任务下发和压测结果上报都经过controller,当agent数量很大时,controller就成为瓶颈了。
说的通俗点,controller干的活又多又慢,整体压力提升不上去。尽管我们优化了controller集群模式,可以同时完成多种测试场景。但是,集群之间没有协作,每个controller只能单独完成一个测试场景。由此,我们着手研发全链路军演压测系统(ForceBot)。
ForceBot架构
新平台在原有功能的基础上,进行了功能模块的解耦,铲除系统瓶颈,便于支持横向扩展。
- 对controller功能进行了拆解,职责变为单一的任务分配;
- 由task service负责任务下发,支持横向扩展;
- 由agent注册心跳、拉取任务、执行任务;
- 由monitor service接受并转发压测数据给JMQ;
- 由dataflow对压测数据做流式计算,将计算结果保存在db中;
- 由git来保存压测脚本和类库。GIT支持分布式,增量更新和压缩
这样极大的减轻了controller的负载压力,并且提升了压测数据的计算能力,还可以获取更多维度的性能指标。
在此基础上,还融合进来了集合点测试场景、参数下发、tps性能指标等新特性。下面将重点介绍以下几个功能:
容器部署
为了快速的创建测试集群,Agent采用docker容器通过镜像方式进行自动化部署。这样做好处如下:
- 利用镜像方式,弹性伸缩快捷;
- 利用Docker资源隔离,不影响CDN服务;
- 每个Agent的资源标准化,能启动的虚拟用户数固定,应用不需要再做资源调度;
问题:Git测试脚步如何在各个Docker之间共享。
解决方案:采用外挂宿主机的共享磁盘实现
任务分配
用户在管理端创建一个测试场景,主要包括:压力源(分布在不同机房的压力机)、虚拟用户数、测试脚本、定时执行时间、process的jvm参数,压力源,启动模式,集合点设置,选择测试脚本。这个测试场景保存到db后,controller按照时间顺序,扫描发现有测试场景了,会根据标签算法寻找当前存活的空闲的agent资源,并计算agent的每个worker process启动的线程数。
任务分配时,要考虑集合点的计算,就是虚拟用户数在不同的时间点不断变化。目前有两种方式:毛刺型和阶梯型。毛刺型,就是在某个时间点有大量的请求瞬时访问业务系统,这样做可以测试系统的并发能力和吞吐量。阶梯型,就是在相等时间间隔,虚拟用户数等值递增。
比如:一个测试场景在今天9点执行10000个虚拟用户数,执行到9点01分要虚拟用户数变为20000个,到9点05分虚拟用户数又变为40000个,到9点07分虚拟用户数变为10000个。虚拟用户数像毛刺一样上下变化。
上面的表格就是对应表结构设计,每个压力线会按照顺序保存到db中。在这张表中,只记录的持续时间,具体的执行时间是由controller根据开始压测时间累加持续时间逐一计算出来。execType还包含了缓慢加压减压两种类型,覆盖了阶梯型,默认时间间隔为5秒。
controller要计算出每个间隔的执行时间点,递增的虚拟用户数。controller将计算结果分给每个agent,保存到db中,由agent定时来拉取。
动态加压减压目前只支持采用Agent的增减方式来实现。
问题:由于购物车是用户共享的,在压测下单过程中,同一个用户并发操作会出现冲突。
解决方案:为每个压测线程绑定使用不同的用户。在任务分配过程中,为每个process进程中的线程分配线程编号。不同的线程根据编号,按照映射规则,找到相应的压测用户。
心跳任务和下发
task service为agent提供了任务交互和注册服务,主要包括agent注册、获取任务、更新任务状态。
agent启动后就会到task service注册信息,然后每个几秒就会有心跳拉取一次任务信息。controller会根据注册的信息和最近心跳时间来判断agent是否存活。
agent拉取任务和执行任务过程中,会将任务状态汇报给task service。controller会根据任务状态来判断任务是否已经结束。
task service采用了GRPC框架,通过接口描述语言生成接口服务。GRPC是基于http2协议,序列化使用的是protobuf3, java语言版采用netty4作为网络IO通讯。使用GRPC作为服务框架,主要原因有两点:
- 服务调用有可能会跨网络,可以提供http2协议;
- 服务端可以认证加密,在外网环境下,可以保证数据安全。
Agent的实现
agent职责主要是注册,获取任务信息,更新任务状态,执行和停止worker process进程,同时上报压测数据。通信协议主要是GRPC。
- Agent启动后生成UUID作为当前Agent的唯一标识,并向Task Service进行注册。如果Agent进程挂掉重启后,会产生新的UUID,放弃原来的任务。
- Agent心跳线程每隔3秒从Task Service拉取任务。获取到任务后,会把任务扔到队列里面,并上报任务接收状态,不阻塞当前拉取线程。
- 任务执行线程从队列获取任务,如果是启动新的测试任务,则先从Git拉取任务所需的脚本文件和用户的自定义类库。然后根据jvm参数,设置并启动worker process进程,同时将任务的执行时间,虚拟用户数,集合点设置信息,压测脚本信息,以及脚本加载plugin所需要的classpath传递给worker process。在这里我们采用使Agent通过stdout的方式与Worker process对stdin的监听构成一个数据传输通道,采用Stream的方式将任务的所有信息和数据发送给Worker process,使Worker process可以获取到足够且结构化的数据以完成初始化的工作。
问题:一台物理机上多个Docker实例,如何减少更新测试脚步造成的GIT压力
解决方案:测试脚步存放到宿主机共享存储,更新前,按照测试任务编号进行文件锁,如果锁成功则调用Git更新。同一个测试任务就可以减少GIT压力。
问题:如何模拟在某一个瞬间压力达到峰值
解决方案:通过集合点功能实现,提前开启峰值所需足够数量的线程,通过计算确定各个时间点上不需要执行任务的线程数量,通过条件锁让这些线程阻塞。当压力需要急剧变化时,我们从这些阻塞的线程中唤醒足够数量的线程,使更多的线程在短时间进入对目标服务压测的任务。
数据收集和计算
实现秒级监控。数据的收集工作由monitor service完成,也是采用GRPC作为服务框架。agent每秒上报一次数据,包括性能,JVM等值。
monitor service将数据经JMQ发送给dataflow平台,进行数据的流式计算后,产生TPS,包括TP999,TP99,TP90,TP50,MAX,MIN等指标存储到ES中进行查询展示。
问题:为了计算整体的TPS,需要每个Agent把每次调用的性能数据上报,会产生大量的数据,如果进行有效的传输。
解决方案:对每秒的性能数据进行了必要的合并,组提交到监控服务。
业务系统改造
黄金流程业务
首期识别从用户浏览到下单成功的黄金流程,其包含的核心业务如下:
压测流量识别
压测流量是模拟真实用户行为,要保障在军演过程中不能污染线上各种统计等数据,比如:PV UV 订单量等,更不能影响正常用户下单购物体验。
首先要对用户、商品进行打标,以便于各个系统进行测试流量识别。针对下单压测,库存系统需要根据测试用户和商品提前准备好库存量。风控系统需要放行测试用户和商品的操作。
压测数据存储
业务系统识别出压测数据后,根据不同的场景,采用两种方式来存放压测数据。
- 打标数据并存储到生产库中,压测的数据能体现生产环境性能。定期清理测试数据。统计报表要过滤掉测试数据。改方案能利用现有资源,需要系统进行较多改造。
- 构造压测库环境,根据压测流量,把压测数据存放到压测库中进行隔离。改方案需要较多的资源,但系统改造量小。支付系统最大的改造困难就是银行接口的强依赖,不能用真实的银行卡扣款和支付,ForceBot的目标不是压银行接口,而是压自己本身的支付系统,所以京东这边支付团队目前是自己造了一个假银行,假接口,通过前端传递过来的压测标识,自动路由到假银行进行扣款支付;
ForceBot未来规划
ForceBot未来的路还很长,要做的优化、功能也很多,比如有:
- 无人值守智能压测,通过监控系统监控到各个被压系统的性能问题,如遇到系统瓶颈问题联动JDOS系统弹性扩容docker资源,扩容到一定量级后发现性能没有提升,停止此压测,由callgraph自动找到性能系统位置;
- 人工智能预言:根据大数据人工智能学习计算,在ForceBot中设定订单量目标值(秒级订单量、日订单量等),根据日常的军演数据自动计算出各链路系统将要承载的军演并发量,给出智能评估各系统的瓶颈所在,预测各系统的性能指标和瓶颈问题。
在线全链路压测,军演备战常态化,将是我们的终极目标。