我们获得了不同机器配置下的每秒请求数上限,现在我们仅剩下前面提到的一个任务:达到生产环境的3倍负载
我们再次在达到220k个TCP连接数上受阻。无论怎么设置休眠时间,TCP连接数就是无法再上升。
让我们来计算下,220k个TCP连接和每秒请求数900,110,000 / 900 ~= 120秒。这里使用110k因为220k个连接同时包含了输入和输出,既双向总数。
这让我们怀疑2分钟是系统某处的一个限制,通过查看HAProxy日志可以验证,日志中大部分连接的总耗时都在120000毫秒。
Mar 23 13:24:24 localhost haproxy[53750]: 172.168.0.232:48380 [23/Mar/2017:13:22:22.686] api~ api-backend/http31 39/0/2062/-1/122101 -1 0 - - SD-- 1714/1714/1678/35/0 0/0 {0,"",""} "POST /ping HTTP/1.1"
其中122101是总处理时长。日志中所有字段详细值参见HAProxy文档。
经过进一步研究,我们发现Node.js有2分钟的默认超时时间。
具体信息参见下面一些资料:
解决了超时时间之后,事情并没有想象中的顺利。当连接数达到1.3m个时,HAProxy连接数突然下降到0,然后再次开始上升。通过dmesg命令查看内核日志之后发现,该现象是系统内存不足造成的。通过更换成16核64GB内存,并设置nbproc = 3
之后,最终达到了2.4m个连接。
下面是HAProxy后端服务的源码。我们在代码中使用了statsd库,以获取服务端每秒请求数。
var http = require('http'); var createStatsd = require('uber-statsd-client'); qs = require('querystring'); var sdc = createStatsd({ host: '172.168.0.134', port: 8125 }); var argv = process.argv; var port = argv[2]; function randomIntInc (low, high) { return Math.floor(Math.random() * (high - low + 1) + low); } function sendResponse(res,times, old_sleep) { res.write('pong'); if(times==0) { res.end(); } else { sleep = randomIntInc(0, old_sleep+1); setTimeout(sendResponse, sleep, res,times-1, old_sleep); } } var server = http.createServer(function(req, res) { headers = req.headers; old_sleep = parseInt(headers["sleep"]); times = headers["times"] || 0; sleep = randomIntInc(0, old_sleep+1); console.log(sleep); sdc.increment("ssl.server.http"); res.writeHead(200); setTimeout(sendResponse, sleep, res, times, old_sleep) }); server.timeout = 3600000; server.listen(port);
同时我们还有一个小脚本来运行多个后端服务。整个测试中,我们使用了8台服务器,每台服务器上运行了10个后端服务进程,以避免后端服务称为压力测试的瓶颈。
counter=0 while [ $counter -le 9 ] do port=$((8282+$counter)) nodejs /opt/local/share/test-tools/HikeCLI/nodeclient/httpserver.js $port & echo "Server created on port " $port ((counter++)) done echo "Created all servers"
对于客户端,每个IP有63k个TCP连接的限制。如果对此不了解,参见本系列的前面一篇文章。
因此为了达到2.4m个连接(双向连接,对于客户端来说要发起1.2m个连接),我们需要大约20台机器。在所有20台机器上同时运行Vegeta命令非常痛苦,即使使用了类似csshx工具,仍然需要从所有Vegeta合并最终测试结果。
脚本如下:
result_file=$1 declare -a machines=("172.168.0.138" "172.168.0.141 " "172.168.0.142" "172.168.0.18" " 172.168.0.5" "172.168.0.122" "172.168.0.123" " 172.168.0.124" "172.168.0.232" " 172.168.0.24 4" "172.168.0.170" "172.168.0.179" " 172.168.0.59" "172.168.0.68" "172.168.0.137" " ;172.168.0.155" "172.168.0.154" "172.168.0.45" " 172.168.0.136" "172.168.0.143") bins="" commas="" for i in "${machines[@]}"; do bins=$bins","$i". bin"; commas=$commas","$i; done; bins=${bins:1} commas=${commas:1} pdsh -b -w "$commas" 'echo "POST http://test.haproxy.in:80/ping" | /home/sachinm/.linuxbrew/bin/vegeta -cpus=32 attack -connections=1000000 -header="sleep:20" -header=" times:2" -body=post_smaller.txt -timeout=2h -rate=3000 -workers= 500 > ' $result_file for i in "${machines[@]}"; do scp sachinm @$i:/home/sachinm/$result_file $i.bin ; done; vegeta report -inputs="$bins"
幸好这里使用了pdsh工具,使得我们能够在多台远程服务器上并行的执行命令。同时Vegeta也提供了结果合并功能,这也是我们急需的。
本节大概是读者最希望了解的内容,下面是我们在压力测试场景中使用的HAProxy配置。其中最重要的部分是nbproc
和maxconn
设置。其中maxconn
设置允许HAProxy能够支持我们期望达到的TCP连接数。
maxconn
设置会影响HAProxy进程的ulimit,例如:
最大文件打开数设置到4m因为HAProxy的最大连接数设置成了2m。干净利落!