Author:放翁(文初)
Date: 2010/9/9
Email:fangweng@taobao.com
圍脖: http://t.sina.com.cn/fangweng
有段時間沒有更新技術(shù)blog了,現(xiàn)在有空每天都寫寫圍脖,記錄生活和工作的點滴,但是有時候發(fā)現(xiàn)有些技術(shù)的想法和工作總結(jié)沒有像過去那么完整的寫很大一篇,但是也有零零散散的不少點滴,因此想著隨意的寫這么一個連續(xù)的片段分享。
為什么叫做代碼背后的點滴呢,其實在現(xiàn)在互聯(lián)網(wǎng)應(yīng)用來說,其實用什么語言,用什么平臺有些場景有影響,但已經(jīng)不是絕對重要的因素的,其實代碼被后的設(shè)計思想才是最重要的。而用最熟悉的方式去表現(xiàn)最自然的想法,那才能做到游刃有余,就好比我向華黎同學申請這次內(nèi)部獎勵的獎品希望是手寫筆,因為不論什么畫圖工具用起來都會妨礙我的順暢的表達,最終我把注意力集中到了畫本身上,而丟失了應(yīng)有的靈感(在沒有拿到畫筆前,最近先少畫點圖)。寫代碼也是一樣,不要被流行,高性能,有潛力這些詞搞丟了自己的目標,工具就是工具,能廣泛的去學,但不必要廣泛的用,把干活要用的那門搞熟練了再說。廢話不多說,言歸正傳。
先說一下,以下的任何觀點都必須“對癥下藥”,沒啥萬能靈丹妙藥,怎么用,什么時候用是關(guān)鍵。
聚與散:
場景一,在分享時談到Jetty7整個體系架構(gòu)就一個線程資源池,為什么?整個jetty體系都是事件驅(qū)動模式(包括系統(tǒng)事件:NIO事件,包括業(yè)務(wù)事件:request suspend),我們系統(tǒng)很多時候有很多資源池(線程,DB,服務(wù))等等,有些是直接的,有些是間接的(依賴于第三方包引入的)。我們要求每個開發(fā)者都要給資源池設(shè)置上限,給隊列限定長度,防止崩潰,但是當這些資源池散落在各個地方的時候,那么每一個資源達到邊界以前,總和可能已經(jīng)超過了系統(tǒng)負載能力,那么一樣會奔潰。因此資源的統(tǒng)一規(guī)劃看來有必要,那可以考慮將物理隔離變成邏輯隔離(防止業(yè)務(wù)干擾,同時可以根據(jù)權(quán)重模型來動態(tài)分配和預(yù)留資源),另一方面整個小團隊到大團隊,對于連接池的使用抽象出來以后,那么多個依賴都可以在抽象的基礎(chǔ)上公用一個物理資源池,如果引入第三方緩存,都可以將資源策略應(yīng)用到多機上。回過來,這里就要說聚和散的關(guān)系,聚能夠在一定程度上有全局觀,同時可以根據(jù)策略模型來調(diào)配資源,散最大的好處就是業(yè)務(wù)隔離,互相不會影響。最近在寫一個線程資源分配的小東西,目標就是:資源可根據(jù)權(quán)重模型分配。(權(quán)重模型兩條:設(shè)定某一類key獲取資源可以保留一定資源獨享,設(shè)定某一類key獲取資源最大上限可控)其實這就是一個邏輯隔離資源的最基本的雛形。
場景二,有同學談起他們的系統(tǒng)屬于消耗CPU類型的應(yīng)用,特別是對數(shù)據(jù)庫獲取到的數(shù)據(jù)作處理和渲染的時候,問有啥好的建議。我的建議是數(shù)據(jù)獲取端沒啥可優(yōu)化的話,考慮數(shù)據(jù)存入端增加數(shù)據(jù)預(yù)處理功能,將數(shù)據(jù)處理的壓力分攤到更多也數(shù)據(jù)存入方。當然這個代價是數(shù)據(jù)的可復(fù)用性降低,業(yè)務(wù)邏輯侵入到其他系統(tǒng),數(shù)據(jù)存入方性能會有所下降(如果這三點不足以影響業(yè)務(wù)流程和數(shù)據(jù)存入方,那么可做)。其實這是聚和散的另一種表現(xiàn),很多時候需要考慮的是全局優(yōu)化,而不是局部優(yōu)化。
場景三,我今天看了淘寶Tair的作者若海介紹關(guān)于tair的一些設(shè)計思路的文章,這和早先我做memcached的客戶端思想是有共同點的。首先就是數(shù)據(jù)的獲取從服務(wù)端中轉(zhuǎn)路由變成了客戶端獲得配置本地hash選擇目標服務(wù)器,這就將服務(wù)路由的功能客戶端化了,避免了單點的瓶頸,也提高了訪問性能。同時metadata的數(shù)據(jù)保存在configserver中,以弱依賴的方式主動推送,避免客戶端受到影響。但他的設(shè)計里面數(shù)據(jù)同步是服務(wù)端做的,而我當年是客戶端做的(隊列中異步備份),當時一方面是沒有辦法去改服務(wù)端的memcached,另一方面服務(wù)端能有多少,客戶端有多少,這些壓力的分攤,天然的緩解服務(wù)端的壓力,將壓力分散在了客戶端上。(代價就是客戶端工作多一點,即時性依賴于各個客戶端的能力)這也是一種散。
場景四,TOP大數(shù)據(jù)請求的處理設(shè)計,TOP的目標是什么?大壩。主要職能:限流,授權(quán),路由。那么路由這件事情必須在TOP走么,數(shù)據(jù)交互和平臺校驗是否一定要在一個流程中完成,其實不然,安全校驗通過以后可以頒發(fā)會話標識和目標服務(wù)地址,直接由客戶端和服務(wù)端交互。(這其實就和很多分布式文件系統(tǒng)或者分布式緩存系統(tǒng)一樣的考慮,校驗通過后就直接建立數(shù)據(jù)通道進行數(shù)據(jù)交互,避免性能受到影響),這了的聚和散就涉及到業(yè)務(wù)的耦合性分析決定流程的聚散,改善性能和穩(wěn)定性。
場景五,上面談到過最近自己實現(xiàn)資源分配邏輯隔離的這樣的資源池,現(xiàn)在就是做線程池,起初不想用jdk自帶的線程池,原因是自帶線程池啟動線程后,線程只要在coresize內(nèi)就不會被銷毀,也沒有調(diào)度來從池里面獲取執(zhí)行任務(wù),完成任務(wù)放回到資源池,這些線程就輪訓(xùn)的去獲取隊列的數(shù)據(jù)。好處在于沒有一個manager的管理性能和復(fù)雜度會提升,缺點就是資源分配就無法實現(xiàn)。其實這就是典型的一種資源分配有一個實例主導(dǎo)還是由多個消費者自己主導(dǎo)的設(shè)計。
強弱依賴
場景:Jetty的Continuation設(shè)計中,從調(diào)用suspend方法來懸掛起請求,避免退出容器service方法req和res被回收,到業(yè)務(wù)主動調(diào)用complete結(jié)束請求流程,回寫數(shù)據(jù)到客戶端,都采用產(chǎn)生事件,異步的由核心線程池去執(zhí)行。這樣業(yè)務(wù)線程池的線程生命周期就不會受到系統(tǒng)線程輸出快慢或者容器本身壓力的影響。這種弱依賴能夠極大的解耦兩個系統(tǒng)的服務(wù)能力和穩(wěn)定性。
關(guān)鍵先生
場景一,有同學一個壓力測試報告中做了這么一個測試,整個事務(wù)處理流程第一步驟消耗CPU,第二步驟消耗IO。整體事務(wù)時間=CPU 10 ms + IO 90 ms。他考慮是否能夠通過縮短IO消耗時間或者CPU消耗時間來提升RT時間,就能夠提升TPS。他的結(jié)果是IO提升一倍TPS沒有任何變化,CPU消耗時間提升一倍,TPS翻番。這能說明啥,說明瓶頸在CPU上,提升一倍,那么處理能力增加一倍(就好比資源池內(nèi)的資源生命周期縮短,被利用的次數(shù)更多),大部分請求都在CPU上排隊等待處理,因此系統(tǒng)的TPS取決于瓶頸資源的處理能力而不是簡單的縮短RT就可以改變的。(貼切的比喻就是漏斗,不論你大口朝上或者朝下,小口決定了水流量,小口就是那個關(guān)鍵先生)
場景二,異步化壓力測試的結(jié)果中有如下一組結(jié)果:
容器 |
請求服務(wù) |
請求處理模式 |
TPS |
Nginx + Jetty |
Time.get |
同步 |
1534 |
Nginx + Jetty |
Time.get |
異步 |
1068 |
Nginx + Jetty |
User.get |
同步 |
1060 |
Nginx + Jetty |
User.get |
異步 |
1027 |
Time.get是不向后中轉(zhuǎn)服務(wù)的模擬請求,也就是服務(wù)端很快就響應(yīng)給客戶端了,而user.get是真實模擬服務(wù)向后請求,有一定消耗。但看這個結(jié)果,同樣是同步和異步對比,前者性能相差很大,后者相差不大。原因?異步一定有消耗,但這個消耗占整體事務(wù)處理時間的比例是多少,這就會放大影響。而TOP大部分服務(wù)都要比user.get更加耗時,因此后面才是線上的真實狀況。(因此場景模擬是壓力測試最重要的條件),這里的關(guān)鍵先生還是要看整個事務(wù)處理的消耗點。衍生一下上次電話面試的一個北京的朋友,談到了NIO,問了他是否做過BIO和NIO的測試比較,什么時候用NIO最好,他還真做過四類測試,有結(jié)果,但還是沒搞明白為什么:1.傳輸數(shù)據(jù)量大,后端服務(wù)處理慢。2.傳輸數(shù)據(jù)量大,后端服務(wù)處理快。3.傳輸數(shù)據(jù)量小,后端服務(wù)處理慢。4.傳輸數(shù)據(jù)量小,后端處理速度快。結(jié)果是NIO在4處于劣勢。其實BIO釋放的夠快,那么NIO的異步在整體事務(wù)處理消耗就形成了劣勢。
生命周期
場景:在異步分享過程中,談到了事件驅(qū)動的架構(gòu)設(shè)計。原來的流程可以分成很多個步驟,每一個步驟由于處理的需要都會申請必要的資源,不同步驟有些是共享資源,有些是不共享的,那么從資源的生命周期角度來說,一個流程中所有資源的生命周期都是一樣,就是整個業(yè)務(wù)事務(wù)的執(zhí)行周期,對于在一個業(yè)務(wù)流程中各個步驟很少共享資源的狀況,或者有等待外部返回內(nèi)容的情況時,切割開流程步驟,降低資源生命周期,提升資源利用率,是事件驅(qū)動的最大優(yōu)勢。NIO如此,Servlet3規(guī)范如此。簡單來說就是:按需分配。另一方面我們也能看到,處理都是無狀態(tài)的,只有輸入和輸出,無狀態(tài)的處理更容易復(fù)用。看起來這種設(shè)計很好,但其實背后還是有不少問題:1.流程復(fù)雜化,原來通過程序保證流程的順序和依賴,現(xiàn)在切割以后,如何驅(qū)動,順序如何保證,如何容錯,都給系統(tǒng)增加很大的維護成本。2.異步化后消耗時間必然增大,不論是通過pull主動獲取狀態(tài)變更還是通過push被通知到,都在性能和時間消耗上會有一些付出。因此還是需要依環(huán)境而定。
瓶頸移動
場景一,還是一個同學的優(yōu)化的案例,當時談到有個叫做最佳線程數(shù),但是覺得在優(yōu)化過程中最佳線程數(shù)是在變化的,其實很多時候當你覺得任何的結(jié)論是不穩(wěn)定的,那么你需要進一步深入挖掘下去。其實之所以最佳線程數(shù)這個值不穩(wěn)定,關(guān)鍵在于我們?nèi)绾稳?yōu)化,找到問題所在。和前面舉例一樣,系統(tǒng)可以看成各種類型資源池的一個整體,然后由很多個步驟不斷申請資源來完成各個步驟,最后返回結(jié)果。那么說白了,哪個資源池是流程中處理能力最弱的,他就是問題所在,就是瓶頸,但是通過優(yōu)化后這個瓶頸可能有所改善,與此同時另一個資源成為漏斗的小口,這時候,瓶頸移動了,優(yōu)化的預(yù)期結(jié)果和提升的瓶頸點性能就不吻合了。所以,把帶寬,CPU,IO,外部服務(wù),DB,Web線程池等等都看成資源池,找到瓶頸所在去優(yōu)化,同時在優(yōu)化過程中不斷修正優(yōu)化目標,達到最后全局優(yōu)化的效果。
場景二,異步化分享中談到一點,異步化要有全局觀。例如A依賴與B系統(tǒng),組成了一個完整的服務(wù)體系,通過異步化方式將A的資源生命周期縮短到只有A處理的時間,B的資源生命周期就是B的處理時間,此時如果A是原有整個系統(tǒng)的瓶頸,那么就會產(chǎn)生雪中送炭的效果,系統(tǒng)整體性能會有所提高(大部分情況下,原因看后面介紹)。如果B是瓶頸,那么就是雪上加霜,更多的水流被放進來,如果B自身沒有流量控制,那么系統(tǒng)就會處于不健康的狀況,甚至奔潰,系統(tǒng)的整體RT時間可能會增加(A節(jié)省的時間小于B增加的時間)。因此這就是系統(tǒng)間瓶頸移動產(chǎn)生的影響,同樣也和一個系統(tǒng)一樣,考慮優(yōu)化一定是整體性的考慮,否則問題不會朝著預(yù)期的方向解決。
晚了晚了,休息,希望這些對大家有幫助。