写点什么

Tornado 异步性能分析

  • 2022 年 9 月 29 日
    北京
  • 本文字数:2587 字

    阅读完需:约 8 分钟

Tornado 是一个 Python web 框架和异步网络库,起初由 FriendFeed 开发。通过使用非阻塞网络 I/O,Tornado 可以支撑上万级的连接,处理长连接, WebSockets , 和其他需要与每个用户保持长久连接的应用。

本文介绍如何正确的使用 Tornado 异步特性以及对其进行压力测试, 查看 Tornado 的性能到底有多强悍。

本文使用 Tornado 5.0.2 版本

最简单的脚手架

# -*- coding: utf-8 -*-import tornado.ioloopimport tornado.webclass Index(tornado.web.RequestHandler):  def get(self):    self.write("hello world")def make_app():  return tornado.web.Application([   (r"/", Index) ])if __name__ == '__main__':  app = make_app()  app.listen(8888)  tornado.ioloop.IOLoop.current().start()
复制代码

我使用 jmeter 进行压测,可以达到 1200,可以说是非常不错

同步的困扰

但是在开发过程中并不是所有的接口都什么都不做就返回个简单的字符串或者 json, 更多的时候是进行一些数据库的操作或者本地的 IO 操作之后再返回相应的数据。

class Index(tornado.web.RequestHandler):  def get(self):    name = self.get_argument('name', 'get')    time.sleep(0.5)    self.write("hello {}".format(name))
复制代码

我们在 get 请求的时候加入了 time.sleep(0.5) 来模拟接口处理数据的耗时操作。

我们在 chrome 的开发者工具中看到这个请求耗时 503ms,

为什么同步的性能会这么差?

在 python 中 time.sleep() 函数是个阻塞函数, 会阻塞 python 的运行, 使用 tornado 做 web 项目,你不可能只为一个用户服务,当有多个用户访问同一个接口时,python 在处理 sleep 操作时会阻塞运行,这时后来的请求也要等待前的请求,只有当前面的请求结束以后再处理,这也就是为什么上面的压测最短是 503ms,最长却需要 50240ms 的原因.因为大家是阻塞进行的。不光 time.sleep 是这样的阻塞函数, python 中绝大多数函数都是这样的阻塞函数,包括文件 IO 操作, 网络请求操作等都是阻塞函数,所以我们需要异步的处理网络请求。

异步的解决方案

01

使用多线程

class Index(tornado.web.RequestHandler):  def get(self):    name = self.get_argument('name', 'get')    t = threading.Thread(target=time.sleep, args=(0.5,))    t.start()    self.write("hello {}".format(name))
复制代码

02

使用线程池来执行耗时操作

from tornado.concurrent import run_on_executorclass Index(tornado.web.RequestHandler):  executor = ThreadPoolExecutor(1)  @tornado.web.asynchronous  @tornado.gen.coroutine  def get(self):    name = self.get_argument('name', 'get')    rst = yield self.work(name)    self.write("hello {}".format(rst))    self.finish()  def post(self):    name = self.get_argument('name', 'post')    self.write("hello {}".format(name))  @run_on_executor  def work(self, name):    time.sleep(0.5)    return "{} world".format(name)
复制代码

这里在 Index 类中初始化了一个线程池, executor, 这有一个线程, 将耗时的操作放到 work 函数中, 并且使用 @run_on_executor 装饰器进行装饰, 这时我们就可以获取到耗时操作的返回值。

压测为 500 个用户在 5 秒内请求


但是我们看下压测结果

依然很糟糕, 每秒也就能处理 2 个请求。而且最长的请求需要 7 万多 ms, 这也是无法忍受的。

每个请求需要耗时 0.5 秒,现在有一个线程池,它里面有一个线程,也就是说,同时最多也就处理一个请求,我们可以加大线程线中的数量, executor = ThreadPoolExecutor(5) 改为 5 个再进行压测,这时看到是有所增长,可以到达 10, 也依然不是很理想, 最大影响有 4 万多 ms。

意义在于, 一个 web 项目, 不太可能只提供一个接口, 假设你有 3 个接口, /user, /info, /case , 其中两个接口( /user, /case )都使用异步处理,放到了线程池中操作, 只有一个接口( /info )没有,而是直接使用

很显然, 即使用到的线程池,并且将线程池中的线程设置为 5, 每秒也只能处理 10 个请求,这样的 QPS 也是令人相当着急,一个成熟的系统访问人数成千上万都很正常,接下来我们使用 asyncio 库来异步处理请求。

使用异步处理库 asyncio

import asyncioclass Index(tornado.web.RequestHandler):  async def get(self):    name = self.get_argument('name', 'get')    rst = await self.work(name)    self.write("hello {}".format(rst))    self.finish()  def post(self):    name = self.get_argument('name', 'post')    self.write("hello {}".format(name))  async def work(self, name):    await asyncio.sleep(0.5)    return "{} world".format(name)if __name__ == '__main__':  try:    app = make_app()    app.listen(8888)    tornado.ioloop.IOLoop.current().start()  except:    print(traceback.format_exc())    tornado.ioloop.IOLoop.current().start()
复制代码

这里我们将 time.sleep 函数换成了 asyncio.sleep 异步的函数, 并且在函数定义 def 将加上 async , 在获取 work 结果时使用了 await 关键词。

此时的压测结果为

可以看到,在 5 秒内就全部执行结束,QPS 可以达到 91, 和之前的 10 或者 2 有着非常大的提高,由于只有 500 个用户, 我们可以模拟更多的用户来访问,使用 1000 个用户来进行压测看下效果。

依然是在 5 秒内完成了请求,并且 QPS 上升到了 181, 继续加大请求用户量为 2000。

依然强悍, QPS 继续上升到 305,而且仅多用了 1 秒钟.如果再继续提高用户量,我们来看看它的极限在哪里?

4000 的时候,服务端会报 ValueError: too many file descriptors in select() 这个是由于在 windows 中 IOloop 使用的是 selector, 它有 1024 个文件描述符的限制所导致的, 将其放到 linux 中我们看看可以达到多少。

6000 的时候还没有问题,如果 7000 的话会出现异常,这时已经比在多线程中运行性能提高的非常多了,如果考虑用户的增加,就应该考虑水平扩展了,通过负载均衡来解决了。


总结

通过上文的对比,我们可以理解,不是因为你使用了 tornado 你的网站性能就能提升多少,而是要正确的使用相应的异步库,只有正确的使用异步特性,才能发挥 tornado 的高性能。

功能同步库异步库|------

github 上有个组织在维护一些常用的异步库,aio-libs · GitHub 常用的库都实现的异步, 大家在使用时可以先搜索一下

更多学习资料戳下方!!!

https://qrcode.ceba.ceshiren.com/link?name=article&project_id=qrcode&from=infoQ&timestamp=1662366626&author=xueqi

用户头像

社区:ceshiren.com 2022.08.29 加入

微信公众号:霍格沃兹测试开发 提供性能测试、自动化测试、测试开发等资料、实事更新一线互联网大厂测试岗位内推需求,共享测试行业动态及资讯,更可零距离接触众多业内大佬

评论

发布
暂无评论
Tornado 异步性能分析_测试_测吧(北京)科技有限公司_InfoQ写作社区