Author:放翁(文初)
Date: 2010/11/23
Email:fangweng@taobao.com
mblog: http://t.sina.com.cn/fangweng
blog: http://blog.csdn.net/cenwenchu79/
問題的誕生與思考:
一. 依賴之苦
做過不少業(yè)務系統(tǒng),最痛苦,最無奈的就是性能和穩(wěn)定性依賴與外部系統(tǒng)處理能力和可用時間。而TOP是典型的Proxy模式,它自身的性能在傳統(tǒng)的Web容器處理模式下依賴與后端服務處理能力。

TOP在Web容器線程連接有限的情況下,最差的處理能力就是min(A,B,C),也就是一個時刻的處理都是在處理最慢的系統(tǒng)的請求。因此產(chǎn)生這么幾個問題:a.通過壓力測試評估TOP自身的處理能力,并且來預估所需要的服務器容量將變得很不可靠。 b.可能由于某一個后端服務的不正常導致正常服務也無法通過TOP被外部訪問到,使得局部不可用演變成為整體不可用。
延展考慮,由于容器端線程不支持根據(jù)業(yè)務情況分配,因此無法實現(xiàn)靜態(tài)或者動態(tài)的線程資源按業(yè)務重要性或者服務健康狀況做調整,這樣對于一個集成了眾多重要程度不同,能力參差不齊的服務平臺來說很難最有效的將合理的資源傾向于重要且健康的服務。
二. 輪詢之苦
首先,耗時的業(yè)務處理,例如對于歷史訂單的數(shù)據(jù)查詢,后臺操作會消耗較多時間,而傳統(tǒng)請求是阻塞式的,因此超時設置成了一個難題,設置太大,傳統(tǒng)容器的連接資源有限,設置短了無法滿足業(yè)務需求,為今之計只能夠將一個請求拆成兩個請求,一個是發(fā)起處理的通知請求,后面是輪詢獲取結果的請求。
其次,在業(yè)務系統(tǒng)設計中,或多或少的會有基于狀態(tài)變更事件來觸發(fā)事件處理的場景,淘寶的業(yè)務體系更是如此。買家和賣家分別是淘寶的兩個角色,相互之間的操作貫穿于整個交易主流程(下單,付款,發(fā)貨,確認收貨),兩個角色之間是通過交易這個虛擬對象的狀態(tài)遷移來實現(xiàn)交互的,而狀態(tài)遷移的動作是由任何一方無預見性的實施的,因此做工具的應用需要能夠接受到狀態(tài)變化事件通知,當前只能通過應用輪詢獲取數(shù)據(jù)來實現(xiàn)。
輪詢一方面使得開發(fā)者軟件設計復雜度高,自身系統(tǒng)消耗大(時間間隔設置,容錯機制等等),另一方面使得TOP服務器壓力增大,無效請求浪費系統(tǒng)資源。
三. 容器資源之苦
有人會說輪詢這件事情干嗎不直接將數(shù)據(jù)推送過去,告知這些ISV,反正他們也都是B/S結構,服務器提供回調地址就可以了。的卻這是最常見的Notify的模式,淘寶內部也有一個Notify的中間件。但是在現(xiàn)在的網(wǎng)絡狀況下,主動推送數(shù)據(jù)到ISV的服務器上基本不靠譜,對方響應速度的快慢直接影響到我們投遞這些數(shù)據(jù)需要多少服務器,投遞的策略如何?(如何處理失敗的投遞消息,重試機制如何),從這里可見容器連接池之苦。另一方面從第一個苦描述中可以看到,其實如果容器資源足夠多,那么就可以無限制放大入口,也就不會受之于后端的依賴系統(tǒng)處理能力,但今天大家看看自己傳統(tǒng)的Web應用服務器(jboss,tomcat非apache,nginx)連接池配置的數(shù)字就知道這是不靠譜的。
技術背景概述:
管道化子任務切分:
我外婆以前是做白鐵加工的,做一個鍋子基本需要這些步驟,每個步驟都需要一些工具,傳統(tǒng)最簡單的做法就是一個人做到底,然后工具都擺在身邊。(這也就是我們現(xiàn)在傳統(tǒng)容器的模式,從請求進入到整個業(yè)務處理結束),要提高效率的做法如下:
1. 增加人手,依然采用一人處理到底的模式。在人和工具無限量的情況下是最簡單和行之有效的方式。
2. 切割流程,最大化資源利用率,每個子任務所需的資源不同,因此在完成子任務后就將資源共享給其他人而不是占用所有資源到整個流程結束。這種優(yōu)化帶來的最明顯的效果:
a) 輕量級子任務完成所需要消耗的資源最小化。(例如第一階段處理消耗時間很短,那么錘子和鉗子的需求量將會最小)
b) 重量級子任務能夠得到更多的資源和線程來處理。如果第一階段和第二階段本身資源消耗是相互影響的,比如第一階段資源分配消耗內存,第二階段資源分配也消耗內存,那么第一階段的資源占用少了以后,自然可以給第二階段資源分配提供了便利,其次如果總資源有限,第一階段也在等待資源的ready,那么此時的線程將會等待在第一階段的資源分配上,此時線程空消耗,但如果降低了第一階段的消耗,線程滿負荷運作,則線程完成第一階段所有任務后可以支援第二階段的工作。
3. 當子任務可以“降級”的情況下,分解任務,根據(jù)資源狀況來并行處理,并且適當?shù)?#8220;丟棄”非關鍵子任務可以提高效率,增加穩(wěn)定性。
總體上來說,可以把原本一個任務拆成管道形式的子任務(管道化也就是每個子任務的上一個任務的輸出是當前子任務的輸入),然后根據(jù)子任務的情況來選擇是否交由不同的線程并行化執(zhí)行。(這個判斷很簡單,子任務是否是有限資源且較輕量化的,子任務的資源占用多少是否會影響到其他任務的資源分配情況,如果這兩點都不成立,則保持簡單處理模式即可,最多可以將某一些子任務“降級”)
管道化的作用:1.將任務各個階段梳理清楚。(降低子任務之間的耦合度,為并行或異步處理提供基礎)。2.最大化資源利用率,便于流程整體優(yōu)化。
事件驅動模式:
事件驅動模式其實在設計中被大規(guī)模使用,思路概括起來就是:對象脫離線程,狀態(tài)脫離事務。回到第一個做鍋子三個流程的實例說明,也許在第二階段,某人拿起了一個已經(jīng)完成第一階段的半成品在等待第二階段的資源Ready,這時候如果他放下這個半成品,先去做已經(jīng)可以做工作的第一或者第三階段的半成品,然后等到另一個人做完后釋放第二階段資源時通知他時,他在去安排做第二階段的工作,那么效率會更高。
那么可以發(fā)現(xiàn),管道化是從釋放資源被占用的角度去提升整體工作效率,而事件驅動模式是從分離工作實施者和工作資源的角度去提升工作實施者的工作效率。一個是工作者是有限而寶貴資源,一個是子任務在完成過程中所需資源是寶貴資源。
由此看來,事件驅動模式與管道模式不同,應該是不需要有評判標準都可以實施的一種優(yōu)化策略。其實不然,事件驅動模式也有自己的弱點:1.設計復雜。(過于松耦合的結構,使得原本事務中有順序的操作需要更多的檢查,容錯和調度)2.性能可能會受到影響。(線程上下文的切換,中間結果的拷貝)3.延時問題產(chǎn)生。對于事件的產(chǎn)生一種是主動推送,一種是輪詢,輪詢就牽涉到時間片大小的問題,在性能和及時性權衡的情況下最后得出合理的設置,但是對于整個事務來說一定是消耗了。
上面描述的兩個概念在后面的Web請求異步化處理及訂閱模式中都會用到,同時在支持Servlet3及Comet Streaming(Comet push)的容器整體架構上都會被用到,優(yōu)劣上面做了簡單的描述,后面從業(yè)務架構到系統(tǒng)架構都會有最實在的設計說明。
業(yè)務架構設計:
基于上述問題,通過兩步走來解決。首先采用支持打破傳統(tǒng)http request生命周期管理的Web容器(很多人說可以自己寫,其實Web容器寫起來并不是最麻煩的,如何做好兼容和照顧好每一個細節(jié)才是漫長發(fā)展的道路)。其次在容器新的線程生命周期管理基礎上封裝業(yè)務框架,為開發(fā)者屏蔽底層異步化和事件驅動模式帶來的復雜流程管理內容。

待續(xù)...