httpclient异步发送请求_关于Tornado5.1:到底是真实的异步和还是虚假的异步
原文轉載自「劉悅的技術博客」https://v3u.cn/a_id_107
我們知道Tornado 優秀的大并發處理能力得益于它的 web server 從底層開始就自己實現了一整套基于 epoll 的單線程異步架構,其他 web 框架比如Django或者Flask的自帶 server 基本是基于 wsgi 寫的簡單服務器,并沒有自己實現底層結構。而tornado.ioloop 就是 tornado web server 最底層的實現。
ioloop 的實現基于 epoll ,那么什么是 epoll? epoll 是Linux內核為處理大批量文件描述符而作了改進的 poll / select 。
那么到底什么是 poll / select ? socket 通信時的服務端,當它接受( accept )一個連接并建立通信后( connection )就進行通信,而此時我們并不知道連接的客戶端有沒有信息發完。 這時候我們有兩種選擇:
一直在這里等著直到收發數據結束;
每隔一會兒來看看這里有沒有數據;
第一種辦法雖然可以解決問題,但我們要注意的是對于一個線程進程同時只能處理一個 socket 通信,其他連接只能被阻塞。 顯然這種方式在單進程情況下不現實。
第二種辦法要比第一種好一些,多個連接可以統一在一定時間內輪流看一遍里面有沒有數據要讀寫,看上去我們可以處理多個連接了,這個方式就是 poll / select 的解決方案。 看起來似乎解決了問題,但實際上,隨著連接越來越多,輪詢所花費的時間將越來越長,而服務器連接的 socket 大多不是活躍的,所以輪詢所花費的大部分時間將是無用的。為了解決這個問題, epoll 被創造出來,它的概念和 poll 類似,不過每次輪詢時,他只會把有數據活躍的 socket 挑出來輪詢,這樣在有大量連接時輪詢就節省了大量時間。
具體說說select:select最早于1983年出現在4.2BSD中,它通過一個select()系統調用來監視多個文件描述符的數組,當select()返回后,該數組中就緒的文件描述符便會被內核修改標志位,使得進程可以獲得這些文件描述符從而進行后續的讀寫操作。
while true {select(streams[])for i in streams[] {if i has dataread until unavailable} }select的優點是支持目前幾乎所有的平臺,缺點主要有如下2個:
1)單個進程能夠監視的文件描述符的數量存在最大限制,在Linux上一般為1024,不過可以通過修改宏定義甚至重新編譯內核的方式提升這一限制。
2)select 所維護的存儲大量文件描述符的數據結構,隨著文件描述符數量的增大,其復制的開銷也線性增長。同時,由于網絡響應時間的延遲使得大量TCP連接處于非活躍狀態,但調用select()會對所有socket進行一次線性掃描,所以這也浪費了一定的開銷。
poll則在1986年誕生于System V Release 3,它和select在本質上沒有多大差別,但是poll沒有最大文件描述符數量的限制。
epoll是Linux 2.6 開始出現的為處理大批量文件描述符而作了改進的poll,是Linux下多路復用IO接口select/poll的增強版本,它能顯著提高程序在大量并發連接中只有少量活躍的情況下的系統CPU利用率。另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內核IO事件異步喚醒而加入Ready隊列的描述符集合就行了。
在select/poll中,進程只有在調用一定的方法后,內核才對所有監視的文件描述符進行掃描,而epoll事先通過epoll_ctl()來注冊一個文件描述符,一旦基于某個文件描述符就緒時,內核會采用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便得到通知。
兩相對比,可以看出來,epoll只輪詢數據活躍的socket,性能自然就比較高了。
而Tornado其實默認是同步阻塞機制的,為了能夠實現異步,你就必須使用異步的寫法才可以,這里有一個簡單的demo:
from tornado.web import RequestHandler import tornado.ioloop import tornado.httpclient import tornado.web import requests#異步任務 class AsyncHandler(RequestHandler):@tornado.web.asynchronousdef get(self):http_client = tornado.httpclient.AsyncHTTPClient()http_client.fetch("http://baidu.com",callback=self.on_fetch)def on_fetch(self, response):print(response)self.write('done')self.finish()#同步任務 class SyncHandler(RequestHandler):def get(self):response = requests.get("http://baidu.com")print(response)self.write('done')def make_app():return tornado.web.Application(handlers=[(r'/async_fetch', AsyncHandler),(r'/sync_fetch', SyncHandler),],debug=True)if __name__ == '__main__':app = make_app()app.listen(8000)tornado.ioloop.IOLoop.current().start()可以看到異步任務我們使用了(回調)和@tornado.web.asynchronous
@tornado.web.asynchronous 并不能將一個同步方法變成異步,所以修飾在同步方法上是無效的,只是告訴框架,這個方法是異步的,且只能適用于HTTP verb方法(get、post、delete、put等)。@tornado.web.asynchronous 裝飾器適用于callback-style的異步方法,對于用@tornado.web.asynchronous 修飾的異步方法,需要主動self.finish()來結束該請求,普通的方法(get()等)會自動結束請求在方法返回的時候。
對比下效率:使用ab命令發送500個請求,每秒50個 ab -n 500 -c 50
結果顯而易見,異步效率更高,15秒完成了同步需要50秒的任務。
但是,要想達到異步效果,就必須使用異步寫法,讓io操作變成異步io,而異步寫法對于后臺研發的綜合素質要求比較高,那么能不能用同步的寫法達成異步效果呢?當然可以,就是使用celery+tornado
最后總結一下:
Tornado的異步原理: 單線程的torndo打開一個IO事件循環, 當碰到IO請求(新鏈接進來 或者 調用api獲取數據),由于這些IO請求都是非阻塞的IO,都會把這些非阻塞的IO socket 扔到一個socket管理器,所以,這里單線程的CPU只要發起一個網絡IO請求,就不用掛起線程等待IO結果,這個單線程的事件繼續循環,接受其他請求或者IO操作,如此循環。
說人話:poll/select: 在一個育嬰室內,護士會對育嬰室內所有的嬰兒挨個check一遍,如此往復。epoll:護士會使用高科技設備對嬰兒進行監聽,并且只會check生命體征有問題(活躍)的嬰兒,如此往復。
另外,對于如果面對超高的并發請求(qps上萬),僅僅采用 epoll 模型是不夠的,我們還必須使用多進程多線程等方式來充分利用系統資源,這就引出了nginx反向代理tornado進行負載均衡。
原文轉載自「劉悅的技術博客」 https://v3u.cn/a_id_107
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的httpclient异步发送请求_关于Tornado5.1:到底是真实的异步和还是虚假的异步的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言筛选法_极少数人用过的另类素数求解
- 下一篇: update 两个表关联_拉链表(二)