某测试模拟器性能优化-交叉验证

新的问题

上一篇文章说到,重构了client_simulator组件以后,能够模拟的客户端并发数量有了明显的提升。但是,这时又出现了一个新的问题,当并发数量超过300的时候,会出现发送频率不满足要求的情况,换句话说,并不是所有的客户端都能每秒请求10次。经过统计,并发量越大,这种情况越严重。但是,此时CPU和网络资源都还没有用满,令人费解。

新的尝试

一开始怀疑是进程和线程数的配比需要调整,比如增加进程数,减少每个进程上启动的线程数,或者相反。经过尝试,发现情况并没有本质的改善。

考虑到线程是由操作系统调度的,即使Python代码里面写time.sleep(0.09),也不能保证该线程一定在90毫秒后会被操作系统调度运行,更何况是在有几百个这样的线程同时运行,反复频繁切换的情况下。所以,我们开始怀疑是线程开销太重,导致系统来不及切换。必须寻找一种更轻的调度模型来代替线程,这时,我们想到了golang的goroutine,据说goroutine这种协程十分轻量,可以在一个进程中执行有数以十万计的协程,依旧保持高性能。

使用Golang验证

考虑到client_simulator逻辑非常简单,花了1个小时,把它用golang重写了一遍,每个goroutine代表一个客户端逻辑。上机一跑,感觉CPU占用明显比之前Python的多进程+多线程版本要少。不过,让人郁闷的是,golang版的client_simulator运行时同样无法满足客户端要求的发送频率,依然有很多客户端是在2秒钟才请求了10次。

调优进行到这一步,似乎又陷入了困境。只好再从头捋一捋整个组件的逻辑了:客户端请求网络数据,拿到后再请求下一次,确保1秒钟请求10次。这个过程中,只有网络响应时间是不可控的,但是考虑到每次请求的数据量很小,又是内网环境,响应时间应该只有10毫秒左右,不应该请求不了10次啊!

慢着,这一切都只是我们的分析,现实是不是这样的呢?我们必须验证一下!

使用JMeter验证

JMeter是工业级的性能测试工具,它的优势是跨平台,容易使用。我们可以通过它的监听器-response time graph看到它发出的请求响应时间统计结果。 使用它有如下几步:

  • 在执行机上安装JRE
  • 下载apache JMeter压缩包,解压
  • 运行JMeter,制作测试计划
  • 在测试计划里面新建线程组,添加Sampler-HTTP请求。
  • 使用JMeter执行该测试计划,生成结果文件jtl
  • 在JMeter的response time graph组件中导入结果文件jtl,生成图表。

跑完一看,见鬼,真的有很多请求无法及时拿到数据,最长的要1秒才能完成HTTP请求。如图。

后续

通过JMeter和Golang版的组件的交叉验证,我们发现模拟器的性能问题是由于服务器无法同时处理超过300个客户端请求导致的。把这个情况和开发沟通后,开发经过调查,确认了服务器确实存在这个问题。

通过这次调优,可见虽然分析很重要,但是实践也必须要跟上分析的节奏。心想为虚,眼见为实。Talk is cheap, show me your data.