Author:放翁(文初)
Date: 2010/6/28
Email:fangweng@taobao.com
圍脖: http://t.sina.com.cn/fangweng
前話
在前面的文章中,先給出了Web服務(wù)請求異步處理的壓力測試報告,從數(shù)據(jù)角度描述了支持Web請求異步化的容器在不同并發(fā)用戶下的處理能力及性能消耗。本文從概念的角度對于應(yīng)用系統(tǒng)異步化,Web服務(wù)請求異步化和Web請求異步化規(guī)范及實現(xiàn)三方面做一個介紹,為系統(tǒng)異步化改造做好基礎(chǔ)準(zhǔn)備。(同樣,文中大部分都是個人意見和想法,非完全正確,歡迎討論)
應(yīng)用系統(tǒng)異步化
Web服務(wù)請求異步化也是應(yīng)用系統(tǒng)異步化的一種,因此首先談一下對于應(yīng)用系統(tǒng)異步化的一些看法和認識。
隨著系統(tǒng)不斷積累和發(fā)展,系統(tǒng)模塊化是必然趨勢,而模塊之間的耦合性和依賴會直接影響系統(tǒng)的穩(wěn)定性和可用性,因此系統(tǒng)異步化概念就產(chǎn)生了。異步化和其他技術(shù)一樣,不是“萬能藥”,在適合的場景揚長避短才能夠體現(xiàn)它的優(yōu)勢,提升系統(tǒng)的可用性和效率。
異步化要素:
1. 會話。
異步模式下,請求和結(jié)果不在一次交互中,因此需要通過會話的設(shè)計方式(增加會話碼)來保證請求和結(jié)果的一一對應(yīng)。另一種方式可以不需要會話,但是要求請求和結(jié)果是保證順序的。(這種設(shè)計方式會使得系統(tǒng)的資源復(fù)用受到限制,同時容錯很難保證,容易“串”,早先用NIO去實現(xiàn)低版本的Memcached客戶端協(xié)議就存在沒有會話號的問題)
2. Callback or Stateful Result。結(jié)果產(chǎn)生如何通知請求方,有兩種手段:a. Callback,通過服務(wù)方主動推送的方式將結(jié)果推送給服務(wù)調(diào)用者。(如果是進程內(nèi),可以通過實現(xiàn)定義的回調(diào)接口方式,如果是進程外,可以通過注冊URL或者WebService等方式來實現(xiàn))。b. Stateful Result,是通過調(diào)用方不斷輪詢執(zhí)行結(jié)果,當(dāng)服務(wù)提供者服務(wù)處理完畢后,修改Result的狀態(tài)。優(yōu)劣不在此贅述。
異步化特點:
1. 系統(tǒng)間可用性和處理能力松耦合。
AàB(A系統(tǒng)依賴于B系統(tǒng))
A的可用性最差情況是sum(A系統(tǒng)自身可用性,B系統(tǒng)可用性)。
A系統(tǒng)的處理能力是min(A系統(tǒng)處理能力,B系統(tǒng)處理能力)
作用:差別化系統(tǒng)設(shè)計,A系統(tǒng)可能是前端系統(tǒng)要求高處理能力,B系統(tǒng)是后段系統(tǒng),要求高一致性,此時兩個系統(tǒng)如果異步方式依賴,A可以設(shè)計的較為輕量化,在高并發(fā)下有很好的吞吐量。B系統(tǒng)可以設(shè)計的有足夠的容錯和備份機制,在效率上適當(dāng)放低要求。(我們時常在設(shè)計系統(tǒng)的時候談CAP原則,不同系統(tǒng)和流程不同階段對于這三個因素的要求都是不同的,因此通過異步的方式防止由于對于其他系統(tǒng)的依賴導(dǎo)致本系統(tǒng)的CAP無法有效的權(quán)衡)
2. 資源的有效利用。
異步模式天生就需要事件驅(qū)動模型支持,而事件驅(qū)動模型在高并發(fā)情況下對資源管理和使用十分有效。NIO設(shè)計就是典型的異步模式,基于對信道事件的監(jiān)聽和分發(fā),最大程度上復(fù)用信道,復(fù)用接收和發(fā)送緩存等相關(guān)資源,提高資源利用率,增強系統(tǒng)的服務(wù)能力。
3. 系統(tǒng)復(fù)雜度增加。
錯誤暴露及時性降低。當(dāng)B系統(tǒng)出現(xiàn)問題時,A系統(tǒng)知曉情況被動,整個流程問題暴露及時性降低。因此要求B系統(tǒng)和A系統(tǒng)作好更多地容錯,異常檢查工作。(例如流程超時處理機制等)
4. 整體業(yè)務(wù)流程處理時間增大。
由于B反饋給A的結(jié)果是異步化,因此A就有了上述兩種方式去獲取結(jié)果:Pull和Push,Push就是B主動回調(diào)A的服務(wù)(及時性較強,不過仍有部分時間消耗),Pull的間隔時間決定了A獲得結(jié)果可能存在的延時性。
異步化場景:
1. 模塊間依賴,系統(tǒng)間依賴。
這里描述的是粒度,異步化最終是對一個流程中的部分環(huán)節(jié)的弱化:一種就是弱化非關(guān)鍵路徑來保證關(guān)鍵路徑的可用性和效率。另一種就是弱化整體流程的及時性和一致性,提高部分環(huán)節(jié)的處理能力和可用性。但不論哪一類弱化都是基于一定的業(yè)務(wù)粒度,模塊應(yīng)該說是最小的業(yè)務(wù)粒度,在模塊內(nèi)部設(shè)計就要求緊耦合。
2. 資源決定一切
異步的使用場景往往是為了節(jié)省資源,但是節(jié)省帶來的成本就是復(fù)雜度,當(dāng)資源本身就不是問題(并發(fā)不高,資源足夠)的情況下,無需要選擇異步方式來增加復(fù)雜度,同時反而降低了可用性和穩(wěn)定性。
3. 整體觀與局部觀
AàB,A依賴于B,此時采取異步化的方式,A的處理能力得到提高,大量的請求被提交到B上,B由于請求堆積,導(dǎo)致性能下降甚至崩潰,那么其實對于整體處理流程來說并沒有隨著A處理能力曲線上升而上升,因此此類優(yōu)化沒有協(xié)調(diào)好局部和整體。
AàB AàC,A依賴于B,A依賴于C,異步改造A和B之間的關(guān)系,但是A還是受限于C,那么此類優(yōu)化未必有效果,同時增加了復(fù)雜度。(這里就要關(guān)注整體流程的關(guān)鍵路徑,關(guān)鍵路徑的優(yōu)化才是有效的優(yōu)化)
Web服務(wù)請求異步化
隨著Servlet3.0的日趨成熟和各個Web容器廠商的支持,Web服務(wù)請求異步化在很多領(lǐng)域開始慢慢被使用和熟悉。
Web服務(wù)請求異步化與NIO的概念是不同的(Web服務(wù)請求異步化可以基于BIO的底層交互也可以基于NIO的底層交互),Web服務(wù)請求異步化是業(yè)務(wù)層的異步設(shè)計,與普通的應(yīng)用系統(tǒng)異步化差別在于Web請求有固定的規(guī)范,請求流程和接口較為固定,同時請求底層的資源管理交由Web容器處理,因此在第一部分中所談到的異步的優(yōu)勢劣勢,適用場景同樣適合Web服務(wù)請求的異步化。
這里也順帶談一下為什么NIO在Web領(lǐng)域里面被沒有像后臺系統(tǒng)一樣被得到廣泛使用:
a. 信道復(fù)用的投入產(chǎn)出比例。Http請求是無狀態(tài)的請求應(yīng)答模式,信道復(fù)用概率不像內(nèi)部后臺系統(tǒng)那么高,再加之業(yè)務(wù)時間占整體流程時間絕對大的比例,那么投入產(chǎn)出不成比例。
b. Web請求受限于Servlet規(guī)范的生命周期管理,導(dǎo)致前段無論如何異步,在服務(wù)處理過程中都是同步阻塞模式,因此異步不徹底,無法體現(xiàn)NIO的優(yōu)勢。
三種Web請求模型的演進:
1. Thread Per Connection。在BIO的模型下,每次連接都會被分配一個線程,線程負責(zé)底層數(shù)據(jù)接收發(fā)送,業(yè)務(wù)處理。
2. Thread Per Request。在NIO的模型下,將不再為連接單獨分配線程,而為每一次請求事件發(fā)生創(chuàng)建線程,處理業(yè)務(wù),對于底層的數(shù)據(jù)發(fā)送和接收資源做到了共享,同時數(shù)據(jù)通道得到了共享。
3. Thread Per Service Event。在Web請求異步處理模型下,底層數(shù)據(jù)處理可以依賴于第二個處理模型,上層業(yè)務(wù)處理將業(yè)務(wù)狀態(tài)對象獨立于業(yè)務(wù)處理流程,通過事件驅(qū)動模式來分階段觸發(fā)業(yè)務(wù)各階段處理,為每一次事件處理創(chuàng)建線程和資源(通常是資源池),最終提高資源利用率。
介紹一下四種場景下的Web請求處理:
角色介紹:(在下面的場景中會涉及到一些角色,有些隸屬于容器,有些是外部服務(wù)和資源)
:并發(fā)用戶。T代表有T個并發(fā)用戶。
Conn Thread Pool :連接線程池,屬于Web容器的一部分,作為響應(yīng)和處理請求的線程資源獲取來源。
Conn Thread:線程連接,從線程連接池中獲取到的線程。
Service Provider:業(yè)務(wù)實現(xiàn)者。可以是本系統(tǒng)的業(yè)務(wù)實現(xiàn),也可以是其他系統(tǒng)的業(yè)務(wù)實現(xiàn),可以是異步方式的返回也可以是同步方式返回結(jié)果。(N)代表本身業(yè)務(wù)處理需要N個時間單位。
Worker Thread Pool:外部工作線程池,可以接收處理“耗時”的業(yè)務(wù)流程。
1. 非異步化Web請求處理

從這個場景可以看出,在非異步化Web請求的容器中,不論后端服務(wù)是否采取異步化,由于請求本身需要在一次阻塞式交互中返回,那么連接線程池中的線程在后端服務(wù)異步化的同時依然Hold沒有被釋放,當(dāng)前端并發(fā)量增加的時候,容器的吞吐量就會成為瓶頸(就算后端服務(wù)能力還有很大的剩余)。
Resource表示消耗的資源:在T個并發(fā)用戶下,需要消耗T個連接線程資源。
Response表示響應(yīng)處理時間:N個時間單位。
2. 異步化Web請求處理,后端服務(wù)提供者為阻塞模式。

這種模式下,增加了一個工作者線程池,在做后端服務(wù)處理的時候,工作線程池的線程取代了連接線程池的線程,在工作者線程獲得了掛起的異步上下文以后, 釋放了連接線程池的線程,當(dāng)業(yè)務(wù)執(zhí)行完畢以后,工作者線程通過異步上下文,提交返回結(jié)果,最后釋放自身資源。
Resource:少量ConnThread + T個WorkThread。
Response:N + workerThead消耗(創(chuàng)建,異步調(diào)用等)
可以看到,對于后端沒有支持異步化的情況下,僅僅前端容器異步化能夠起到效果的前提是:1. WorkThread很輕量化,消耗資源遠小于連接池線程資源。2.WorkerThread消耗占整體消耗的很小一部分,甚至可以忽略。此時通過用輕量級線程池替換容器連接線程池可以較好的提高效率和資源利用率。
3. 異步化Web請求處理,后端服務(wù)提供者為非阻塞模式。(Push & complete mode)

Push & complete mode指的是對于后端服務(wù)結(jié)果的反饋是Service Provider主動push給服務(wù)調(diào)用者,在Web請求異步化過程中,返回請求結(jié)果是直接通過調(diào)用異步上下文的complete事件來觸發(fā)commite的。(想對于resume的喚醒重入方式)
此場景服務(wù)提供者支持異步化處理業(yè)務(wù)請求,因此連接線程池的線程負責(zé)處理最輕量一些操作,然后將業(yè)務(wù)請求轉(zhuǎn)交給服務(wù)提供者,同時將異步上下文傳給服務(wù)提供者的處理線程,就此連接池連接資源釋放。當(dāng)服務(wù)完成后,服務(wù)端線程通過異步上下文獲取到輸出對象,將處理結(jié)果直接返回給客戶。
Resource:少量Conn Thread。
Response:N個時間單位。
可以看到容器請求異步化結(jié)合后端服務(wù)體系異步化能夠起到最好的效果,但有一點是要注意的,前端線程池在沒有異步化以前吞吐量取決于它的線程池大小和后端服務(wù)處理速度,而當(dāng)異步化后,吞吐量取決于它的線程池大小和異步化帶來的消耗,明顯并發(fā)服務(wù)能力得到了很大的提升(特別是后端服務(wù)耗時嚴(yán)重的時候),這樣就意味著會有更多的服務(wù)請求流向后端,當(dāng)后端處理能力無法支撐的時候,那么N那個時間單位就會上升,同時穩(wěn)定性也會產(chǎn)生問題,因此反而會起副作用,因此這種模式需要評估后端服務(wù)能力,保證異步化后服務(wù)質(zhì)量依舊。
4. 異步化Web請求處理,后端服務(wù)提供者為非阻塞模式。(Pull & Complete mode)

Pull & complete mode指的是對于后端服務(wù)結(jié)果的反饋是Service Provider被動的等待其他監(jiān)控線程定時pull結(jié)果對象并比對結(jié)果狀態(tài)確定是否完成,在Web請求異步化過程中,返回請求結(jié)果是直接通過調(diào)用異步上下文的complete事件來觸發(fā)commite的。(想對于resume的喚醒重入方式)
這個場景和前一個場景差別在于對于結(jié)果的獲取方式,同樣對于后臺服務(wù)來說,前端系統(tǒng)的業(yè)務(wù)處理不會侵入到它的業(yè)務(wù)代碼(Push方式會要求Service Provider回調(diào)或者直接提交結(jié)果到客戶端)。這種場景需要增加一個結(jié)果隊列,連接線程池中的線程責(zé)任就是調(diào)用后端服務(wù),然后將Future結(jié)果和異步上下文作為服務(wù)結(jié)果放入隊列。由一個小的工作者線程池定時檢查任務(wù)執(zhí)行情況,當(dāng)執(zhí)行通過時,直接取出結(jié)果集,將結(jié)果通過異步上下文輸出到客戶端。
Resource:少量的Conn Thread + 部分worker Thread
Response:N + 異步化消耗的時間(結(jié)果輪詢消耗的時間)
就系統(tǒng)本身來說,穩(wěn)定性和可用性有所“折扣”,增加了對于隊列和工作者線程的依賴。
5. 異步化Web請求處理,后端服務(wù)提供者為非阻塞模式。(Push & resume mode)

Push & resume mode指的是對于后端服務(wù)結(jié)果的反饋是Service Provider主動push給服務(wù)調(diào)用者,在Web請求異步化過程中,在業(yè)務(wù)處理結(jié)束后,重入當(dāng)前同樣的Servlet中(帶上結(jié)果),由Servlet在新的請求中返回結(jié)果給客戶端。(在Servlet3.0中是允許dispatch到不同的Servlet中,這樣帶來的靈活性就比較高了,在jetty的continuation中只允許重入當(dāng)前請求的Servlet)
重入機制會給容器帶來一定的壓力,一次請求在容器這邊變成了兩次或者多次請求,同時對于Servlet中的業(yè)務(wù)代碼需要去關(guān)注是否是原始請求還是被模擬的重入的請求,區(qū)別化對待。
Resource:部分的Conn Thread。
Response:N + redispatch time。
連接線程消耗要比普通的complete來的多,同時消耗時間也比complete模式來的大。
Web請求異步化規(guī)范及實現(xiàn)
當(dāng)前實現(xiàn)Web請求異步化的容器有Jetty6,Jetty7,Tomcat7.其中Jetty6支持他特有的Continuation機制,jetty7支持Continuation和Servlet3.0,Tomcat7支持Servlet3.0.后面就從Jetty的角度去介紹Continuation機制,再比較Continuation與Servlet3.0的差異。
Continuation
Jetty可以使用BIO或者NIO的底層來支持Continuation,不過就效果來說肯定是NIO的效果好,這里給出兩個圖(Jetty的NIO的類結(jié)構(gòu)圖和Continuation的交互圖),從中可以看到這兩塊設(shè)計的實現(xiàn)。

Jetty NIO 類圖
Jetty作為外部容器或者嵌入式容器入口都是Server,Server中包含了Connector(這塊實現(xiàn)的不同就決定了是用BIO還是NIO的模式,這里描述的是NIO模式,因此Connector是SelectorChannelConnector),ThreadPool成為整個系統(tǒng)中的線程資源池,用來完成事件驅(qū)動模型的各種需求。為了提高性能,NIO模式下的Connector包含多個Selector(即SelectSet)和Acceptor,通過Acceptor循環(huán)檢測來觸發(fā)多個Selector檢查IO事件,當(dāng)有請求產(chǎn)生的時候創(chuàng)建SelectChannelEndPoint來分配必要的資源處理請求(與前面描述的Thread Pre Request是一致的,確切的說是One Resource Pre Request)。
圖片過大了,請點擊看(http://www.flickr.com/photos/33194437@N03/4746678724/sizes/l/)
Continuation 交互圖
1. 用戶發(fā)起請求。
2. NIO的Selector接收到了IO事件創(chuàng)建了Endpoint分配了相應(yīng)的資源。
3. 同時將Endpoint作為一個任務(wù)封裝后插入到線程池隊列中,等待工作線程執(zhí)行請求處理。(IO事件處理到此結(jié)束)
4. 工作線程池中線程執(zhí)行請求處理。
5. 先讀取請求數(shù)據(jù)。
同步模式:
6.1 -6.4調(diào)用內(nèi)部的handler串行化處理請求,最后返回處理結(jié)果。
異步模式:
7.1 執(zhí)行Handler的業(yè)務(wù)邏輯。
7.2 掛起請求,進入異步模式。
7.3 創(chuàng)建異步事件置入到Request中。(一來用于容器后續(xù)判斷當(dāng)前請求是否處于同步模式,是否需要提交response,另一方面用于異步事件的超時檢測)
7.4 在Servlet的原生命周期的方法中(service,doget,dopost …)創(chuàng)建新的線程去執(zhí)行業(yè)務(wù)操作,并且將Continuation傳遞給線程用于后續(xù)complete或者resume來提交業(yè)務(wù)處理結(jié)果或則重新分發(fā)進入Servlet。
7.5 結(jié)束常規(guī)的Servlet生命周期。
7.6 容器判斷,如果是異步模式,則將異步事件放入到timeoutTasks這個鏈狀超時事件隊列中,如果沒有啟動異步模式,則提交結(jié)果,回收請求處理資源。
7.7.1 工作線程執(zhí)行業(yè)務(wù)處理。
7.7.2 執(zhí)行完畢業(yè)務(wù)處理以后調(diào)用Continuation的complete或者resume方法來提交處理結(jié)果。
7.7.3 complete或者resume方法調(diào)用將產(chǎn)生事件被放入到了線程池隊列中。
7.7.4 執(zhí)行complete或者resume事件,調(diào)用事件宿主Endpoint的分發(fā)請求的處理。(可以理解為重新模擬執(zhí)行了一次服務(wù)端的dispatch,也就是重新執(zhí)行一次handler鏈,不過中間結(jié)果數(shù)據(jù)已經(jīng)完全不同)
7.7.5 如果沒有complete則重新回到7.7.1
7.7.6 如果complete 則返回結(jié)果,回收資源。
7.8.1 Endpoint會循環(huán)檢查異步事件是否已經(jīng)超時(查看timeoutTasks)。
7.8.2 如果出現(xiàn)超時,則封裝超時事件放入線程池隊列等待執(zhí)行。
7.8.3 線程池執(zhí)行超時處理,返回結(jié)果,回收資源。
Servlet3.0 與 Continuation的差異
可以說Jetty團隊在Servlet3.0沒有成為正式規(guī)范之前就參考了它的設(shè)計理念,因此本質(zhì)上來說兩者沒有太大的區(qū)別,唯一的幾個區(qū)別點在于:
1. Continuation和AsynContext分別是兩個體系的異步上下文載體。
2. Continuation resume機制沒有Servlet3靈活,Servlet3可以支持dispatch到內(nèi)部任意的Service上。
異步化在客戶端
前面一致介紹異步化在服務(wù)端的應(yīng)用,其實在客戶端的應(yīng)用可以提高客戶端的連接能力及容錯能力(加長Timeout時間也不會導(dǎo)致連接耗盡),Jetty已經(jīng)支持客戶端異步化,使用比較簡單。
后話
這些是剛開始,接下來對于應(yīng)用實際的改造(TOP現(xiàn)有管道化流程的異步化嘗試)會找到異步化的優(yōu)勢和軟肋。如何用好異步化對于高并發(fā)的多模塊或者多依賴系統(tǒng)來說是很關(guān)鍵的,是一把雙刃劍,需要有足夠能力的人去把控,這個人需要的不僅是教條,更多的是經(jīng)驗。