tornado——异步请求

tornado框架

Tornado是使用Python编写的一个非阻塞Web服务器,也是一个轻量级框架。其基于EPOLL,所以可以非阻塞的就解决C10K的问题。

Web服务器与Web框架、同步与异步、C10K问题 的相关概念,会在下一篇文章整理写出来


网上看到一张tornado的框架图,画得很好:

主要模块

  • web - FriendFeed 使用的基础 Web 框架,包含了 Tornado 的大多数重要的功能
  • escape - XHTML, JSON, URL 的编码/解码方法
  • database - 对 MySQLdb 的简单封装,使其更容易使用
  • template - 基于 Python 的 web 模板系统
  • httpclient - 非阻塞式 HTTP 客户端,它被设计用来和 web 及 httpserver 协同工作
  • auth - 第三方认证的实现(包括 Google OpenID/OAuth、Facebook Platform、Yahoo BBAuth、FriendFeed OpenID/OAuth、Twitter OAuth)
  • locale - 针对本地化和翻译的支持
  • options - 命令行和配置文件解析工具,针对服务器环境做了优化

    底层模块
  • httpserver - 服务于 web 模块的一个非常简单的 HTTP 服务器的实现
  • iostream - 对非阻塞式的 socket 的简单封装,以方便常用读写操作
  • ioloop - 核心的 I/O 循环

tornado的同步

Tornado虽然是一个异步框架,但是如果使用不当很容易造成性能低下。首先实现一个同步的最小服务HTTP的服务器:

import tornado.httpserver                         
import tornado.ioloop
import tornado.web
import time

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello world!")

class SleepHandler(tornado.web.RequestHandler):
    def get(self):
        time.sleep(10)
        self.write("Sleep!")

requestHandlers=[(r'/',MainHandler),
                 (r'/sleep',SleepHandler)]

if __name__ == "__main__":
    app=tornado.web.Application(requestHandlers)
    app.listen(9998)
    tornado.ioloop.IOLoop.instance().start()

因为tornado本身是单线程的,所以当你调用 sleep 接口后会发现,整个服务会阻塞,直到这个耗时的任务处理完成才能响应接下来的请求,服务器的性能大幅度的降低。

gen.coroutine的异步

在Tornado中有两个装饰器:

  • tornado.web.asynchronous
  • tornado.gen.coroutine

    asynchronous 装饰器是让请求变成长连接的方式,必须手动调用 self.finish() 才会响应。

    coroutine 装饰器是指定改请求为协程模式。

    加入这两个装饰器:
class SleepHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self):
        yield tornado.gen.sleep(10)
        self.write("Sleep!")
        self.finish()

实际上gen.coroutine 在 Tornado 3.1 后会自动调用 self.finish() 结束请求,所以可以不使用 asynchronous 装饰器。

class SleepHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        yield tornado.gen.sleep(10)
        self.write("Sleep!")

可以注意到此处我们将time.sleep() 修改为 tornado.gen.sleep(),因为使用 gen.coroutine 装饰器编写异步函数,如果库本身不支持异步,那么响应任然是阻塞的。所以这种实现异步非阻塞的方式需要依赖大量的基于 Tornado 协议的异步库,使用上比较局限。所以我们再使用另一个方法解决这个问题。

concurrent.futures的异步

concurrent.futures 是python3新增加的一个库,用于并发处理。其中 ThreadPoolExecutor 是对标准库中的 threading 的高度封装,利用线程的方式让阻塞函数异步化,解决了很多库是不支持异步的问题。在 Tornado 中有个装饰器能使用 ThreadPoolExecutor 来让阻塞过程编程非阻塞,其原理是在 Tornado 本身这个线程之外另外启动一个线程来执行阻塞的程序。

import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.gen
import time
import threading
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello world!")

class SleepHandler(tornado.web.RequestHandler):
    executor = ThreadPoolExecutor(2)

    @tornado.gen.coroutine
    def get(self):
        yield self.sleep(10)
        self.write("Sleep!")

    @run_on_executor
    def sleep(self,sec):
        time.sleep(sec)

def activeth():
    while True:
        print(threading.active_count())
        time.sleep(1)

requestHandlers=[(r'/',MainHandler),
                 (r'/sleep',SleepHandler)]

if __name__ == "__main__":
    acc = threading.Thread(target = activeth)
    acc.start()
    app=tornado.web.Application(requestHandlers)
    app.listen(9998)
    tornado.ioloop.IOLoop.instance().start()

总结

gen.coroutine用法方便,消耗小,但需要异步库的支持。concurrent.futures则是不需要异步库的支持,但是线程的消耗很大。

还有一种就是利用celery。使用线程和 Celery 的模式进行异步编程,轻量级的放在线程中执行,复杂的放在 Celery 中执行。当然如果有异步库使用那最好不过了。


layout: post
title: "tornado——异步请求"
subtitle: "使用gen.coroutine和concurrent.futures使请求异步"
date: 2017-03-10 12:00:00
author: "ZWY"
header-img: "img/in-post/3-10/banner.jpg"
catalog: true
tags:
- tornado
- web框架
- 服务器


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,635评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,543评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,083评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,640评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,640评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,262评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,833评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,736评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,280评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,369评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,503评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,185评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,870评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,340评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,460评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,909评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,512评论 2 359

推荐阅读更多精彩内容