python tornado
# 聊聊Tornado一个被低估的Python异步框架它到底是什么Tornado本质上是一个用Python写的非阻塞式Web服务器框架。说到这个问题得从开发者面临的一个实际困境说起。去年我帮一个朋友重构他的爬虫服务用的是Flask跑在Gunicorn上。平时运行得挺好可一旦遇到某个网站突然返回一堆慢响应整个服务就开始卡死——不是CPU不够而是所有工作线程都被堵在那儿等I/O。你想想看一个线程在等待远程服务器的响应什么事情都做不了。这就是Tornado要解决的根本问题。它用一种事件循环的机制替代了传统的线程或进程模型。简单来说当你的代码在等某个网络请求返回时Tornado不会傻傻地在那里干等而是去处理其他请求。等那个网络请求的数据到了它会回过来继续处理。有人可能会说这不就是异步吗Python 3里不是有asyncio吗没错但Tornado从2010年左右就开始这么做了比asyncio早了好多年。它的核心——ioloop模块是一个完整的事件循环实现可以和asyncio协同工作但并不依赖它。它真正擅长什么Tornado的用武之地主要集中在需要处理大量并发连接的场景。拿长轮询来说。想象你在做个即时通讯功能用户打开网页需要实时接收新消息。传统做法是让客户端每隔几秒发个请求问“有没有新消息”。这太傻了——多数情况下服务器根本没新消息白白浪费带宽和资源。长轮询的做法是客户端发一个请求服务器把这个请求挂在那里直到有新消息了才返回。这样做一个客户端就会有一个长时间挂起的连接。如果用户有1万人同时在线那就是1万个并发连接。这在Flask这类同步框架上几乎是灾难每个连接需要一个线程1万个线程的内存消耗和上下文切换成本就够你受的。但在Tornado里1万个连接只是一个事件循环里维护的1万个socket对象完全不是一个数量级。除此之外WebSocket也是Tornado的强项。很多框架的WebSocket支持像是后来硬加上去的而Tornado在设计之初就考虑到了双向通信的需求。你可以在同一个应用里同时处理HTTP请求和WebSocket连接共享相同的身份验证和逻辑。怎么开始用它用Tornado写个简单的服务比大部分框架都直接。importtornado.ioloopimporttornado.webclassMainHandler(tornado.web.RequestHandler):defget(self):self.write({message:Hello, World!})defmake_app():returntornado.web.Application([(r/,MainHandler),])if__name____main__:appmake_app()app.listen(8888)tornado.ioloop.IOLoop.current().start()这段代码启动后会直接监听8888端口完全不需要额外的WSGI服务器。Tornado本身就是个完整的HTTP服务器。但要注意上面这个例子里的get方法是同步的。如果在这里面做耗时操作比如查询数据库或调用外部API它还是会阻塞事件循环。正确的做法是使用异步方法classAsyncHandler(tornado.web.RequestHandler):asyncdefget(self):# 假设有个异步的HTTP客户端http_clienttornado.httpclient.AsyncHTTPClient()responseawaithttp_client.fetch(https://api.example.com/data)self.write(response.body)这里用了async/await关键字让Tornado知道这个方法在执行到await的时候可以切出去做别的事。一些实际经验用Tornado这几年有几点体会特别深。第一别把所有事情都做成异步的。Tornado的异步模型很棒但Python本身没有真正的多核并行能力。如果你的业务逻辑里有CPU密集型的计算比如图片处理、数据压缩这些事不应该放在主事件循环里。正确做法是用tornado.process模块里提供的子进程机制或者直接丢给Celery去处理。第二数据库连接池是个容易被忽略的坑。很多ORM的连接池是线程安全的但不一定适用于协程环境。在Tornado里用SQLAlchemy需要特别注意最好用create_async_engineSQLAlchemy 1.4之后支持的异步版本。如果非要用同步的数据库驱动就得把数据库操作封装成run_in_executor调用这会失去一些性能优势。第三也是很多人会踩的坑不要在RequestHandler里持有长时间的引用。Tornado会复用这些handler对象如果某个handler里的回调持有了它的引用这个对象就永远无法被垃圾回收相当于内存泄漏。实际中见过一个项目因为代码里不小心把self传进了另一个协程结果内存涨到十几个G。和其他框架的比较说到Flask它和Tornado面对的场景几乎完全不同。Flask追求的是简单、优雅开发者写个API可以很快上手学习曲线几乎为零。但它根本上是个同步框架部署时得靠Gunicorn这类WSGI服务器撑起并发。遇到长连接或WebSocket场景Flask就比较吃力了虽然可以通过插件支持但体验和Tornado的“原生支持”还是有差距。FastAPI是近几年比较火的框架。它在性能上做了很多优化底层依赖Starlette和asyncio。FastAPI的强项在于类型提示、自动生成API文档这些Tornado确实比不上。但FastAPI的WebSocket支持更像是在HTTP框架上“打了补丁”而Tornado的设计从一开始就考虑了这个需求。如果要做复杂的实时通信系统Tornado在可靠性和代码组织上往往更合适。Django则是另一个维度的东西。它是一个全栈框架自带ORM、Admin后台、模板引擎。Tornado几乎不做这些事它更像个工具箱。用Django做大型CMS类项目很顺手但如果主要需求是处理超高并发的WebSocket连接Django的同步本质和大型框架的启动开销会成为拖累。有一点很多人不知道Tornado被很多大公司的关键组件采用。比如FacebookTornado最初就是他们开发的不过在Facebook内部用C重写了一个版本还有ZMQ的官方Python库Czmq的异步接口就用到了Tornado。这些场景对稳定性和性能都有苛刻要求说明Tornado在这些方面经得起考验。我的看法是如果你明确需要高并发、长连接、WebSocket这类特性直接选Tornado如果只是写个普通的Web APIFlask或FastAPI可能更顺手。没必要为了用Tornado而用Tornado毕竟它的同步编码的方式确实增加了代码复杂度。最后说个有意思的现象。从前段时间开始Python社区似乎又对Tornado的关注度上升了。原因可能是微服务和消息驱动的架构越来越普及服务之间的通信量越来越大长连接的需求也越来越多。Tornado这种“铁了心为高并发而生”的框架又开始展现出它的价值。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2576048.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!