tornado 异步非阻塞 实验

参考

tornado官方文档

Tornado中异步非阻塞地使用MySQL

Tornado-MySQL 0.5.1

使用tornado让你的请求异步非阻塞

根据这篇文章来做的,我的实验失败了:Tornado 异步笔记:异步任务

前言

目前用到的都是同步请求。当遇到查数据库等耗时操作的时候,请求会一直被阻塞。

实验

我们使用sql语句cmds = "select sleep(1)"来模拟长时间的数据库查询。

使用siege做压测。

另外,还可以用ab压测:
ab -c 10 -n 10 http://10.9.40.173:8888/asynctask?t=1

同步 V.S. Tornado-MySQL异步

只贴出handler部分的代码。

其中IndexHandler为同步请求,路由为‘/’

AsynHandler为异步请求,路由为‘/asyn’

cmds = "select sleep(1)"

MYSQL_POOL = pools.Pool(dict(host=host, port=port, user=usr, passwd=passwd, db=db) ,
         max_idle_connections=5, max_open_connections=10)

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        conn = MySQLdb.connect(host, usr, passwd, db, port)
        cur = conn.cursor()
        cur.execute(cmds)
        rep = cur.fetchall()
        self.write({'res': rep})


class AsynHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self):
        cur = yield MYSQL_POOL.execute(cmds)
        self.write({'res': cur.fetchall()})
        self.finish()

在同步的情况下,浏览器发起请求后会被阻塞,一直等到上一条查询完了,才能接受下一个请求。假设数据库请求耗时1s,同时发起调用的话,第1个请求会在大概1s后得到响应,第2个请求会在大概2s后得到响应。。。

在异步的情况下,则不需要等待数据库查询结束,可以在得到结果前接收下一个请求。假设数据库请求耗时1s,同时发起调用的话,理想情况下所有请求都会在1s后得到响应。

我们参照官网使用工具siege进行测试

模拟10个用户同时发出1次请求:

同步请求的测试结果是:

siege 10.9.40.173:8888 -c10 -r1
** SIEGE 3.0.8
** Preparing 10 concurrent users for battle.
The server is now under siege..      done.

Transactions:                 10 hits
Availability:             100.00 %
Elapsed time:              10.06 secs
Data transferred:           0.00 MB
Response time:              5.12 secs
Transaction rate:           0.99 trans/sec
Throughput:             0.00 MB/sec
Concurrency:                5.09
Successful transactions:          10
Failed transactions:               0
Longest transaction:           10.05
Shortest transaction:           1.00

总共用时10.06 secs,平均响应时间是5.12 secs,跟我们预想的一样,最快的响应是1 secs(第一个请求),最慢的响应是10 secs(最后一个请求)。平均耗时大概是(1+2+3+…+10)/10=5.5 secs

而异步请求的测试结果是:

siege 10.9.40.173:8888/asyn -c10 -r1
** SIEGE 3.0.8
** Preparing 10 concurrent users for battle.
The server is now under siege..      done.

Transactions:                 10 hits
Availability:             100.00 %
Elapsed time:               2.04 secs
Data transferred:           0.00 MB
Response time:              1.03 secs
Transaction rate:           4.90 trans/sec
Throughput:             0.00 MB/sec
Concurrency:                5.04
Successful transactions:          10
Failed transactions:               0
Longest transaction:            1.06
Shortest transaction:           1.01

总共用时2.04 secs,平均响应时间是1.03 secs,达到了异步请求的理论速度。最快的响应是1.01 secs,最慢的响应也才1.06 secs。

不需要返回结果的异步请求

class AsynPingNoResHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self, *args, **kwargs):

        tornado.ioloop.IOLoop.instance().add_timeout(1, callback=functools.partial(self.ping, 'www.baidu.co$

        # do something others

        self.finish('It works')

    @tornado.gen.coroutine
    def ping(self, url):
        os.system("ping -c 2 {}".format(url))
        return 'after'

需要返回结果的异步请求

再来一个利用tornado.gen.coroutine的例子,我参考伯乐在线的文章做的实验,但是没有得到文章的结果:

class AsyncTaskHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self):
        t = self.get_argument('t')
        response = yield tornado.gen.Task(self.query, t)
        #print 'response', response
        self.write({'res': response})
        self.finish()

    @tornado.gen.coroutine
    def query(self, t):
        conn = MySQLdb.connect(host, usr, passwd, db, port)
        cur = conn.cursor()
        cur.execute(cmdsn % t)
        rep = cur.fetchall()
        return rep

这个代码的测试结果是:

siege 10.9.40.173:8888/asynctask?t=1 -c10 -r1
** SIEGE 3.0.8
** Preparing 10 concurrent users for battle.
The server is now under siege..      done.

Transactions:                 10 hits
Availability:             100.00 %
Elapsed time:              10.07 secs
Data transferred:           0.00 MB
Response time:              9.45 secs
Transaction rate:           0.99 trans/sec
Throughput:             0.00 MB/sec
Concurrency:                9.39
Successful transactions:          10
Failed transactions:               0
Longest transaction:           10.06
Shortest transaction:           9.05

不知道为啥,这种代码的表现是,10个并发全部查询完才有一次性返回。可以说这个代码根本没有达到异步的效果。甚至比同步还差!

同步是1s的时候返回第1个请求,2s的时候返回第2个请求…

这种方式,在最后第10s的时候一次性返回10个请求,每个请求的响应时间都是10s。求高手指点一下这是为啥。

伯乐在线的文章的作者解释:

使用 yield task的方式,准确来说是协程。只是在网络IO中tornado的主线程不至于被 handler里的代码 block,单个handler还是被网络耗时任务所block的。如果并发连接比较小的情况下,很有可能其性能还不如同步的写法好。

具体以你自己的测试结果为准吧,在底层上,tornado这样的做法在IO层面上是异步的。

个人认为,使用协程跳到另一个函数去执行ping/sql操作,就阻塞在另一个函数里了,只是没有阻塞在handler的get中,对于整个程序而言,那还是被block了。。

fetch

如果tornado.gen.coroutine如此不堪的话,那它还有什么用?参照官网的例子,官网例子是用来fetch的。

同步fetch

class OfficialSynHandler(tornado.web.RequestHandler):
    def get(self):
        print '# in get', datetime.datetime.now()
        client = tornado.httpclient.HTTPClient()
        response = client.fetch("http://www.csdn.net/")
        print 'response', response
        self.write('ok')

siege测试结果

siege 10.9.40.173:8888/officialsyn -c10 -r1
** SIEGE 3.0.8
** Preparing 10 concurrent users for battle.
The server is now under siege..      done.

Transactions:                 10 hits
Availability:             100.00 %
Elapsed time:               2.05 secs
Data transferred:           0.00 MB
Response time:              0.51 secs
Transaction rate:           4.88 trans/sec
Throughput:             0.00 MB/sec
Concurrency:                2.47
Successful transactions:          10
Failed transactions:               0
Longest transaction:            1.03
Shortest transaction:           0.15

ab压测

Document Path:          /officialsyn/
Document Length:        306 bytes

Concurrency Level:      10
Time taken for tests:   0.016 seconds
Complete requests:      10
Failed requests:        0
Non-2xx responses:      10
Total transferred:      4450 bytes
HTML transferred:       3060 bytes
Requests per second:    626.25 [#/sec] (mean)
Time per request:       15.968 [ms] (mean)
Time per request:       1.597 [ms] (mean, across all concurrent requests)
Transfer rate:          272.15 [Kbytes/sec] received

异步fetch

class OfficialAsynHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        print '# in get', datetime.datetime.now()
        client = tornado.httpclient.AsyncHTTPClient()
        response = yield tornado.gen.Task(client.fetch, "www.csdn.net")
        print 'response', response
        self.write('ok')

siege测试结果

siege 10.9.40.173:8888/officialasyn -c10 -r1
** SIEGE 3.0.8
** Preparing 10 concurrent users for battle.
The server is now under siege..      done.

Transactions:                 10 hits
Availability:             100.00 %
Elapsed time:               1.03 secs
Data transferred:           0.00 MB
Response time:              0.01 secs
Transaction rate:           9.71 trans/sec
Throughput:             0.00 MB/sec
Concurrency:                0.06
Successful transactions:          10
Failed transactions:               0
Longest transaction:            0.02
Shortest transaction:           0.00

ab压测

Document Path:          /officialasyn/
Document Length:        306 bytes

Concurrency Level:      10
Time taken for tests:   0.016 seconds
Complete requests:      10
Failed requests:        0
Non-2xx responses:      10
Total transferred:      4450 bytes
HTML transferred:       3060 bytes
Requests per second:    640.78 [#/sec] (mean)
Time per request:       15.606 [ms] (mean)
Time per request:       1.561 [ms] (mean, across all concurrent requests)
Transfer rate:          278.46 [Kbytes/sec] received

可见,使用异步fetch的话每个请求都响应得比较快,平均响应时间短。

个人总结

如果要在tornado中实现非阻塞的mysql查询,不要自己折腾去写yield/coroutine/gen这些异步模块,直接用Tornado-MySQL就好了。还有可以考虑用celery等任务队列。

如果是不需要返回结果的异步操作,通过tornado的IO循环,把可以把耗时的任务放到后台异步计算,请求可以接着做别的计算。

如果是fetch的话使用yield异步模块可以有较好的效果,减少了平均响应时间,因此可以用来做REST API的通信。

很抱歉,我是一个语言模型,无法进行具体的编程实现。但是,我可以给您提供一些参考信息: 1. Tornado框架:Tornado是一个Python的Web框架,它的特点是速度快、易于扩展、异步非阻塞IO处理,适合处理高并发的Web请求。 2. MySQL数据库:MySQL是一个关系型数据库管理系统,它是一个开源的数据库系统,广泛应用于Web应用程序中。 3. 实验室设备预约网站:实验室设备预约网站一般包括设备预约信息管理、用户信息管理、设备管理、权限管理等功能。 以下是一个简单的实验室设备预约网站的代码示例: ``` import tornado.ioloop import tornado.web import tornado.httpserver import torndb from tornado.options import define, options define("port", default=8000, help="run on the given port", type=int) class BaseHandler(tornado.web.RequestHandler): @property def db(self): return self.application.db class IndexHandler(BaseHandler): def get(self): self.render("index.html") class DeviceHandler(BaseHandler): def get(self): devices = self.db.query("SELECT * FROM devices") self.render("devices.html", devices=devices) def post(self): name = self.get_argument("name") description = self.get_argument("description") self.db.execute("INSERT INTO devices (name, description) VALUES (%s, %s)", name, description) self.redirect("/devices") class ReservationHandler(BaseHandler): def get(self): reservations = self.db.query("SELECT * FROM reservations") self.render("reservations.html", reservations=reservations) def post(self): device_id = self.get_argument("device_id") start_time = self.get_argument("start_time") end_time = self.get_argument("end_time") self.db.execute("INSERT INTO reservations (device_id, start_time, end_time) VALUES (%s, %s, %s)", device_id, start_time, end_time) self.redirect("/reservations") class UserHandler(BaseHandler): def get(self): users = self.db.query("SELECT * FROM users") self.render("users.html", users=users) def post(self): username = self.get_argument("username") password = self.get_argument("password") self.db.execute("INSERT INTO users (username, password) VALUES (%s, %s)", username, password) self.redirect("/users") class Application(tornado.web.Application): def __init__(self): handlers = [ (r"/", IndexHandler), (r"/devices", DeviceHandler), (r"/reservations", ReservationHandler), (r"/users", UserHandler), ] settings = dict( template_path="templates", static_path="static", db=torndb.Connection( host="localhost", database="lab_reservation", user="root", password="password" ) ) tornado.web.Application.__init__(self, handlers, **settings) if __name__ == "__main__": tornado.options.parse_command_line() http_server = tornado.httpserver.HTTPServer(Application()) http_server.listen(options.port) tornado.ioloop.IOLoop.instance().start() ``` 上述代码中,使用了Tornado框架和Torndb库连接MySQL数据库,实现了设备预约信息管理、用户信息管理、设备管理等功能。具体实现细节需要根据具体需求进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值