基於管道化和事件驅動模型的Web請求處理(一)

Author:放翁(文初)

Date: 2010/11/23

Email:[email protected]

mblog: http://t.sina.com.cn/fangweng

blog: http://blog.csdn.net/cenwenchu79/

 

         這篇文章將會從問題,技術背景,設計實現,代碼範例這些角度去談基於管道化和事件驅動模型的Web請求處理。其中的一些描述和例子也許不是很恰當,也希望得到更多的反饋。

問題的誕生與思考:

一.   依賴之苦

做過不少業務系統,最痛苦,最無奈的就是性能和穩定性依賴與外部系統處理能力和可用時間。而TOP是典型的Proxy模式,它自身的性能在傳統的Web容器處理模式下依賴與後端服務處理能力。

           TOPWeb容器線程連接有限的情況下,最差的處理能力就是min(A,B,C),也就是一個時刻的處理都是在處理最慢的系統的請求。因此產生這麼幾個問題:a.通過壓力測試評估TOP自身的處理能力,並且來預估所需要的服務器容量將變得很不可靠。 b.可能由於某一個後端服務的不正常導致正常服務也無法通過TOP被外部訪問到,使得局部不可用演變成爲整體不可用。

         延展考慮,由於容器端線程不支持根據業務情況分配,因此無法實現靜態或者動態的線程資源按業務重要性或者服務健康狀況做調整,這樣對於一個集成了衆多重要程度不同,能力參差不齊的服務平臺來說很難最有效的將合理的資源傾向於重要且健康的服務。

 

二.   輪詢之苦

首先,耗時的業務處理,例如對於歷史訂單的數據查詢,後臺操作會消耗較多時間,而傳統請求是阻塞式的,因此超時設置成了一個難題,設置太大,傳統容器的連接資源有限,設置短了無法滿足業務需求,爲今之計只能夠將一個請求拆成兩個請求,一個是發起處理的通知請求,後面是輪詢獲取結果的請求。

其次,在業務系統設計中,或多或少的會有基於狀態變更事件來觸發事件處理的場景,淘寶的業務體系更是如此。買家和賣家分別是淘寶的兩個角色,相互之間的操作貫穿於整個交易主流程(下單,付款,發貨,確認收貨),兩個角色之間是通過交易這個虛擬對象的狀態遷移來實現交互的,而狀態遷移的動作是由任何一方無預見性的實施的,因此做工具的應用需要能夠接受到狀態變化事件通知,當前只能通過應用輪詢獲取數據來實現。

輪詢一方面使得開發者軟件設計複雜度高,自身系統消耗大(時間間隔設置,容錯機制等等),另一方面使得TOP服務器壓力增大,無效請求浪費系統資源。

 

三.   容器資源之苦

有人會說輪詢這件事情幹嗎不直接將數據推送過去,告知這些ISV,反正他們也都是B/S結構,服務器提供回調地址就可以了。的卻這是最常見的Notify的模式,淘寶內部也有一個Notify的中間件。但是在現在的網絡狀況下,主動推送數據到ISV的服務器上基本不靠譜,對方響應速度的快慢直接影響到我們投遞這些數據需要多少服務器,投遞的策略如何?(如何處理失敗的投遞消息,重試機制如何),從這裏可見容器連接池之苦。另一方面從第一個苦描述中可以看到,其實如果容器資源足夠多,那麼就可以無限制放大入口,也就不會受之於後端的依賴系統處理能力,但今天大家看看自己傳統的Web應用服務器(jboss,tomcatapache,nginx)連接池配置的數字就知道這是不靠譜的。

 

技術背景概述:

         管道化子任務切分:

        

           我外婆以前是做白鐵加工的,做一個鍋子基本需要這些步驟,每個步驟都需要一些工具,傳統最簡單的做法就是一個人做到底,然後工具都擺在身邊。(這也就是我們現在傳統容器的模式,從請求進入到整個業務處理結束),要提高效率的做法如下:

1.       增加人手,依然採用一人處理到底的模式。在人和工具無限量的情況下是最簡單和行之有效的方式。

2.       切割流程,最大化資源利用率,每個子任務所需的資源不同,因此在完成子任務後就將資源共享給其他人而不是佔用所有資源到整個流程結束。這種優化帶來的最明顯的效果:

a)         輕量級子任務完成所需要消耗的資源最小化。(例如第一階段處理消耗時間很短,那麼錘子和鉗子的需求量將會最小)

b)           重量級子任務能夠得到更多的資源和線程來處理。如果第一階段和第二階段本身資源消耗是相互影響的,比如第一階段資源分配消耗內存,第二階段資源分配也消耗內存,那麼第一階段的資源佔用少了以後,自然可以給第二階段資源分配提供了便利,其次如果總資源有限,第一階段也在等待資源的ready,那麼此時的線程將會等待在第一階段的資源分配上,此時線程空消耗,但如果降低了第一階段的消耗,線程滿負荷運作,則線程完成第一階段所有任務後可以支援第二階段的工作。

3.       當子任務可以“降級”的情況下,分解任務,根據資源狀況來並行處理,並且適當的“丟棄”非關鍵子任務可以提高效率,增加穩定性。

 

總體上來說,可以把原本一個任務拆成管道形式的子任務(管道化也就是每個子任務的上一個任務的輸出是當前子任務的輸入),然後根據子任務的情況來選擇是否交由不同的線程並行化執行。(這個判斷很簡單,子任務是否是有限資源且較輕量化的,子任務的資源佔用多少是否會影響到其他任務的資源分配情況,如果這兩點都不成立,則保持簡單處理模式即可,最多可以將某一些子任務“降級”)

管道化的作用:1.將任務各個階段梳理清楚。(降低子任務之間的耦合度,爲並行或異步處理提供基礎)。2.最大化資源利用率,便於流程整體優化。

 

 

事件驅動模式:

         事件驅動模式其實在設計中被大規模使用,思路概括起來就是:對象脫離線程,狀態脫離事務。回到第一個做鍋子三個流程的實例說明,也許在第二階段,某人拿起了一個已經完成第一階段的半成品在等待第二階段的資源Ready,這時候如果他放下這個半成品,先去做已經可以做工作的第一或者第三階段的半成品,然後等到另一個人做完後釋放第二階段資源時通知他時,他在去安排做第二階段的工作,那麼效率會更高。

         那麼可以發現,管道化是從釋放資源被佔用的角度去提升整體工作效率,而事件驅動模式是從分離工作實施者和工作資源的角度去提升工作實施者的工作效率。一個是工作者是有限而寶貴資源,一個是子任務在完成過程中所需資源是寶貴資源。

         由此看來,事件驅動模式與管道模式不同,應該是不需要有評判標準都可以實施的一種優化策略。其實不然,事件驅動模式也有自己的弱點:1.設計複雜。(過於鬆耦合的結構,使得原本事務中有順序的操作需要更多的檢查,容錯和調度)2.性能可能會受到影響。(線程上下文的切換,中間結果的拷貝)3.延時問題產生。對於事件的產生一種是主動推送,一種是輪詢,輪詢就牽涉到時間片大小的問題,在性能和及時性權衡的情況下最後得出合理的設置,但是對於整個事務來說一定是消耗了。

 

 

         上面描述的兩個概念在後面的Web請求異步化處理及訂閱模式中都會用到,同時在支持Servlet3Comet Streaming(Comet push)的容器整體架構上都會被用到,優劣上面做了簡單的描述,後面從業務架構到系統架構都會有最實在的設計說明。

 

 

業務架構設計:

         基於上述問題,通過兩步走來解決。首先採用支持打破傳統http request生命週期管理的Web容器(很多人說可以自己寫,其實Web容器寫起來並不是最麻煩的,如何做好兼容和照顧好每一個細節纔是漫長髮展的道路)。其次在容器新的線程生命週期管理基礎上封裝業務框架,爲開發者屏蔽底層異步化和事件驅動模式帶來的複雜流程管理內容。

待續...

發佈了156 篇原創文章 · 獲贊 9 · 訪問量 124萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章