av激情亚洲男人的天堂国语,日韩欧美精品一中文字幕,无码av一区二区三区无码,国产又色又爽又刺激的a片,国产又色又爽又刺激的a片

Python異步IO的未來(從Web后端開發(fā)的角度)

免責(zé)聲明:我是一個工程師,擁有10年以上的 WEB 后端開發(fā)經(jīng)驗(yàn),大部分職業(yè)生涯都在編寫 Python代碼。所以本文大部分文字描述可能跟軟件開發(fā)的其他領(lǐng)域無關(guān),同樣的,也跟使用 JVM 或 CLR 的開發(fā)者無關(guān),他們只是用不同的方式解決問題。

開發(fā)Web應(yīng)用程序看起來與我們10年前做的有很大的不同?,F(xiàn)在,我們用微服務(wù)建立的一切。它徹底改變了我們的應(yīng)用程序的架構(gòu)。

 2014年,如果你還在構(gòu)建完整巨大的 web 應(yīng)用程序,你需要改變這一行為,否則很快會被解雇

雖然我們的應(yīng)用程序的設(shè)計(jì)發(fā)生了很大變化,但是我們的工具沒有。這里我要介紹未來我會如何編寫微服務(wù)。但是,首先,讓我們來看看我們有什么。

全局解釋鎖

對于聲名狼籍的Python GIL,Python的支持者說的比較多的是其他的腳本語言也有(Ruby,Perl,Node.js,還有一些)。對于不好的語言解釋器的設(shè)計(jì),這是圣戰(zhàn)的源頭,但這對于web應(yīng)用從來不是問題。我們總是以來許多進(jìn)程共享一個數(shù)據(jù)庫。

在微服務(wù)面前,全局解釋鎖更加不是問題了。在大多數(shù)情況下,單個微服務(wù)甚至比十年前的典型web應(yīng)用還小。盡管很小,它每秒也可以處理大量的請求,大多數(shù)是因?yàn)樗槍Σ樵兊念愋?,進(jìn)行了高度的定制化和很好的調(diào)整。

而當(dāng)構(gòu)建的微服務(wù)需要等待其他服務(wù)回復(fù)的時間時,這開始成為一個問題。好戲由此開場。。。

 “異步I / O,或非阻塞I/ O是輸入/輸出處理的一種形式,它允許其他進(jìn)程繼續(xù)在傳輸完成之前?!? 維基百科

任何異步庫基本是從左側(cè)到右側(cè)的流程來調(diào)整代碼的:

同步(左)與異步(右)請求進(jìn)程對比圖

Python的異步I/O支持是相當(dāng)?shù)暮?。有一堆庫可以做這個工作(Twisted, Tornado, Gevent, Eventlet,這里僅列舉幾個)。每個庫都支持很多協(xié)議。你可以使用MySQL, Mongo, PostgreSQL, Redis, Memcache, ElasticSearch...,幾乎每個DB,和許多其他得服務(wù)。一些奇異的協(xié)議,像SSH或者Beanstalk只在幾個庫中支持。不過這些都不是問題,寫另一個協(xié)議或從一個I/O框架移植到另一個也不是很難。

當(dāng)然,每一個I/O庫都支持客戶端和服務(wù)端HTTP。想必,這就是為什么HTTP是最常見的用于微服務(wù)之間通信的協(xié)議。不過大多數(shù)框架也支持各種其他協(xié)議(msgpack-rpc, thrift, zeromq, ice,這里僅列舉幾個)。

有很多框架存在,彼此在不同協(xié)議的使用便捷性和其他種類的并發(fā)抽象上的有所不同。當(dāng)然對不同協(xié)議的支持已經(jīng)變得越來越流行。直到Twisted在2002年發(fā)布,這種狀況才有所改變。是的,即使當(dāng)python支持了yield和stackless及greenlets出現(xiàn),便捷性確實(shí)大大增加了,但是這也僅僅是增加一點(diǎn)點(diǎn)的便捷性。真正的改變是在2002年。

但是有些事情是大多數(shù)Python框架的弱項(xiàng)。當(dāng)你在一個線程中操控很多個客戶端的請求時,你有可能把它們管道進(jìn)單個連接中。也就是說,如果你在前端有三個GET請求,你可能向MySQL數(shù)據(jù)庫發(fā)送三次請求,但不等待回復(fù)。一旦收到(數(shù)據(jù)庫)的數(shù)據(jù)就盡快回復(fù)客戶端。就像上面圖中表示的,但是使用了單個DB連接。大多數(shù)python框架現(xiàn)在,在請求開始時從連接池中拉取一個連接,在請求結(jié)束時釋放連接,這樣高效的保證連接數(shù)目與同時請求數(shù)目相等。

對于多數(shù)數(shù)據(jù)庫來說,成千上萬個連接仍然是個問題。即使的典型的Sharding也無濟(jì)于事,因?yàn)閷γ總€shard來說也是相同數(shù)目的連接。很多用戶采用了特殊的微服務(wù)(microservice)。這不僅僅是數(shù)據(jù)庫的問題,許多微服務(wù)(microservice)也同樣遇到這樣問題。

幸好asyncio架構(gòu)允許更容易的構(gòu)建流水線(pipelining),所以越來越多的asyncio協(xié)議采用這種技術(shù)。不幸的是連接代價巨大的數(shù)據(jù)庫(比如MySQL和PostgreSQL)使用了不支持(該技術(shù))的C庫,而且沒有人有足夠的重視來寫一個更好的。

使用像Resque一樣的發(fā)布-訂閱

當(dāng)前,許多工程團(tuán)隊(duì)圍繞著發(fā)布-訂閱來構(gòu)建微服務(wù)(microservices)架構(gòu)。比如,他們運(yùn)用RabbitMQ或者其競爭者之一的產(chǎn)品將所有內(nèi)容發(fā)布成消息(message)。他們相信這是簡化他們的建構(gòu):

1.單總線(Single Bus),無須再考慮這個

2.能夠無需等待回復(fù)即可發(fā)布消息。不需要任何異步庫即可高效的獲取異步I/O

當(dāng)部分工程師們以為這就是答案的時候,我認(rèn)為這不適合普遍的情況。

 我認(rèn)為將我的設(shè)計(jì)決策限制在使用特定插件及特定的消息調(diào)度算法不會解決我的所有網(wǎng)絡(luò)問題。

Zeromq 和 Nanomsg 方式

另一個頗具魅力的微服務(wù)架構(gòu)是Zeromq。如果你對它不熟悉,你應(yīng)該盡快了解它。Zeromq只不過是巧合的使用MQ(消息隊(duì)列)作為后綴,畢竟它不像 RabbitMQ,Kafka以及其他的那樣擁有中心消息隊(duì)列。它是以socket形式工作在steroids。也就是說,它看起來就像常規(guī)的socket,確實(shí)會自動發(fā)送消息(分割TCP數(shù)據(jù)流為幀),重新連接,點(diǎn)對點(diǎn)平衡加載等等。

在 Zeromq 世界里有三種方式供你的服務(wù)于其他連接:

1.發(fā)布-訂閱,工作方式基本上和其他發(fā)布-訂閱應(yīng)用程序相同

2.請求-回復(fù),基本與RPC工作方式相同

3.Push-Pull,請求卻不需要回復(fù),或者發(fā)布-訂閱發(fā)送消息到單一接收端(基于輪叫)

Nanomsg 做到了更多事情,它不僅支持上面提到的所有方式,而且增加了更多的通信模式(單就nanomsg而言):

1.監(jiān)督者-應(yīng)答者(Surveyor-respondent),允許向多點(diǎn)發(fā)送請求而且接收來至所有的請求

2.總線(Bus),允許向任何點(diǎn)發(fā)送消息

更多的是:nanomsg的前景在于通信模式是插件式的,也就是說,在未來更多的通信模式會被增加到庫中。

 我相信更多的通信模式會出現(xiàn),而使用發(fā)布-訂閱或者HTTP難逃厄運(yùn)

像nanomsg和zeromq作為腳本語言的優(yōu)勢是:它們在一個單獨(dú)的線程中操控I/O。所以當(dāng)你使用python做一些事情操控全局解釋器鎖(GIL)時,你的zeromq線程保持你的連接,清空消息緩沖器,接收和建立連接等等。

真實(shí)世界中的微服務(wù)

當(dāng)聰明的黑客們創(chuàng)建了像 zeromq 、 nanomsg 和發(fā)布-訂閱總線( publish-subscribe buses )這樣優(yōu)秀的項(xiàng)目的時候,實(shí)干的工程師們卻仍然使用舊的技術(shù)干活。

到目前為止我還沒有看到使用 zeromq 作為數(shù)據(jù)庫存取通訊方式的數(shù)據(jù)庫。嗯,現(xiàn)在是有很少一些使用了 zeromq 的開源服務(wù)?;旧纤鞋F(xiàn)代的數(shù)據(jù)庫在通訊方式的實(shí)現(xiàn)上目前分成了以下兩個陣營:

1.創(chuàng)建并使用自身協(xié)議

2.使用 HTTP 協(xié)議

在這個方面數(shù)據(jù)庫算是個比較突出的例子。另外的例子比如 Docker , Docker 使用了基于 unix sockets 的 HTTP 協(xié)議作為其通訊協(xié)議,然而,當(dāng)她需要使用全雙工流( full-duplex streams )來替代請求-回應(yīng)( request-reply )模式的時候,就只能很不優(yōu)雅地打破了其使用的協(xié)議的語義( protocol semantics )。

HTTP協(xié)議

我不得不說一些關(guān)于HTTP的事情。

對HTTP的支持普遍存在,但是使用python會很低效。不僅是因?yàn)椴荒芟駔eromq那樣在其他線程處理事務(wù),對HTTP解析也很緩慢。常常持久連接(keep-alive connection)也不受支持。

同時HTTP是復(fù)雜的,如果你認(rèn)為不是這樣,你就錯了。來看個簡單的例子吧。你可能寫出下面的代碼:

 
 
  1. def simple_app(environ, start_response):
  2.     status = '304 Not Modified'
  3.     headers = [('Content-Length', '5')]
  4.     start_response(status, headers)
  5.     return [b'hello']

理論上服務(wù)器會返回一個寫有“hello”的頁面(在wsgiref下測試),但是實(shí)際上:

  The 304 response MUST NOT contain a message-body, and thus is always terminated by the first empty line after the header fields   

(304響應(yīng)必須不包含消息體,因此通常會被頭域后的第一個空行終止)

意思是說“hello”行會被客戶端在下次請求時識別成響應(yīng)的第一行。這在一些設(shè)置中會導(dǎo)致緩存污染和安全漏洞。有多少使用HTTP的程序員意識到了這個現(xiàn)象呢?還有更多莫名其妙的細(xì)節(jié)。

 HTTP不要在內(nèi)部消息中使用,因?yàn)樗菀资褂玫缓唵危踔翉?fù)雜到使用了5個RFC也只描述了基本內(nèi)容。即使誤解最簡單的內(nèi)容可能會導(dǎo)致安全漏洞。

說實(shí)話,大多數(shù)微服務(wù)(microservice)使用了HTTP的子集,比如只認(rèn)可200的響應(yīng)碼(其他的都作為失敗),不使用特別的數(shù)據(jù)頭或類似的,這可能不會出問題。當(dāng)然這不是真正意義上的HTTP(但是經(jīng)常用來負(fù)載均衡的代理則需要真正的HTTP,比如HAProxy),而且需要對HTTP特性非常熟悉的人才能構(gòu)建安全的HTTP子集。

那么Zeromq怎么樣

首先,它不是那種多功能的軟件:

1.它很難拓展(hack on)(使用了復(fù)雜的C++ Actor模型)

2.嵌入進(jìn)一些程序的效果欠佳(也就是說它沒有使用好fork)

3.對故障切換(failover)和服務(wù)發(fā)現(xiàn)(service discovery)整合欠佳

4.對非冪等(non-idempotent)請求和有狀態(tài)路由(stateful routing)操控欠佳

Nanomsg在(1)表現(xiàn)相對要好但遠(yuǎn)未達(dá)到完美。而在當(dāng)前的設(shè)計(jì)中(2)還不能解決。(3)nanoconfig庫為nanomsg解決,但卻比nanomsg本身受到了更少的關(guān)注。(4)在nanomsg中可能最終解決但現(xiàn)在還沒有。

第二個大問題是工程師們還不太適應(yīng)它的思考方式,例如對同樣的連接,redis協(xié)議使用發(fā)布-訂閱(pub-sub)和請求-響應(yīng)(req-rep),mongo使用push-pull和請求-響應(yīng)(req-rep),而zeromq不允許。nonomsq特別想修正工程師們頭腦中的這種想法,但這條路還很長。

別誤解我,zeromq很好。nanomsq從這個失誤中學(xué)到了很多,當(dāng)它可用于生產(chǎn)環(huán)境時,它將是我用于服務(wù)間消息傳遞的第一選擇。

#p#

但是微服務(wù)怎么了?

好吧,最簡單的原因是,僅僅使用zeromq你甚至不能構(gòu)建一個很小的服務(wù)。但是如果你的DB支持HTTP,你就可以使用HTTP在客戶端和服務(wù)端兩端構(gòu)建服務(wù)。聽起來很簡單(但是記住HTTP很復(fù)雜)。

另外一個問題是I/O模型。當(dāng)你的代碼是單線程的,你就不能在不使用連接的情況下,依然保持連接心跳。即使你使用異步循環(huán),它也可能因做其他運(yùn)算而停頓很久。

有時你想給連接發(fā)請求,但是連接實(shí)際上已經(jīng)關(guān)閉了。有種廣泛使用的方法,讀取連接數(shù)據(jù),檢出是否可用,因?yàn)橥ǔ0l(fā)送請求后很難恢復(fù):

 
 
  1. if s.read() == b'':
  2.   self.reconnect()
  3. s.write(request)
  4. response = s.read()

這意味著連接僅當(dāng)你發(fā)送請求時才開始建立,而不是當(dāng)zeromq或nanomsg中那樣只要準(zhǔn)備好了。

而且這樣也不好做服務(wù)發(fā)現(xiàn),現(xiàn)在你有三種簡單的選擇:

1.每次請求前檢查服務(wù)名字(如解析DNS)

2.下次連接請求時解析服務(wù)名字

3.永不更新服務(wù)(即,直到進(jìn)程重啟)

大部分用戶選擇(3)。有時(2)可以直接用(work out of the box, 開箱即可用),但是它只有機(jī)器不可達(dá)后故障切換時才會發(fā)生。(1)相當(dāng)?shù)托?,幾乎不可用?/p>

I/O內(nèi)核設(shè)計(jì)

(I/O內(nèi)核線程與Python線程使用RPC交互)

所以,我建議重新設(shè)計(jì)所有IO子系統(tǒng),即,用C(或其他無GIL的語言)寫個庫來處理IO,這樣IO與程序主線程無關(guān)了。它應(yīng)該與python主線程使用類似消息(messaging)的機(jī)制來通信。但是,不能發(fā)送Python對象,也不要在I/O線程內(nèi)持有GIL。

I/O內(nèi)核要支持多種協(xié)議,每個協(xié)議應(yīng)該:(a)處理握手,(b)把流切分成消息,這樣完整的消息才會被轉(zhuǎn)發(fā)給主線程。如果可以設(shè)計(jì)連接細(xì)節(jié),比如自動故障切換(automatic failover)的主從關(guān)系(master/slave relations),就更好了。

I/O線程應(yīng)該可以解析名字,處理連接請求,能夠訂閱DNS名字變化,以及其他的一些高級特性。

注意,這些不僅僅對python有用,也適用于其他有GIL的腳本語言。事實(shí)上,對無GIL的語言,也能很好地工作,但可能沒那個必要。

先前的做法

這個思路部分存在于很多產(chǎn)品中:

1.上文提到的zeromq和nanomsg使用不同的線程來處理I/O

2.Kazoo(python版的zookeeper)使用單獨(dú)的(python式的)線程處理重連、ping連接

3.Twisted把阻塞計(jì)算轉(zhuǎn)移到線程池(盡管我們需要相反的東西,這已經(jīng)算是工作量解除了)

也許還有更多的例子,我仍然沒有看到用單獨(dú)線程來創(chuàng)建統(tǒng)一的I/O內(nèi)核的嘗試。如果你知道,告訴我。

這種模式和最近出現(xiàn)的Ambassador模式很像。Ambassador是個進(jìn)程,存在于每臺機(jī)器,進(jìn)行服務(wù)發(fā)現(xiàn),但是通過自身代理所有連接,即,所有服務(wù)都連接到localhost上Ambassador監(jiān)聽的端口,然后Ambassador把連接轉(zhuǎn)發(fā)到真正的服務(wù)上去。類似的,I/O內(nèi)核也應(yīng)該代替主線程進(jìn)行服務(wù)發(fā)現(xiàn)、與服務(wù)進(jìn)行通信(協(xié)議仍然與Ambassador使用的那個有很大不同)。

意義所在

 難道是為了性能上能提升幾毫秒?

對。事實(shí)上,當(dāng)使用多個服務(wù)來處理單個前端請求時,毫秒級的延遲累積地相當(dāng)快。而且,這種技術(shù)可以在CPU使用率接近100%時,能挽救非線性增長的延遲。

 還是為了保持持久連接?如果你足夠小心地經(jīng)常放棄CPU,它們在傳統(tǒng)的異步I/O上也工作得很好。

對。如果你在用異步I/O,那你已經(jīng)非常出色了,因?yàn)楹芏嗳烁緵]有看到這種必要。但是服務(wù)發(fā)現(xiàn)需要多少異步庫才算合理?(我的回答是:一個都不需要)

但用異步I/O也能進(jìn)行服務(wù)發(fā)現(xiàn)。

當(dāng)然。但是沒人這樣做。我認(rèn)為應(yīng)該趁機(jī)也解決這個問題。

下面是我設(shè)想的I/O內(nèi)核應(yīng)該做的任務(wù):

檢測和統(tǒng)計(jì)

對CPU密集型的任務(wù)來說,定期發(fā)送統(tǒng)計(jì)會比較困難。這個需要被修復(fù)。主線程應(yīng)該遞增在某個內(nèi)存區(qū)域的計(jì)數(shù)器,而完全與I/O線程無關(guān)。

而且我們獲取請求-響應(yīng)計(jì)時器的精確時間戳。通常它們對python主循環(huán)的工作量評估嚴(yán)重不準(zhǔn)。

在正確的服務(wù)發(fā)現(xiàn)幫助下,我們甚至可以在真正的用戶試圖在此worker上執(zhí)行請求前,知道哪些必要的服務(wù)不可用。

調(diào)試

假設(shè)你可以讓Python在任何時間獲取狀態(tài)。首先,我們總會有一批在處理中的請求。而且,我們可以像統(tǒng)計(jì)一樣,貼上一些標(biāo)記點(diǎn)。最后,我們可以使用類似錯誤處理器(faulthandler)所用的一種方法來找出主線程的棧。

關(guān)鍵在于,有個線程可以回應(yīng)調(diào)試請求,甚至是當(dāng)主線程在做CPU密集型的事情,或者因?yàn)槟承┰驋炱饡r。

管道

請求應(yīng)該盡可能通過管道傳輸,即,不管哪個前端請求需要數(shù)據(jù)庫請求,我們通過一個數(shù)據(jù)庫連接發(fā)送全部。

這樣,db連接數(shù)就可以很少,而且允許我們統(tǒng)計(jì)哪份副本較慢。

名字發(fā)現(xiàn)

我們不僅要解析DNS名字(不管我們選擇的是什么名字解析方案),還要當(dāng)名字變化時獲取更新,比如zookeeper中的設(shè)置watch。

這個過程必須對應(yīng)用透明,并且在應(yīng)用開始請求前,連接已經(jīng)存在。

統(tǒng)一

既然I/O內(nèi)核就位,各個python的I/O框架只需要支持內(nèi)核支持的協(xié)議。所有的新協(xié)議應(yīng)該在內(nèi)核里完成。這促使框架在方便上和效率上競爭,而不是協(xié)議的支持上。

節(jié)流(Throttling)

即使在Java和Go這些可以自由使用線程的語言里,也需要控制客戶端的連接數(shù)。該設(shè)計(jì),不管到底哪個庫才是網(wǎng)絡(luò)請求的真正執(zhí)行者,允許控制應(yīng)用中單個位置處的請求數(shù)目。

設(shè)計(jì)隨想

下面是關(guān)于設(shè)計(jì)I/O內(nèi)核的一些隨想(沒有順序),有些可能在最終設(shè)計(jì)時會被去掉。

1.I/O內(nèi)核應(yīng)該是單線程的。因?yàn)椴惶赡苡肞ython代碼重載用C實(shí)現(xiàn)的I/O線程。相比于nanomsg或者zeromq,設(shè)計(jì)選擇更加簡單。

2.所有I/O應(yīng)該在使用最小互鎖(minimal interlocking)的I/O線程內(nèi)完成。因?yàn)閱拘哑渌€程比執(zhí)行python字節(jié)碼的開銷要小(這樣設(shè)計(jì)也更簡單)。

3.相較于持有GIL(全局解釋鎖,global interpreter lock),從其他python對象復(fù)制數(shù)據(jù)代價更小。但是,如果可行,應(yīng)該直接分配非python的緩沖區(qū),并直接將數(shù)據(jù)串行化進(jìn)去。

4.所有支持的協(xié)議至少應(yīng)該可以用C切分成幀。這樣不完整的包不會到達(dá)python代碼,其他解析應(yīng)該在主線程內(nèi)用python對象直接完成。

5.服務(wù)發(fā)現(xiàn)(service discovery)應(yīng)該可插拔(pluggable)。最可能的選項(xiàng)應(yīng)該最先被實(shí)現(xiàn)(比如DNS名稱查詢)。

6.服務(wù)發(fā)現(xiàn)應(yīng)該可以被簡單地集成到任何協(xié)議。事實(shí)上,對協(xié)議的實(shí)現(xiàn)者來說,使用服務(wù)發(fā)現(xiàn)比忽略它更簡單。

結(jié)束

當(dāng)然建立這樣的工具不是一個周末就可以完成的事情。這是一份艱苦的工作,而且是無限長的旅程。

直到現(xiàn)在也適合重新思考為什么我們使用Python操控網(wǎng)絡(luò)。最近的工具比如穩(wěn)定的libuv和Rust語言可以極大的簡化建立I/O內(nèi)核。當(dāng)然可以明智的使用go-python來原型化(prototype)代碼,但這容易做但是不是長久之計(jì)。

論及的方案,使用起來很簡潔。期望在未來我們能使用python建立高效,高性能的服務(wù),尤其是在動態(tài)網(wǎng)絡(luò)配置方面,從而在性能差異明顯的問題上不需要使用其他語言重寫所有內(nèi)容。

 
這意味著連接僅當(dāng)你發(fā)送請求時才開始建立,而不是當(dāng)zeromq或nanomsg中那樣只要準(zhǔn)備好了。    
理論上服務(wù)器會返回一個寫有“hello”的頁面(在wsgiref下測試),但是實(shí)際上:


網(wǎng)站欄目:Python異步IO的未來(從Web后端開發(fā)的角度)
文章分享:http://uogjgqi.cn/article/cciiopo.html
掃二維碼與項(xiàng)目經(jīng)理溝通

我們在微信上24小時期待你的聲音

解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流