日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > python >内容正文

python

python websocket异步高并发_高并发异步uwsgi+web.py+gevent

發(fā)布時間:2023/12/2 python 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python websocket异步高并发_高并发异步uwsgi+web.py+gevent 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

為什么用web.py?

python的web框架有很多,比如webpy、flask、bottle等,但是為什么我們選了webpy呢?想了好久,未果,硬要給解釋,我想可能原因有兩個:第一個是兄弟項目組用webpy,被我們組拿來主義,直接用了;第二個是我可能當(dāng)時不知道有其他框架,因為剛工作,知識面有限。但是不管怎么樣,webpy還是好用的,所有API的URL和class在一個文件中進(jìn)行映射,可以很方便地查找某個class是為了哪個API服務(wù)的。(webpy的其中一個作者是Aaron Swartz,這是個牛掰的小伙,英年早逝)

這里對webpy、flask、bottle性能進(jìn)行了測試,測試結(jié)果詳見:webpy/flask/bottle性能測試

wsgi,這是python開發(fā)中經(jīng)常遇到詞(以前只管用了,趁寫博客之際,好好學(xué)習(xí)下細(xì)節(jié))。

wsgi協(xié)議

WSGI的官方文檔參考http://www.python.org/dev/peps/pep-3333/。WSGI是the Python Web Server Interface,它的作用是在協(xié)議之間進(jìn)行轉(zhuǎn)換。WSGI是一個橋梁,一邊是web服務(wù)器(web server),一邊是用戶的應(yīng)用(wsgi app)。但是這個橋梁功能非常簡單,有時候還需要別的橋(wsgi middleware)進(jìn)行幫忙處理,下圖說明了wsgi這個橋梁的關(guān)系。

一個簡單的WSGI應(yīng)用:

def simple_app(environ, start_response):

"""Simplest possible application object"""

status = '200 OK'

response_headers = [('Content-type', 'text/plain')]

start_response(status, response_headers)

return [HELLO_WORLD]

這個是最簡單的WSGI應(yīng)用,那么兩個參數(shù)environ,start_response是什么?

evniron是一系列環(huán)境變量,參考https://www.python.org/dev/peps/pep-3333/#id24,用于表示HTTP請求信息。(為了讓理解更具體一點,下表給出一個例子,這是uWSGI傳遞給wsgi app的值)

{

'wsgi.multiprocess': True,

'SCRIPT_NAME': '',

'REQUEST_METHOD': 'GET',

'UWSGI_ROUTER': 'http',

'SERVER_PROTOCOL': 'HTTP/1.1',

'QUERY_STRING': '',

'x-wsgiorg.fdevent.readable': ,

'HTTP_USER_AGENT': 'curl/7.19.7(x86_64-unknown-linux-gnu)libcurl/7.19.7NSS/3.12.7.0zlib/1.2.3libidn/1.18libssh2/1.2.2',

'SERVER_NAME': 'localhost.localdomain',

'REMOTE_ADDR': '127.0.0.1',

'wsgi.url_scheme': 'http',

'SERVER_PORT': '7012',

'uwsgi.node': 'localhost.localdomain',

'uwsgi.core': 1023,

'x-wsgiorg.fdevent.timeout': None,

'wsgi.input': ,

'HTTP_HOST': '127.0.0.1: 7012',

'wsgi.multithread': False,

'REQUEST_URI': '/index.html',

'HTTP_ACCEPT': '*/*',

'wsgi.version': (1,0),

'x-wsgiorg.fdevent.writable': ,

'wsgi.run_once': False,

'wsgi.errors': ,

'REMOTE_PORT': '56294',

'uwsgi.version': '1.9.10',

'wsgi.file_wrapper': ,

'PATH_INFO': '/index.html'

}

start_response是個函數(shù)對象,參考https://www.python.org/dev/peps/pep-3333/#id26,其定義為為start_response(status, response_headers, exc_info=None),其作用是設(shè)置HTTP status碼(如200 OK)和返回結(jié)果頭部。

WSGI服務(wù)器

wsgi服務(wù)器就是為了構(gòu)建一個讓W(xué)SGI app運行的環(huán)境。運行時需要傳入正確的參數(shù),以及正確地返回結(jié)果,最終把結(jié)果返回客戶端。工作流程大致是,獲取客戶端的request信息,封裝到environ參數(shù),然后把執(zhí)行結(jié)果返回給客戶端。

對于webpy而言,其內(nèi)置了一個CherryPy服務(wù)器。CheckPy在運行時,采用多線程模式,主線程負(fù)責(zé)accept客戶端請求,將請求放入一個request Queue里面,然后有N個子線程負(fù)責(zé)從Queue中取出request,然后處理后將結(jié)果返回給客戶端。不過看起來,這個內(nèi)置的服務(wù)器看起來是為了開發(fā)調(diào)試用,因為webpy并未開放這個默認(rèn)容器的參數(shù)調(diào)節(jié),例如線程數(shù)目,所以為了尋求高效地托管WSGI app,不建議用這個默認(rèn)的容器。這可能也是使用uWSGI的原因,因為我們的線上系統(tǒng)有幾個API的訪問量很大,目前大概是250萬次/天。(當(dāng)然對于業(yè)務(wù)量不是很大的服務(wù),可能這個默認(rèn)的CheckPy也就夠了)

uWSGI

我們知道,Python有把大鎖GIL,會將多個線程退化為串行執(zhí)行,所以一個多線程python進(jìn)程,并不能充分使用多核CPU資源,所以對于Python進(jìn)程,可能采用多進(jìn)程部署方式比較有利于充分利用多核的CPU資源。

uWSGI就是這么一個項目,可以以多進(jìn)程方式執(zhí)行WSGI app,其工作模式為 1 master進(jìn)程 + N worker進(jìn)程(N*m線程),主進(jìn)程負(fù)責(zé)accept客戶端request,然后將請求轉(zhuǎn)發(fā)給worker進(jìn)程,因此最終是worker進(jìn)程負(fù)責(zé)處理客戶端request,這樣很方便的將WSGI app以多進(jìn)程方式進(jìn)行部署。以下給出uwsgi響應(yīng)客戶端請求的執(zhí)行流程圖:

值得注意的是,在master進(jìn)程接收到客戶端請求時,以round-bin方式分發(fā)給worker進(jìn)程,所以多個process在處理前端請求時,所承受的負(fù)載相對還是均衡的。(這是我測試時的經(jīng)驗,改天再扒一下uwsgi的源代碼確認(rèn)一下 TODO)。關(guān)于uWSGI的使用,可能并不是這里的重點,不再贅述。

(其實看到uWSGI的多進(jìn)程模型,我想到了Nginx,它也是多進(jìn)程模型,這個也很有意思,由此了解了thunder herd問題,扯遠(yuǎn)了,繼續(xù)往下說)

考慮一個應(yīng)用場景:client向serverM(uwsgi)發(fā)起一個HTTP請求,serverM在處理這次請求時,需要訪問另一個服務(wù)器serverN,直到serverN返回數(shù)據(jù),serverM才會返回結(jié)果給client,即wsgi app是同步的。假如serverM訪問serverN花費時間比較久,那么若是client請求數(shù)量比較多的情況下,(N*m)線程都會被占用,可想而知,大容量下的并發(fā)處理能力就受(N*m)的限制。

如果碰上了這種情況,怎么解決呢?

(1)增大N,即worker的數(shù)量:在增加進(jìn)程的數(shù)量的時候,進(jìn)程是要消耗內(nèi)存的,并且如果進(jìn)程數(shù)量太多的情況下(并且進(jìn)程均處于活躍狀態(tài)),進(jìn)程間的切換會消耗系統(tǒng)資源的,所以N并不是越大越好。一般情況下,可能將進(jìn)程數(shù)目設(shè)置為CPU數(shù)量的2倍。

(2)增大m,即worker的線程數(shù)量:在創(chuàng)建線程的時候,最大能夠多大呢?由于線程棧是要消耗內(nèi)存的,因此線程的數(shù)量跟系統(tǒng)設(shè)置(virtual memory)和(stack size)有關(guān)。線程數(shù)量太大會不會不太好?(這個肯定不好,我答不上來 TODO)

由此在大并發(fā)需求的情況下,我了解到了C10K問題,并進(jìn)一步學(xué)習(xí)到I/O的多路復(fù)用的epoll,可以避免阻塞調(diào)用在某個socket上。比如libevent就是封裝了多個平臺的高效地I/O多路復(fù)用方法,在linux上用的就是epoll。但是這里我們不討論epoll或者libevent的使用,我們這里引入gevent模塊。

gevent協(xié)程

gevent在使用時,跟thread的接口很像,但是thread是由操作系統(tǒng)負(fù)責(zé)調(diào)度,而gevent是用戶態(tài)的“線程”,也叫協(xié)程。gevent的好處就是無需等待I/O,當(dāng)發(fā)生I/O調(diào)用是,gevent會主動切換到另一gevent進(jìn)行運行,這樣在等待socket數(shù)據(jù)返回時,可以充分利用CPU資源。

在使用gevent內(nèi)部實現(xiàn):

1. gevent 協(xié)程切換使用greenlet項目,greenlet其實就是一個函數(shù),及保存函數(shù)的上下文(也就是棧),greenlet的切換由應(yīng)用程序自己控制,所以非常適合對于I/O型的應(yīng)用程序,發(fā)生I/O時就切換,這樣能夠充分利用CPU資源。

2. gevent在監(jiān)控socket事件時,使用了libevent,就是高級的epoll。

3. python中有個猴子補(bǔ)丁(monkey patch)的說法,在python進(jìn)程中,python的函數(shù)都是對象,存在于進(jìn)程的全局字典中,因此,開發(fā)者可以通過替換這些對象,來改變標(biāo)準(zhǔn)庫函數(shù)的實現(xiàn),這樣還不用修改已有的應(yīng)用程序。在gevent里,也有這樣的monkey patch,通過gevent.monkey.patch_all()替換掉了標(biāo)準(zhǔn)庫中阻塞的模塊,這樣不用修改應(yīng)用程序就能充分享受gevent的優(yōu)勢了。(真是方便啊)

使用gevent需注意的問題

1. 無意識地引入阻塞模塊

我們知道gevent通過monkey patch替換掉了標(biāo)準(zhǔn)庫中阻塞的模塊,但是有的時候可能我們會“無意識”地引入阻塞模塊,例如MySQL-Python,pylibmc。這兩個模塊是通過C擴(kuò)展程序?qū)崿F(xiàn)的,都需要進(jìn)行socket通信,由于調(diào)用的底層C的socket接口,所以超出了gevent的管控范圍,這樣就在使用這兩個模塊跟mysql或者memcached進(jìn)行通信時,就退化為了阻塞調(diào)用。

這樣一個應(yīng)用場景:在一個gevent進(jìn)程中,基于MySQL-Python模塊,創(chuàng)建一個跟mysql有10個連接的連接池,當(dāng)并發(fā)量大的情況下,我們期望這10個連接可以同時處理對mysql的10個請求。實際如我們期望的這樣么?的確跟期望不一樣,因為在conn.query()的時候,阻塞了進(jìn)程,這樣意味著同一時刻不可能同時有2個對mysql的訪問,所以并發(fā)不起來了。如mysql響應(yīng)很慢的話,那么整個process跟hang住了沒什么兩樣(這個時候,便不如多線程的部署模式了,因為多個線程的話,一個線程hang住,另一個線程還是有機(jī)會執(zhí)行的)。

如何解決:在這些需要考慮并發(fā)的效率的場景,盡量避免引入阻塞模塊,可以考慮純python實現(xiàn)的模塊,例如MySQL-Python->pymysql, pylibmc->memcached(這個python實現(xiàn)的memcached client可能不太完備,例如一致性hash目前都沒實現(xiàn))。

2. gevent在遇到I/O訪問時,會進(jìn)行g(shù)reenlet切換。若是某個greenlet需要占用大量計算,那么若是計算任務(wù)過多(激進(jìn)一點,陷入死循環(huán)),可能會導(dǎo)致其他greenlet沒有機(jī)會執(zhí)行。若是一個gevent進(jìn)程需要執(zhí)行多個任務(wù)時,若某個任務(wù)計算過多,可能會影響其他任務(wù)的執(zhí)行。例如我曾遇到一個進(jìn)程中,采用生產(chǎn)者任務(wù)(統(tǒng)計數(shù)據(jù),將結(jié)果放入內(nèi)存)+消費者任務(wù)(將計算結(jié)果寫入磁盤),然而當(dāng)數(shù)據(jù)很大的時候,生產(chǎn)者任務(wù)占用大量的CPU資源,然而消費者任務(wù)不能及時將統(tǒng)計結(jié)果寫入磁盤,即生產(chǎn)太快,消費太慢,這樣內(nèi)存占用越來越多,一度高達(dá)2G內(nèi)存。所以鑒于此,需要根據(jù)任務(wù)的特點(I/O密集或者CPU密集),合理分配進(jìn)程任務(wù)。

參考:

以上為工作經(jīng)驗總結(jié),在整理成文的時候,才發(fā)現(xiàn)有些知識點只是一知半解,所以需要繼續(xù)完善該文。

總結(jié)

以上是生活随笔為你收集整理的python websocket异步高并发_高并发异步uwsgi+web.py+gevent的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。