1. 程式人生 > >使用tornado讓你的請求非同步非阻塞

使用tornado讓你的請求非同步非阻塞

前言

也許有同學很迷惑:tornado不是標榜非同步非阻塞解決10K問題的嘛?但是我卻發現不是torando不好,而是你用錯了.比如最近發現一個事情:某網站開啟頁面很慢,伺服器cpu/記憶體都正常.網路狀態也良好.後來發現,開啟頁面會有很多請求後端資料庫的訪問,有一個mongodb的資料庫業務api的rest服務.但是它的tornado卻用錯了,一步步的來研究問題:

說明

以下的例子都有2個url,一個是耗時的請求,一個是可以或者說需要立刻返回的請求,我想就算一個對技術不熟,從道理上來說的使用者,他希望的是他訪問的請求不會影響也不會被其他人的請求影響

#!/bin/env python

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.httpclient

import time

from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)

class SleepHandler(tornado.web.RequestHandler):
    def get(self):
        time.sleep(5)
        self.write("when i sleep 5s")

class JustNowHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("i hope just now see you")

if __name__ == "__main__":
    tornado.options.parse_command_line()
    app = tornado.web.Application(handlers=[
            (r"/sleep", SleepHandler), (r"/justnow", JustNowHandler)])
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

假如你使用頁面請求或者使用哪個httpie,curl等工具先訪問http://localhost:8000/sleep,再訪問http://localhost:8000/justnow.你會發現本來可以立刻返回的/jsutnow的請求會一直阻塞到/sleep請求完才返回.

這是為啥?為啥我的請求被/sleep請求阻塞了?如果平時我們的web請求足夠快我們可能不會意識到這個問題,但是事實上經常會有一些耗時的程序,意味著應用程式被有效的鎖定直至處理結束.

這是時候你有沒有想起@tornado.web.asynchronous這個裝飾器?但是使用這個裝飾器有個前提就是你要耗時的執行需要執行非同步,比如上面的time.sleep,你只是加裝飾器是沒有作用的,而且需要注意的是Tornado預設在函式處理返回時關閉客戶端的連線,但是當你使用@tornado.web.asynchonous裝飾器時,Tornado永遠不會自己關閉連線,需要顯式的self.finish()關閉

我們大部分的函式都是阻塞的, 比如上面的time.sleep其實tornado有個非同步的實現:

#!/bin/env python

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.gen
import tornado.httpclient
import tornado.concurrent
import tornado.ioloop

import time

from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)

class SleepHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self):
        yield tornado.gen.Task(tornado.ioloop.IOLoop.instance().add_timeout, time.time() + 5)
        self.write("when i sleep 5s")


class JustNowHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("i hope just now see you")

if __name__ == "__main__":
    tornado.options.parse_command_line()
    app = tornado.web.Application(handlers=[
            (r"/sleep", SleepHandler), (r"/justnow", JustNowHandler)])
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

這裡有個新的tornado.gen.coroutine裝飾器, coroutine是3.0之後新增的裝飾器.以前的辦法是用回撥,還是看我這個例子:
class SleepHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        tornado.ioloop.IOLoop.instance().add_timeout(time.time() + 5, callback=self.on_response)
    def on_response(self):
        self.write("when i sleep 5s")
        self.finish()

使用了callback, 但是新的裝飾器讓我們通過yield實現同樣的效果:你在開啟/sleep之後再點選/justnow,justnow的請求都是立刻返回不受影響.但是用了asynchronous的裝飾器你的耗時的函式也需要執行非同步

剛才說的都是沒有意義的例子,下面寫個有點用的:讀取mongodb資料庫資料,然後再前端按行write出來

#!/bin/env python

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.gen
import tornado.httpclient
import tornado.concurrent
import tornado.ioloop

import time
# 一個mongodb出品的支援非同步的資料庫的python驅動
import motor
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
# db其實就是test資料庫的遊標
db = motor.MotorClient().open_sync().test

class SleepHandler(BaseHandler):
    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self):
        # 這一行執行還是阻塞需要時間的,我的tt集合有一些資料並且沒有索引
        cursor = db.tt.find().sort([('a', -1)])
        # 這部分會非同步非阻塞的執行二不影響其他頁面請求
        while (yield cursor.fetch_next):
            message = cursor.next_object()
            self.write('<li>%s</li>' % message['a'])
        self.write('</ul>')
        self.finish()

    def _on_response(self, message, error):
        if error:
            raise tornado.web.HTTPError(500, error)
        elif message:
            for i in message:
                self.write('<li>%s</li>' % i['a'])
        else:
            self.write('</ul>')
            self.finish()


class JustNowHandler(BaseHandler):
    def get(self):
        self.write("i hope just now see you")

if __name__ == "__main__":
    tornado.options.parse_command_line()
    app = tornado.web.Application(handlers=[
            (r"/sleep", SleepHandler), (r"/justnow", JustNowHandler)])
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

一個同事提示為什麼這個耗時的東西不能非同步的丟給某工具去執行而不阻塞我的請求呢?好吧,我也想到了:celery,正好github有這個東西:tornado-celery

執行下面的程式首先你要安裝rabbitmq和celery:

#!/bin/env python

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.gen
import tornado.httpclient
import tcelery, tasks

import time

from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)

tcelery.setup_nonblocking_producer()

class SleepHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self):
        # tornado.gen.Task的引數是:要執行的函式, 引數
        yield tornado.gen.Task(tasks.sleep.apply_async, args=[5])
        self.write("when i sleep 5s")
        self.finish()

class JustNowHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("i hope just now see you")

if __name__ == "__main__":
    tornado.options.parse_command_line()
    app = tornado.web.Application(handlers=[
            (r"/sleep", SleepHandler), (r"/justnow", JustNowHandler)])
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

task是celery的任務定義的檔案,包含我們說的time.sleep的函式
import time
from celery import Celery

celery = Celery("tasks", broker="amqp://guest:[email protected]:5672")
celery.conf.CELERY_RESULT_BACKEND = "amqp"

@celery.task
def sleep(seconds):
    time.sleep(float(seconds))
    return seconds

if __name__ == "__main__":
    celery.start()

然後啟動celelry worker(要不然你的任務怎麼執行呢?肯定需要一個消費者取走):
celery -A tasks worker --loglevel=info

但是這裡的問題也可能很嚴重:我們的非同步非阻塞依賴於celery,還是這個佇列的長度,假如任務很多那麼就需要等待,效率很低.有沒有一種辦法把我的同步阻塞函式變為非同步(或者說被tornado的裝飾器理解和識別)呢?

#!/bin/env python

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.httpclient
import tornado.gen
from tornado.concurrent import run_on_executor
# 這個併發庫在python3自帶在python2需要安裝sudo pip install futures
from concurrent.futures import ThreadPoolExecutor

import time

from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)

class SleepHandler(tornado.web.RequestHandler):
    executor = ThreadPoolExecutor(2)
    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self):
        # 假如你執行的非同步會返回值被繼續呼叫可以這樣(只是為了演示),否則直接yield就行
        res = yield self.sleep()
        self.write("when i sleep")
        self.finish()

    @run_on_executor
    def sleep(self):
        time.sleep(5)
        return 5

class JustNowHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("i hope just now see you")

if __name__ == "__main__":
    tornado.options.parse_command_line()
    app = tornado.web.Application(handlers=[
            (r"/sleep", SleepHandler), (r"/justnow", JustNowHandler)])
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

但是有朋友留言和我說為什麼在瀏覽器開啟多個url請求還是會阻塞一個個的響應呢?

這個事瀏覽器自身實現的可能是快取把,當請求的資源相同就會出現這個問題,可以使用多瀏覽器(多人)或者命令列下的curl登都不會有這個問題,還有個比較惡的解決方法:

給你的請求新增一些無用引數,比如: http://localhost:8000/sleep/?a=1 也可以是個時間戳

from concurrent.futures import ThreadPoolExecutor
from functools import partial, wraps
import time

import tornado.ioloop
import tornado.web

from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)

EXECUTOR = ThreadPoolExecutor(max_workers=4)


def unblock(f):

    @tornado.web.asynchronous
    @wraps(f)
    def wrapper(*args, **kwargs):
        self = args[0]

        def callback(future):
            self.write(future.result())
            self.finish()

        EXECUTOR.submit(
            partial(f, *args, **kwargs)
        ).add_done_callback(
            lambda future: tornado.ioloop.IOLoop.instance().add_callback(
                partial(callback, future)))

    return wrapper


class JustNowHandler(tornado.web.RequestHandler):

    def get(self):
        self.write("i hope just now see you")


class SleepHandler(tornado.web.RequestHandler):

    @unblock
    def get(self, n):
        time.sleep(float(n))
        return "Awake! %s" % time.time()


class SleepAsyncHandler(tornado.web.RequestHandler):

    @tornado.web.asynchronous
    def get(self, n):

        def callback(future):
            self.write(future.result())
            self.finish()

        EXECUTOR.submit(
            partial(self.get_, n)
        ).add_done_callback(
            lambda future: tornado.ioloop.IOLoop.instance().add_callback(
                partial(callback, future)))

    def get_(self, n):
        time.sleep(float(n))
        return "Awake! %s" % time.time()


application = tornado.web.Application([
    (r"/justnow", JustNowHandler),
    (r"/sleep/(\d+)", SleepHandler),
    (r"/sleep_async/(\d+)", SleepAsyncHandler),
])


if __name__ == "__main__":
   application.listen(options.port)
   tornado.ioloop.IOLoop.instance().start()

from  http://www.dongwm.com/archives/shi-yong-tornadorang-ni-de-qing-qiu-yi-bu-fei-zu-sai/?utm_source=tuicool

相關推薦

使用tornado請求非同步阻塞

前言 也許有同學很迷惑:tornado不是標榜非同步非阻塞解決10K問題的嘛?但是我卻發現不是torando不好,而是你用錯了.比如最近發現一個事情:某網站開啟頁面很慢,伺服器cpu/記憶體都正常.網路狀態也良好.後來發現,開啟頁面會有很多請求後端資料庫的訪問,有一個mon

利用tornado使請求實現非同步阻塞

基本IO模型 網上搜了很多關於同步非同步,阻塞非阻塞的說法,理解還是不能很透徹,有必要買書看下。 參考:使用非同步 I/O 大大提高應用程式的效能 怎樣理解阻塞非阻塞與同步非同步的區別? 同步和非同步:主要關注訊息通訊機制(重點在B?)。 同步:A呼叫B,B處理直到獲得結

真正的 Tornado 非同步阻塞

原文出處https://hexiangyu.me/posts/15 其中 Tornado 的定義是 Web 框架和非同步網路庫,其中他具備有非同步非阻塞能力,能解決他兩個框架請求阻塞的問題,在需要併發能力時候就應該使用 Tornado。 但是在實際使用過程中很容易把 Tornado

【Flask】Flask實現非同步阻塞請求功能

前言 最近做物聯網專案的時候需要搭建一個非同步非阻塞的HTTP伺服器,經過查詢資料,發現可以使用gevent包。 關於gevent Gevent 是一個 Python 併發網路庫,它使用了基於 libevent 事件迴圈的 greenlet 來提供一個高階

基於Java NIO2實現的非同步阻塞訊息通訊框架

原文傳送門 基於Java NIO2實現的非同步非阻塞訊息通訊框架 前奏 AIO應用開發 Future方式 Callback方式 Reader/Writer方式實現 執行緒池和Group PendingExceptio

簡單理解什麼是同步阻塞/同步阻塞非同步阻塞/非同步阻塞

簡單理解什麼是同步阻塞/同步非阻塞,非同步阻塞/非同步非阻塞 舉個栗子 1、你在家做飯,用普通的湯鍋,米放進去,就站在鍋邊,傻等飯熟。——這叫同步阻塞 是不是覺得浪費了大量的時間,於是你想提高時間的利用效率。 2、還是用普通的湯鍋,米放進去,然後繼續回去打遊戲,過一會就來看一次。——

Netty非同步阻塞事件驅動及元件原理詳解

本文基於 Netty 4.1 展開介紹相關理論模型,使用場景,基本元件、整體架構,知其然且知其所以然,希望給大家在實際開發實踐、學習開源專案方面提供參考。 Netty 是一個非同步事件驅動的網路應用程式框架,用於快速開發可維護的高效能協議伺服器和客戶端。 JDK 原生 NIO 程式的問題

QT技巧 - 非同步阻塞轉為同步阻塞的方法

QT技巧 - 非同步非阻塞轉為同步阻塞的方法 如需轉載請標明出處:http://blog.csdn.net/itas109 QQ技術交流群:129518033 目錄 文章目錄 QT技巧 - 非同步非阻塞轉為同步阻塞的方法

[python]socket傳送http請求阻塞io的一個例子

#通過非阻塞io實現http請求 import socket from urllib.parse import urlparse #使用非阻塞io完成http請求 def get_url(url): #通過socket請求html url

02-node.js 單執行緒,‘ 非同步阻塞io

1、基本概念     同步:多個任務順序執行     非同步:多個任務並排執行 2、node的併發實現原理     Node JS是單執行緒應用程式,但它通過事件和回撥概念,支援併發。 由於Node JS每一個API是非同步的,作為一個單獨的執行緒,它使用非同步函

java高併發系統之非同步阻塞

在做電商系統時,流量入口如首頁、活動頁、商品詳情頁等系統承載了網站的大部分流量,而這些系統的主要職責包括聚合資料拼裝模板、熱點統計、快取、下游功能降級開關、託底資料等等。其中聚合資料需要呼叫其它多個系統服務獲取資料、拼裝資料/模板然後返回給前端,聚合資料來源主要有依賴系統

聊聊java高併發系統之非同步阻塞

幾種呼叫方式 同步阻塞呼叫 即序列呼叫,響應時間為所有服務的響應時間總和; 半非同步(非同步Future) 執行緒池,非同步Future,使用場景:併發請求多服務,總耗時為最長響應時間;提升總響應時間,但是阻塞主請求執行緒,高併發時依然會造成執行緒數過多,CPU上下文切換; 全非同步(Cal

Netty之BIO(同步阻塞IO)、PIO(偽非同步阻塞IO)、NIO(非同步阻塞IO)、AIO(非同步阻塞IO)

學習書籍:Netty權威指南 多種IO方式的比較: 1、BIO(同步阻塞IO) 使用ServerSocket繫結IP地址和監聽埠,客戶端發起連線,通過三次握手建立連線,用socket來進行通訊,通過輸入輸出流的方式來進行同步阻塞的通訊 每次客戶端發起連線請求,都會

如何理解同步阻塞、同步阻塞非同步阻塞非同步阻塞

網上閒逛技術貼,看見一個關於理解同步阻塞、同步非阻塞、非同步阻塞、非同步非阻塞比較風趣的故事,簡單明瞭,很容易理解,因此記錄一下,希望更多人能看見。 故事原文: 老張愛喝茶,廢話不說,煮開水。出場人物:老張,水壺兩把(普通水壺,簡稱水壺;會響的水壺,簡稱響水壺)。 1 老張把水壺

Python非同步阻塞IO多路複用Select/Poll/Epoll使用

有許多封裝好的非同步非阻塞IO多路複用框架,底層在linux基於最新的epoll實現,為了更好的使用,瞭解其底層原理還是有必要的。 下面記錄下分別基於Select/Poll/Epoll的echo server實現。 Python Select Server,可監控事件數

基於Socket的多執行緒和非同步阻塞模式程式設計

      剛開始接觸socket的程式設計的時候,遇到了很多的問題,費了很大勁搞懂。其實往往都是一些比較基本的知識,但是都是很重要的,只要對其熟練的掌握後,相信對基於網路的程式設計會有很大的提高,呵呵。       就拿基於C/S結構的例子來說,我們先看看伺服器和客戶端的流

非同步阻塞引發的思考

Tornado是一個用Python編寫的非同步HTTP伺服器,同時也是一個web開發框架。作為Web框架,是一個輕量級的Web框架,其擁有非同步非阻塞IO的處理方式。Tornado有較為出色的抗負載能力,官方用nginx反向代理的方式部署Tornado和其它Python w

java網路程式設計(四)----非同步阻塞aio及proactor模型

(aio)NIO 2.0引入了新的非同步通道的概念,並提供了非同步檔案通道和非同步套接字通道的實現。非同步的套接字通道時真正的非同步非阻塞I/O,對應於UNIX網路程式設計中的事件驅動I/O(AIO)。他不需要過多的Selector對註冊的通道進行輪詢即可實現非

Netty之BIO(同步阻塞IO)、PIO(偽非同步阻塞IO)、NIO(非同步阻塞IO)、AIO(非同步阻塞IO)、Netty

學習書籍:Netty權威指南 多種IO方式的比較: 1、BIO(同步阻塞IO) 使用ServerSocket繫結IP地址和監聽埠,客戶端發起連線,通過三次握手建立連線,用socket來進行通訊,通過輸入輸出流的方式來進行同步阻塞的通訊 每次客戶端發起連線請求,都會啟動一個執

Guava ListenableFuture實現非同步阻塞呼叫

為了保證系統響應迅速,需要尋找一種方法能夠使調取介面能夠非同步執行,而Java正好提供了類似的方法,在java.util.concurrent中包含了Future相關的類,運用其中的一些類可以進行非同步計算,以減少主執行緒的等待時間。比如啟動一個main方