代碼背後的點滴

Author:放翁(文初)

Date: 2010/9/9

Email:[email protected]

圍脖: http://t.sina.com.cn/fangweng

 

         有段時間沒有更新技術blog了,現在有空每天都寫寫圍脖,記錄生活和工作的點滴,但是有時候發現有些技術的想法和工作總結沒有像過去那麼完整的寫很大一篇,但是也有零零散散的不少點滴,因此想着隨意的寫這麼一個連續的片段分享。

         爲什麼叫做代碼背後的點滴呢,其實在現在互聯網應用來說,其實用什麼語言,用什麼平臺有些場景有影響,但已經不是絕對重要的因素的,其實代碼被後的設計思想纔是最重要的。而用最熟悉的方式去表現最自然的想法,那才能做到遊刃有餘,就好比我向華黎同學申請這次內部獎勵的獎品希望是手寫筆,因爲不論什麼畫圖工具用起來都會妨礙我的順暢的表達,最終我把注意力集中到了畫本身上,而丟失了應有的靈感(在沒有拿到畫筆前,最近先少畫點圖)。寫代碼也是一樣,不要被流行,高性能,有潛力這些詞搞丟了自己的目標,工具就是工具,能廣泛的去學,但不必要廣泛的用,把幹活要用的那門搞熟練了再說。廢話不多說,言歸正傳。

 

         先說一下,以下的任何觀點都必須“對症下藥”,沒啥萬能靈丹妙藥,怎麼用,什麼時候用是關鍵。

 

聚與散:

         場景一,在分享時談到Jetty7整個體系架構就一個線程資源池,爲什麼?整個jetty體系都是事件驅動模式(包括系統事件:NIO事件,包括業務事件:request suspend),我們系統很多時候有很多資源池(線程,DB,服務)等等,有些是直接的,有些是間接的(依賴於第三方包引入的)。我們要求每個開發者都要給資源池設置上限,給隊列限定長度,防止崩潰,但是當這些資源池散落在各個地方的時候,那麼每一個資源達到邊界以前,總和可能已經超過了系統負載能力,那麼一樣會奔潰。因此資源的統一規劃看來有必要,那可以考慮將物理隔離變成邏輯隔離(防止業務干擾,同時可以根據權重模型來動態分配和預留資源),另一方面整個小團隊到大團隊,對於連接池的使用抽象出來以後,那麼多個依賴都可以在抽象的基礎上公用一個物理資源池,如果引入第三方緩存,都可以將資源策略應用到多機上。回過來,這裏就要說聚和散的關係,聚能夠在一定程度上有全局觀,同時可以根據策略模型來調配資源,散最大的好處就是業務隔離,互相不會影響。最近在寫一個線程資源分配的小東西,目標就是:資源可根據權重模型分配。(權重模型兩條:設定某一類key獲取資源可以保留一定資源獨享,設定某一類key獲取資源最大上限可控)其實這就是一個邏輯隔離資源的最基本的雛形。

         場景二,有同學談起他們的系統屬於消耗CPU類型的應用,特別是對數據庫獲取到的數據作處理和渲染的時候,問有啥好的建議。我的建議是數據獲取端沒啥可優化的話,考慮數據存入端增加數據預處理功能,將數據處理的壓力分攤到更多也數據存入方。當然這個代價是數據的可複用性降低,業務邏輯侵入到其他系統,數據存入方性能會有所下降(如果這三點不足以影響業務流程和數據存入方,那麼可做)。其實這是聚和散的另一種表現,很多時候需要考慮的是全局優化,而不是局部優化。

         場景三,我今天看了淘寶Tair的作者若海介紹關於tair的一些設計思路的文章,這和早先我做memcached的客戶端思想是有共同點的。首先就是數據的獲取從服務端中轉路由變成了客戶端獲得配置本地hash選擇目標服務器,這就將服務路由的功能客戶端化了,避免了單點的瓶頸,也提高了訪問性能。同時metadata的數據保存在configserver中,以弱依賴的方式主動推送,避免客戶端受到影響。但他的設計裏面數據同步是服務端做的,而我當年是客戶端做的(隊列中異步備份),當時一方面是沒有辦法去改服務端的memcached,另一方面服務端能有多少,客戶端有多少,這些壓力的分攤,天然的緩解服務端的壓力,將壓力分散在了客戶端上。(代價就是客戶端工作多一點,即時性依賴於各個客戶端的能力)這也是一種散。

 

    場景四,TOP大數據請求的處理設計,TOP的目標是什麼?大壩。主要職能:限流,授權,路由。那麼路由這件事情必須在TOP走麼,數據交互和平臺校驗是否一定要在一個流程中完成,其實不然,安全校驗通過以後可以頒發會話標識和目標服務地址,直接由客戶端和服務端交互。(這其實就和很多分佈式文件系統或者分佈式緩存系統一樣的考慮,校驗通過後就直接建立數據通道進行數據交互,避免性能受到影響),這了的聚和散就涉及到業務的耦合性分析決定流程的聚散,改善性能和穩定性。

 

    場景五,上面談到過最近自己實現資源分配邏輯隔離的這樣的資源池,現在就是做線程池,起初不想用jdk自帶的線程池,原因是自帶線程池啓動線程後,線程只要在coresize內就不會被銷燬,也沒有調度來從池裏面獲取執行任務,完成任務放回到資源池,這些線程就輪訓的去獲取隊列的數據。好處在於沒有一個manager的管理性能和複雜度會提升,缺點就是資源分配就無法實現。其實這就是典型的一種資源分配有一個實例主導還是由多個消費者自己主導的設計。

 

強弱依賴

         場景:JettyContinuation設計中,從調用suspend方法來懸掛起請求,避免退出容器service方法reqres被回收,到業務主動調用complete結束請求流程,回寫數據到客戶端,都採用產生事件,異步的由核心線程池去執行。這樣業務線程池的線程生命週期就不會受到系統線程輸出快慢或者容器本身壓力的影響。這種弱依賴能夠極大的解耦兩個系統的服務能力和穩定性。

 

關鍵先生

         場景一,有同學一個壓力測試報告中做了這麼一個測試,整個事務處理流程第一步驟消耗CPU,第二步驟消耗IO。整體事務時間=CPU 10 ms + IO 90 ms。他考慮是否能夠通過縮短IO消耗時間或者CPU消耗時間來提升RT時間,就能夠提升TPS。他的結果是IO提升一倍TPS沒有任何變化,CPU消耗時間提升一倍,TPS翻番。這能說明啥,說明瓶頸在CPU上,提升一倍,那麼處理能力增加一倍(就好比資源池內的資源生命週期縮短,被利用的次數更多),大部分請求都在CPU上排隊等待處理,因此係統的TPS取決於瓶頸資源的處理能力而不是簡單的縮短RT就可以改變的。(貼切的比喻就是漏斗,不論你大口朝上或者朝下,小口決定了水流量,小口就是那個關鍵先生)

 

         場景二,異步化壓力測試的結果中有如下一組結果:

容器

請求服務

請求處理模式

TPS

Nginx + Jetty

Time.get

同步

1534

Nginx + Jetty

Time.get

異步

1068

Nginx + Jetty

User.get

同步

1060

Nginx + Jetty

User.get

異步

1027

        

        Time.get是不向後中轉服務的模擬請求,也就是服務端很快就響應給客戶端了,而user.get是真實模擬服務向後請求,有一定消耗。但看這個結果,同樣是同步和異步對比,前者性能相差很大,後者相差不大。原因?異步一定有消耗,但這個消耗佔整體事務處理時間的比例是多少,這就會放大影響。而TOP大部分服務都要比user.get更加耗時,因此後面纔是線上的真實狀況。(因此場景模擬是壓力測試最重要的條件),這裏的關鍵先生還是要看整個事務處理的消耗點。衍生一下上次電話面試的一個北京的朋友,談到了NIO,問了他是否做過BIONIO的測試比較,什麼時候用NIO最好,他還真做過四類測試,有結果,但還是沒搞明白爲什麼:1.傳輸數據量大,後端服務處理慢。2.傳輸數據量大,後端服務處理快。3.傳輸數據量小,後端服務處理慢。4.傳輸數據量小,後端處理速度快。結果是NIO4處於劣勢。其實BIO釋放的夠快,那麼NIO的異步在整體事務處理消耗就形成了劣勢。

        

 

生命週期

         場景:在異步分享過程中,談到了事件驅動的架構設計。原來的流程可以分成很多個步驟,每一個步驟由於處理的需要都會申請必要的資源,不同步驟有些是共享資源,有些是不共享的,那麼從資源的生命週期角度來說,一個流程中所有資源的生命週期都是一樣,就是整個業務事務的執行週期,對於在一個業務流程中各個步驟很少共享資源的狀況,或者有等待外部返回內容的情況時,切割開流程步驟,降低資源生命週期,提升資源利用率,是事件驅動的最大優勢。NIO如此,Servlet3規範如此。簡單來說就是:按需分配。另一方面我們也能看到,處理都是無狀態的,只有輸入和輸出,無狀態的處理更容易複用。看起來這種設計很好,但其實背後還是有不少問題:1.流程複雜化,原來通過程序保證流程的順序和依賴,現在切割以後,如何驅動,順序如何保證,如何容錯,都給系統增加很大的維護成本。2.異步化後消耗時間必然增大,不論是通過pull主動獲取狀態變更還是通過push被通知到,都在性能和時間消耗上會有一些付出。因此還是需要依環境而定。

        

 

瓶頸移動

         場景一,還是一個同學的優化的案例,當時談到有個叫做最佳線程數,但是覺得在優化過程中最佳線程數是在變化的,其實很多時候當你覺得任何的結論是不穩定的,那麼你需要進一步深入挖掘下去。其實之所以最佳線程數這個值不穩定,關鍵在於我們如何去優化,找到問題所在。和前面舉例一樣,系統可以看成各種類型資源池的一個整體,然後由很多個步驟不斷申請資源來完成各個步驟,最後返回結果。那麼說白了,哪個資源池是流程中處理能力最弱的,他就是問題所在,就是瓶頸,但是通過優化後這個瓶頸可能有所改善,與此同時另一個資源成爲漏斗的小口,這時候,瓶頸移動了,優化的預期結果和提升的瓶頸點性能就不吻合了。所以,把帶寬,CPUIO,外部服務,DBWeb線程池等等都看成資源池,找到瓶頸所在去優化,同時在優化過程中不斷修正優化目標,達到最後全局優化的效果。

         場景二,異步化分享中談到一點,異步化要有全局觀。例如A依賴與B系統,組成了一個完整的服務體系,通過異步化方式將A的資源生命週期縮短到只有A處理的時間,B的資源生命週期就是B的處理時間,此時如果A是原有整個系統的瓶頸,那麼就會產生雪中送炭的效果,系統整體性能會有所提高(大部分情況下,原因看後面介紹)。如果B是瓶頸,那麼就是雪上加霜,更多的水流被放進來,如果B自身沒有流量控制,那麼系統就會處於不健康的狀況,甚至奔潰,系統的整體RT時間可能會增加(A節省的時間小於B增加的時間)。因此這就是系統間瓶頸移動產生的影響,同樣也和一個系統一樣,考慮優化一定是整體性的考慮,否則問題不會朝着預期的方向解決。

         晚了晚了,休息,希望這些對大家有幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章