田忠波(云帆)
1. 项目背景
早期,业务线同事都是使用公司内部基于 Jmeter 实现的压测系统进行压测。在使用过程中,发现压测数据准备起来很麻烦,而且自己造的数据总感觉不真实,丰富度不足。要解决这个问题,最好的办法是使用真实的线上流量进行压测。于是,一个新的需求提到了我们基础应用组 —— 提供一个平台,方便业务同学进行线上流量录制和回放。基于这个需求,我们展开了调研和选型工作,最终把目光锁定在了 GoReplay 上。
2. 技术选型
2.1 选型介绍
我们从功能性和知名度两个维度进行调研,首先功能上一定要能满足我们的需求,其次要有一定的知名度,知名度较高的软件,理论上不会有大的坑。从这两个维度出发,我们调研出了下面几个工具。
第一个是选型是阿里开源的工具,全称是 jvm-sandbox-repeater,这个工具实际上是开源项目 JVM-Sandbox 的一个插件。基于动态字节码增强对目标方法进行拦截,实现流量录制功能。
第二个选型是 GoReplay,基于 Go 语言实现,底层依赖 pcap 库提供流量录制能力。著名的抓包工具 tcpdump 也依赖于 pcap 库,可以把 GoReplay 看作极简版的 tcpdump。
第三个选型是 Nginx 的流量镜像模块 ngx_http_mirror_module,基于这个模块,可以将流量镜像到一台机器上,实现流量录制。
第四个选型是阿里云云效里的子产品——双引擎回归测试平台,从名字上可以看出来,这个系统是为回归测试开发的。而我们需求是做压测,所以这个服务里的很多功能我们用不到。
2.2 选型对比与选择
上面几个选型各有各的优缺点,详细对比如下:
经过一番对比试用后,我们最终选定 GoReplay 作为流量录制和回放工具。尽管这个工具的开发语言与公司的技术栈不相符,但胜在其功能丰富,简单易用,可以完全满足我们的需求。同时考虑其具备一定的知名度,理论上不会有太大的问题,因此作为选型是合适的。
3. 落地实践
3.1 最小可用系统
初期,我们的目标是快速搭建一个基本可用的流量录制和回放平台,可满足业务线日常的压测需求。因此,我们当时选择用 Java 编写了一个简单的管理后台,使用 GoReplay 提供底层的流量录制和回放能力。由于 GoReplay 只是一个命令行工具,如何把这个工具和我们的管理后台进行整合,是一个要考虑的问题,我们的解决办法是加个中间层。中间层用 shell 脚本实现。中间层负责掌控 GoRepaly 生杀大权,统计录制数据,上传录制数据,回传录制状态等。
在具体实践过程中,我们也碰到了一些头疼的问题,最大的问题莫过于中间层的实现了。我们的中间层需要做的事情比较多,选用 shell 作为开发语言,给开发造成了比较大的麻烦。首先,我们对 shell 语法不是很熟悉,基本是边开发边查文档。其次,shell 调试不方便,基本上只能通过日志法查找问题。最后,对于像 JSON 这样数据结构,用 shell 处理起来很麻烦,全靠拼接字符串完成。对于 shell 开发,这里给大家推荐一个开源的 shell 代码分析工具 shellcheck,可以帮助分析代码的坏味道。按照工具提示的推荐写法,能够规避掉一些奇奇怪怪的问题。
3.2 项目一期
3.2.1 基本情况
从这一期开始,我们把项目名称改掉了,从流量录制与回放平台改成了流量平台,我们想做的事情不局限于压测,还期望落地更多的场景。这里特地说明一下,避免大家产生误解。
最小可用系统运行了一段时间后,我们根据用户的反馈对系统进行了一些改进和增强。本期主要涉及到两个方面的改动:
对 GoReplay 进行二次开发,支持 Dubbo 录制与回放
落地新的场景,对流量对比测试场景进行支持
经过改进增强后,本期架构图如下:
以流量录制为例,这里说明一下具体的工作流程。
压测平台发出流量录制请求
管理后台收到请求后,生成录制命令,并通过运维平台进行转发
运维平台下发命令到指定的机器上
gorctl 脚本被调起,根据接收到的参数拼接 GoReplay 命令,并调起 GoReplay,最后回传“开始录制”事件
GoRepaly 录制结束自动退出,gorctl 脚本恢复运行,分析和上传录制数据,最后将“录制结束”状态回传给管理后台
管理后台判断所有机器都回传了结束状态后,将录制任务状态置为结束
3.2.2 Dubbo 录制支持
由于 HTTP 流量录制与回放给用户带来了显著的价值,压测准备时间大大缩短了。因此,对 Dubbo 协议的支持也提上了日程。
在具体实现上,我们基于 GoReplay 的插件机制,使用 Java 语言编写了 Dubbo 的流量解析与回放逻辑。可能大家比较好奇,GoReplay 是 Go 语言编写的,怎么做到与 Java 语言编写的插件一起工作呢? 在揭晓答案之前,我们先看一下怎么启用 Java 插件。
gor --input-raw :80 --middleware "java -jar xxx.jar" --output-file request.gor
通过 middleware 参数可以传递一条命令给 GoRepaly,GoReplay 会拉起一个进程执行这个命令。在录制过程中,GoReplay 通过获取进程的标准输入和输出与插件进程进行通信。数据流向大致如下:
了解 GoRepaly 的插件机制后,我们就可以编写 Dubbo 协议的解码模块了。实际上我们并不需要重头开始写,在 Dubbo 框架中,消息体的解析逻辑包含在 DecodeableRpcInvocation#decode(Channel, InputStream) 方法中,我们只需要按需定制即可。由于这里面很多内容是和 Dubbo 相关的,这里就不展开了。
3.2.3 开花结果,落地新场景
初期,我们的流量录制与回放主要用于压测。系统稳定后,我们也在探索新的使用场景,并落地了流量对比测试。流量对比测试项目是由 QA 团队主导,我们提供基础的录制能力。其原理是把线上流量录制一份下来,由重放器同时重放到线上和预发环境,再由比对模块来检测两个环境返回的结果是否一致。如果不一致,说明预发环境的代码存在问题。工作示意图大致如下:
3.3 项目二期
经过上一期的迭代,我们的流量平台在功能上已经能够满足内部需求了。但对于项目维护人员来说,项目中仍然由很多需要改进的地方。比较突出的有两点:
命令经过运维平台中转,时间变长不说,还会偶发性失败。一旦失败就需要投入人力排查,同时还会阻塞压测工作
GoReplay 的中间层是用 shell 脚本写的,几百行的脚本维护起来也很痛苦,继续改进
我们在项目二期中对这两个痛点进行了针对性处理。首先我们是用 Go 语言重写了中间层逻辑,命名为 gor-deamon,即 GoReplay 的守护服务。守护服务通过定时发送心跳包与管理后台保活,同时暴露出 HTTP 接口供外部调用,这样守护服务可以直接接收来自管理后台的命令,不用再绕道运维平台。改造后的架构图如下:
经过改造后,流量平台解除了对运维平台的依赖,不管是新需求还是问题排查都可以在团队内部解决掉,不用协调外部资源了。其次,命令下发稳定性也有所提升,改进后的代码上线3个月未出现过一次命令下发失败的情况,保障了压测工作稳定进行。最后,录制或压测命令可以瞬间下发到目标机器上,不会像以前需要等几秒钟才能看到状态更新。
另一方面,用 Go 语言重写后的中间层维护起来也简单了很多,比如像 JSON 这样的数据结构不用通过拼接字节串来解决了。一旦有新需求,也可以快速迭代支持,开发体验好了很多。
4. 项目价值
截止到发稿时间,项目上线正好满一年。在这一年里,总共有6个应用接入使用,共进行了约90次录制,380次回放。基本的使用数据没有太多好说的,对于压测平台而言,其最核心的价值是帮助业务发现性能问题,这一年我们也发现了一些问题。其次,在效率提升方面,新压测平台取得了很好的成绩,将压测数据准备的时间缩短到10分钟,效率提升了至少10倍。一个很好的佐证就是,目前几乎所有压测任务都在我们的流量平台上进行的。
5. 展望未来
经过前面的建设,目前流量平台可以比较好的满足内部需求。但展望未来,还是有一些有价值的事情可以做,包含但不限于下面的一些点:
1.全链路节点压力图
目前在压测的时候,压测人员需要到监控平台上同时打开很多个应用的监控页面,压测期间需要在多个应用监控页面之间进行切换。未来可以把全链路上各节点的压力图展示出来,同时可以把节点的报警信息发送给压测人员,降低压测的监视成本。
2.压测工具状态收集与可视化
压测工具自身有一些很有用的状态信息,比如任务队列积压情况,当前的协程数等。这些信息帮助我们了解压测工具的状态,排查问题等。未来可以把状态数据接入到公司的监控平台上,进行数据可视化,以及配置报警规则等。
3.压力感知与自动调节
目前压测系统没有对业务应用的压力进行感知,不管压测应用处于什么样的状态,压测系统都会按照既定的设置进行压测。当业务系统处于高压力的情况下,持续增压还是很危险的。因此后续可以让回放模块感知目标应用的压力情况,动态调整压力值,避免压垮应用。
6.总结
经过一年的迭代优化,流量平台功能日益完善,趋于稳定。平台稳定运行一年,期间未发生过重大问题,很好的为业务线提供了压测服务。在稳定提供服务的同时,也为业务线发现了很多性能问题,发挥了其应有价值。期望在未来能够发现更多的问题,为业务稳定性建设保驾护航。本篇文章到此结束,感谢阅读!