因为目前是运维岗, tornado 主要是用来接收请求并执行检测任务,比如说,我起一个服务,用户将服务器IP或者主机名 Post 到 tornado, 然后 tornado 执行检测,比如就是 Ping,然后返回给用户结果。 假设是 ping -c 10 url,这个还是挺耗时的,在mac上测试大概 9s 左右,如果不是异步,这 9s 的时间服务器就不能响应其他请求,那这个服务显然是没法用的, 这时候就需要 tornado 的异步非阻塞了。
而执行 bash shell 这种,是需要开启多线程或多进程的,Ping 这种操作, 直觉上是多进程好一些, 因为可以利用多核,多线程的版本没有测试过。有时间会补上的,tornado.process.
Subprocess
封装了原生的 subprocess.Popen, 增加了对 IOStream 的支持。
torando with subprocess PIPE
import tornado.ioloop import tornado.web from subprocess32 import PIPE from tornado.gen import coroutine, Return from tornado.process import Subprocess class MainHandler(tornado.web.RequestHandler): @coroutine def get(self): res = yield self._work() self.write(res) @coroutine def _work(self): command = ‘ping -c 10 www.cnode.com‘ p = Subprocess([command], stdout=PIPE, shell=True) yield p.wait_for_exit() raise Return(p.stdout.read()) if __name__ == "__main__": application = tornado.web.Application([ (r"/", MainHandler), ]) application.listen(6000) tornado.ioloop.IOLoop.instance().start()
tornado with subprocess STREAM
import tornado.ioloop import tornado.web from tornado.gen import coroutine, Return from tornado.process import Subprocess class MainHandler(tornado.web.RequestHandler): @coroutine def get(self): res = yield self._work() self.write(res) @coroutine def _work(self): command = ‘ping -c 10 www.cnode.com‘ p = Subprocess([command], stdout=Subprocess.STREAM, stderr=Subprocess.STREAM, shell=True) out = yield p.stdout.read_until_close() raise Return(out) if __name__ == "__main__": application = tornado.web.Application([ (r"/", MainHandler), ]) application.listen(6000) tornado.ioloop.IOLoop.instance().start()
In addition to using tornado.process.Subprocess
as dano suggests, you should use stdout=tornado.process.Subprocess.STREAM
instead of PIPE
, and read from stdout/stderr asynchronously. Using PIPE
will work for small amounts of output, but you will deadlock in wait_for_exit()
if you use PIPE
and the subprocess tries to write too much data (used to be 4KB but the limit is higher in most modern linux systems).
stackoverflow 上老外对于 tornado.process.Subprocess 的使用建议,PIPE在处理大量的数据时会造成死锁,所以推荐使用用STREAM。
nodejs express child_process.exec
"use strict"; var express = require(‘express‘); var request = require(‘superagent‘); var app = express(); var exec = require(‘child_process‘).exec; app.get("/", function (req, res) { exec("ping -c 10 www.cnode.com", function (err, stdout, stderr) { if(err) throw err; res.send(stdout); }); }); var server = app.listen(5000);
不论怎么看都是 nodejs 更舒服吧, 要不收拾收拾东西离职算了, tornado 这货真的写不来啊。
一般来说,会使用 ab 进行压力测试。 leader 一般会问,你写的这东西并发能有多少啊?性能怎样啊,如何如何?我们需要回答这个问题。
ping -c 10 www.cnode.com 的耗时,9.6s
time ping -c 10 www.cnode.com ... --- www.cnode.com ping statistics --- 10 packets transmitted, 10 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 271.707/276.759/282.695/3.782 ms ping -c 10 www.cnode.com 0.00s user 0.00s system 0% cpu 9.604 total
ab test 很简单,就是按照设定好的并发数目,发送给定数量的请求,接收请求,并计算相关的指标。
测试之前,先想象一下,我们对结果的预期是啥? 因为是异步的,并发的请求会同时得到处理,所以处理1个请求应该跟处理多个请求响应时间相同,直到达到系统能力极限,即系统无法同时处理那么多 Ping。 所以,测试的方案如下, 并发(-c)从1开始,依次 1,5,10,20,30,总共处理(-n)30个请求。
ab -c 1 -n 30 http://127.0.0.1:6000/ ... Requests per second: 0.11 [#/sec] (mean) Time per request: 9506.393 [ms] (mean) Time per request: 9506.393 [ms] (mean, across all concurrent requests) ab -c 5 -n 30 http://127.0.0.1:6000/ ... Requests per second: 0.35 [#/sec] (mean) Time per request: 14476.120 [ms] (mean) Time per request: 2895.224 [ms] (mean, across all concurrent requests) ab -c 10 -n 30 http://127.0.0.1:6000/ ... Requests per second: 0.62 [#/sec] (mean) Time per request: 16117.301 [ms] (mean) Time per request: 1611.730 [ms] (mean, across all concurrent requests) ab -c 20 -n 30 http://127.0.0.1:6000/ ... Requests per second: 1.60 [#/sec] (mean) Time per request: 12496.166 [ms] (mean) Time per request: 624.808 [ms] (mean, across all concurrent requests) ab -c 30 -n 30 http://127.0.0.1:6000/ ... Requests per second: 3.16 [#/sec] (mean) Time per request: 9485.194 [ms] (mean) Time per request: 316.173 [ms] (mean, across all concurrent requests)
最先看到这结果的时候,我很诧异,觉得完全没有异步么,5并发的时候才 qps 才 0.35. 正常 100+ 没有任何问题吧?
Requests per second
This is the number of requests per second. This value is the result of dividing the number of requests by the total time taken
这个值实际上是 完成的请求总数 / 总测试时间
Time per request
The average time spent per request. The first value is calculated with the formula concurrency * timetaken * 1000 / done
while the second value is calculated with the formula timetaken * 1000 / done
第一个 Time per request: 并发数 * 耗时 * 1000 / 完成的请求总数
第二个 Time per request: 耗时 * 1000 / 完成的请求总数
ab test 是测试网站性能的,不假,但是设计上,一般的网站,是绝不会 9s 才返回的, 除了高峰时段的12306。换句话说,如果我们想看到的是 server 在检测性能下降之前能承担多少并发,-c 和 -n 的参数应该相等, 或者 -n 参数值是 -c 参数值的倍数是一定的。
ab -c 30 -n 30 http://127.0.0.1:6000/ ... Requests per second: 3.16 [#/sec] (mean) Time per request: 9485.194 [ms] (mean) Time per request: 316.173 [ms] (mean, across all concurrent requests) ab -c 40 -n 40 http://127.0.0.1:6000/ ... Requests per second: 3.60 [#/sec] (mean) Time per request: 11122.398 [ms] (mean) Time per request: 278.060 [ms] (mean, across all concurrent requests) ab -c 50 -n 50 http://127.0.0.1:6000/ ... Requests per second: 5.26 [#/sec] (mean) Time per request: 9513.345 [ms] (mean) Time per request: 190.267 [ms] (mean, across all concurrent requests) ab -c 100 -n 100 http://127.0.0.1:6000/ ... Requests per second: 10.30 [#/sec] (mean) Time per request: 9705.259 [ms] (mean) Time per request: 97.053 [ms] (mean, across all concurrent requests) ab -c 200 -n 200 http://127.0.0.1:6000/ ... Requests per second: 19.23 [#/sec] (mean) Time per request: 10402.855 [ms] (mean) Time per request: 52.014 [ms] (mean, across all concurrent requests)
并发数 * 第二个 Time per request 的值应该等于 ping -c 10 的总时间,9.7s 左右。
可以看到,在200的时候,这个值大于10s,说明达到了服务器 Ping 的极限。所以,可以跟 leader 汇报说,单台服务器同时相应 200 检测请求问题不大。
如果机器配置高一些,核心数,内存,带宽,那么并发还可以开到更高,当然,这只是个简陋的测试。
坚持不下去了,有 nodejs 为什么我要用 tornado 啊,python 对我最大的价值应该是抓取数据和与 bash 配合,而不是写什么 web 服务,退一万步说,有 php 在, 轮得到你 python 么?