<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    放翁(文初)的一畝三分地

      BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
      210 隨筆 :: 1 文章 :: 320 評論 :: 0 Trackbacks

    細節(jié)優(yōu)化提升資源利用率

    Author: 放翁(文初)

    Email:  fangweng@taobao.com

    Mblogweibo.com/fangweng

     

                      這里通過介紹對于淘寶開放平臺基礎設置之一的TOPAnalyzer的代碼優(yōu)化,來談一下對于海量數據處理的Java應用可以共享的一些細節(jié)設計(一個系統能夠承受的處理量級別往往取決于細節(jié),一個系統能夠支持的業(yè)務形態(tài)往往取決于設計目標)。

                      先介紹一下整個TOPAnalyzer的背景,目標和初始設計,為后面的演變做一點鋪墊。

                      開放平臺從內部開放到正式對外開放,逐步從每天幾千萬的服務調用量發(fā)展到了上億到現在的15億,開放的服務也從幾十個到了幾百個,應用接入從幾百個增加到了幾十萬個。此時,對于原始服務訪問數據的分析需求就凸現出來:

    1.  應用維度分析(應用的正常業(yè)務調用行為和異常調用行為分析)

    2.  服務維度分析(服務RT,總量,成功失敗率,業(yè)務錯誤及子錯誤等)

    3.  平臺維度分析(平臺消耗時間,平臺授權等業(yè)務統計分析,平臺錯誤分析,平臺系統健康指標分析等)

    4.  業(yè)務維度分析(用戶,應用,服務之間關系分析,應用歸類分析,服務歸類分析等)

    上面只是一部分,從上面的需求來看需要一個系統能夠靈活的運行期配置分析策略,對海量數據作即時分析,將接過用于告警,監(jiān)控,業(yè)務分析。

     

    下圖是最原始的設計圖,很簡單,但還是有些想法在里面:




    Master:管理任務(分析任務),合并結果(Reduce),輸出結果(全量統計,增量片段統計)

    SlaveRequire Job + Do Job + Return Result,隨意加入,退出集群。

    Job(Input + Analysis Rule + Output)的定義。

     

    幾個設計點:

    1.           后臺系統任務分配:無負載分配算法,采用細化任務+工作者按需自取+粗暴簡單任務重置策略。

    2.           SlaveMaster采用單向通信,便于容量擴充和縮減。

    3.           Job自描述性,從任務數據來源,分析規(guī)則,結果輸出都定義在任務中,使得Slave適用與各種分析任務,一個集群分析多種日志,多個集群共享Slave

    4.           數據存儲無業(yè)務性(意味著存儲的時候不定義任何業(yè)務含義),分析規(guī)則包含業(yè)務含義(在執(zhí)行分析的時候告知不同列是什么含義,怎么統計和計算),優(yōu)勢在于可擴展,劣勢在于全量掃描日志(無預先索引定義)。

    5.           透明化整個集群運行狀況,保證簡單粗暴的方式下能夠快速定位出節(jié)點問題或者任務問題。(雖然沒有心跳,但是每個節(jié)點的工作都會輸出信息,通過外部收集方式快速定位問題,防止集群為了監(jiān)控耦合不利于擴展)

    6.           Master單點采用冷備方式解決。單點不可怕,可怕的是丟失現場和重啟或重選Master周期長。因此采用分析數據和任務信息簡單周期性外部存儲的方式將現場保存與外部(信息盡量少,保證恢復時快速),另一方面采用外部系統通知方式修改Slave集群MasterIP,人工快速切換到冷備。

     

    Master的生活軌跡:




     

    Slave的生活軌跡:

     



     

    有人會覺得這玩意兒簡單,系統就是簡單+透明才會高效,往往就是因為系統復雜才會帶來更多看似很高深的設計,最終無非是折騰了自己,苦了一線。廢話不多說,背景介紹完了,開始講具體的演變過程。

    數據量:2千萬 à 1 à 8 à15億。報表輸出結果:10份配置à30à60à100份。統計后的數據量:10k à 10M à 9G。統計周期的要求:1à5分鐘à3分鐘à1分半。

    從上面這些數據可以知道從網絡和磁盤IO,到內存,到CPU都會經歷很大的考驗,由于Master是縱向擴展的,因此優(yōu)化Master成為每個數據跳動的必然要求。由于是用Java寫的,因此內存對于整體分析的影響更加嚴重,GC的停頓直接可以使得系統掛掉(因為數據在不斷流入內存)。

     

    優(yōu)化過程:

     

    縱向系統的工作的分擔:

                      Master的生活軌跡可以看到,它負荷最大的一步就是要去負責Reduce,無論如何都需要交給一個單節(jié)點來完成所有的Reduce,但并不表示對于多個Slave的所有的Reduce都需要Master來做。有同學給過建議說讓Master再去分配給不同的Slave去做Slave之間的Reduce,但一旦引入MasterSlave的通信和管理,這就回到了復雜的老路。因此這里用最簡單的方式,一個機器可以部署多個Slave,一個Slave可以一次獲取多個Job,執(zhí)行完畢后本地合并再匯報給Master。(優(yōu)勢:MasterJob合并所產生的內存消耗可以減輕,因為這是統計,所以合并后數據量一定大幅下降,此時Master合并越少的Job數量,內存消耗越小),因此Slave的生活軌跡變化了一點:




     

     

    流程中間數據優(yōu)化:

                      這里舉兩個例子來說明對于處理中中間數據優(yōu)化的意義。

                      在統計分析中往往會有對分析后的數據做再次處理的需求,例如一個API報表里面會有API訪問總量,API訪問成功數,同時會要有API的成功率,這個數據最早設計的時候和普通的MapReduce字段一樣處理,計算和存儲在每一行數據分析的時候都做,但其實這類數據只有在最后輸出的時候才有統計和存儲價值,因為這些數據都可以通過已有數據計算得到,而中間反復做計算在存儲和計算上都是一種浪費,因此對于這種特殊的Lazy處理字段,中間不計算也不存儲,在周期輸出時做一次分析,降低了計算和存儲的壓力。

                      對于MapReduce中的Key存儲的壓縮。由于很多統計的Key是很多業(yè)務數據的組合,例如APPAPIUser的統計報表,它的Key就是三個字段的串聯:taobao.user.get—12132342—fangweng,這時候大量的Key會占用內存,而Key的目的就是產生這個業(yè)務統計中的唯一標識,因此考慮這些API的名稱等等是否可以替換成唯一的短內容就可以減少內存占用。過程中就不多說了,最后在分析器里面實現了兩種策略:

    1.     不可逆數字摘要采樣。

    有點類似與短連接轉換的方式,對數據做Md5數字摘要,獲得16byte,然后根據壓縮配置來采樣16byte部分,用可見字符定義出64進制來標識這些采樣,最后形成較短的字符串。

    由于Slave是數據分析者,因此用SlaveCPU來換Master的內存,將中間結果用不可逆的短字符串方式表示。弱點:當最后分析出來的數據量越大,采樣md5后的數據越少,越容易產生沖突,導致統計不準確。

     

    2.     提供需要壓縮的業(yè)務數據列表。

    業(yè)務方提供日志中需要替換的列定義及一組定義內容。簡單來說,當日志某一列可以被枚舉,那么就意味者這一列可以被簡單的替換成短標識。例如配置APIName這列在分析生成key的時候可以被替換,并且提供了500多個api的名稱文件載入到內存中,那么每次api在生成key的時候就會被替換掉名稱組合在key中,大大縮短key。那為什么要提供這些api的名稱呢?首先分析生成keySlave,是分布式的,如果采用自學習的模式,勢必要引入集中式唯一索引生成器,其次還要做好足夠的并發(fā)控制,另一方面也會由并發(fā)控制帶來性能損耗。這種模式雖然很原始,但不會影響統計結果的準確性,因此在分析器中被使用,這個列表會隨著任務規(guī)則每次發(fā)送到Slave中,保證所有節(jié)點分析結果的一致性。

     

    特殊化處理特殊的流程:

                      Master的生活軌跡中可以看出,影響一輪輸出時間和內存使用的包括分析合并數據結果,導出報表和導出中間結果。在數據上升到1億的時候,SlaveMaster之間數據通信以及Master的中間結果磁盤化的過程中都采用了壓縮的方式來減少數據交互對IO緩沖的影響,但一直考慮是否還可以再壓榨一點。首先導出中間結果的時候最初采用簡單的Object序列化導出,從內存使用,外部數據大小,輸出時間上來說都有不少的消耗,仔細看了一下中間結果是Map<String,Map<String,Obj>>,其實最后一個Obj無非只有兩種類型DoubleString,既然這樣,序列化完全可以簡單來作,因此直接很簡單的實現了類似Json簡化版的序列化,從序列化速度,內存占用減少上,外部磁盤存儲都有了極大的提高,外部磁盤存儲越小,所消耗的IO和過程中需要的臨時內存都會下降,序列化速度加快,那么內存中的數據就會被盡快釋放。總體上來說就是特殊化處理了可以特殊化對待的流程,提高了資源利用率。(同時中間結果在前期優(yōu)化階段的作用就是為了備份,因此不需要每個周期都做,當時做成可配置的周期值輸出)

                      再接著來談一下中間結果合并時候對于內存使用的優(yōu)化。Master會從多個Slave得到多個Map<Key,Map<Key,Value>>,合并過程就是對多個Map將第一級Key相同的數據做整合,例如第一級Key的一個值是API訪問總量,那么它對應的Map中就是不同的api名稱和總量的統計,而多個Map直接合并就是將一級keyAPI訪問總量)下的Map數據合并起來(同樣的api總量相加最后保存一份)。最簡單的做法就是多個Map<Key,Map<Key,Value>>遞歸的來合并,但如果要節(jié)省內存和計算可以有兩個小改進,首先選擇其中一個作為最終的結果集合(避免申請新空間,也避免輪詢這個Map的數據),其次每一次遞歸時候,將合并后的后面的Map中數據移出(減少后續(xù)無用的循環(huán)對比,同時也節(jié)省空間)。看似小改動,但效果很不錯。

                      再談一下在輸出結果時候的內存節(jié)省。在輸出結果的時候,是基于內存中一份Map<Key,Map<Key,Value>>來構建的。其實將傳統的MapReduceKV結果如何轉換成為傳統的Report,只需要看看Sql中的Group設計,將多個KV通過Group by key,就可以得到傳統意義上的KeyValueValueValue。例如:KV可以是<apiName,apiTotalCount>,<apiName,apiResponse>,<apiName,apiFailCount>,如果Group by apiName,那么就可以得到 apiName,apiTotalCount,apiResponse,apiFailCount的報表行結果。這種歸總的方式可以類似填字游戲,因為我們結果是KV,所以這個填字游戲默認從列開始填寫,遍歷所有的KV以后就可以完整的得到一個大的矩陣并按照行輸出,但代價是KV沒有遍歷完成以前,無法輸出。因此考慮是否可以按照行來填寫,然后每一行填寫完畢之后直接輸出,節(jié)省申請內存。按行填寫最大的問題就是如何在對KV中已經處理過的數據打上標識,不要重復處理。(一種方式引入外部存儲來標識這個值已經被處理過,因為這些KV不可以類似合并的時候刪除,后續(xù)還會繼續(xù)要用,另一種方式就是完全備份一份數據,合并完成后就刪除),但本來就是為了節(jié)約內存的,引入更多的存儲,就和目標有悖了。因此做了一個計算換存儲的做法,例如填充時輪訓的順序為:K1V1K2V2K3V3,到K2V2遍歷的時候,判斷是否要處理當前這個數據,就只要判斷這個K是否在K1里面出現過,而到K3V3遍歷的時候,判斷是否要處理,就輪詢K1K2是否存在這個K,由于都是Map結構,因此這種查找的消耗很小,由此改為行填寫,逐行輸出。

     

    最后再談一下最重頭的優(yōu)化,合并調度及磁盤內存互換的優(yōu)化

                Master的生活軌跡可以看到,原來的主線程負責檢查外部分析數據結果狀態(tài),合并數據結果這個循環(huán),考慮到最終合并后數據只有一個主干,因此采用單線程合并模式來運作,見下圖:




                      這張圖大致描述了一下處理流程,Slave隨時都會將分析后的結果掛到結果緩沖隊列上,然后主線程負責批量獲取結果并且合并。雖然是批量獲取,但是為了節(jié)省內存,也不能等待太久,因為每一點等待就意味著大量沒有合并的數據將會存在與內存中,但合并的太頻繁也會導致在合并過程中,新加入的結果會等待很久,導致內存吃緊。或許這個時候會考慮,為什么不直接用多線程來合并,的確,多線程合并并非不可行,但要考慮如何兼顧到主干合并的并發(fā)控制,因為多個線程不可能同時都合并到數據主干上,由此引入了下面的設計實現,半并行模式的合并:




                      從上圖可以發(fā)現增加了兩個角色:Merge Worker Thread PoolBranch merged ResultList,與上面設計的差別就在于主線程不再負責合并數據,而是批量的獲取數據交給合并線程池來合并,而合并線程池中的工作者在合并的過程中會競爭主干合并鎖,成功獲得的就和主干合并,不成功的就將結果合并后放到分支合并隊列上,等待下次合并時被主干合并或者分支合并獲得再次合并。這樣改進后,發(fā)現由于數據掛在隊列沒有得到及時處理產生的內存壓力大大下降,同時也充分利用了多核,多線程榨干了多核的計算能力(線程池大小根據cpu核來設置的小一點,預留一點給GC用)。這種設計中還多了一些小的調優(yōu)配置,例如是否允許被合并過的數據多次被再次合并(防止無畏的計算消耗),每次并行合并最小結果數是多少,等待堆積到最小結果數的最大時間等等。(有興趣看代碼)

                      至上面的優(yōu)化為止,感覺合并這塊已經被榨干了,但分析日志數據的增多,對及時性要求的加強,使得我又要重新審視是否還有能力繼續(xù)榨出這個流程的水份。因此有了一個大膽的想法,磁盤換內存。因為在調度合并上已經找不到更多可以優(yōu)化的點了,但是有一點還可以考慮,就是主干的那點數據是否要貫穿于整個合并周期,而且主干的數據隨著增量分析不斷增大(在最近這次優(yōu)化的過程中也就是發(fā)現GC的頻繁導致合并速度下降,合并速度下降導致內存中臨時數據保存的時間久,反過來又影響GC,最后變成了惡性循環(huán))。盡管覺得靠譜,但不測試不得而知。于是得到了以下的設計和實現:




                      這個流程發(fā)現和第二個流程就多了最后兩個步驟,判斷是否是最后的一次合并,如果是載入磁盤數據,然后合并,合并完后將主干輸出到磁盤,清空主干內存。(此時發(fā)現導出中間結果原來不是每次必須的,但是這種模式下卻成為每次必須的了)

                      這個改動的優(yōu)勢在什么地方?例如一個分析周期是2分鐘,那么在2分鐘內,主干龐大的數據被外置到磁盤,內存大量空閑,極大提高了當前時間片結果合并的效率(GC少了)。缺點是什么?會在每個周期產生兩次磁盤大量的讀寫,但配合上優(yōu)化過的中間結果載入載出(前面的私有序列化)會適當緩和。

                      由于線下無法模擬,就嘗試著線上測試,發(fā)現GC減少,合并過程加速達到預期,但是每輪的磁盤和內存的換入換出由于也記入在一輪分析時間之內,每輪寫出最大時候70m數據,需要消耗10多秒,甚至20秒,讀入最大需要10s,這個時間如果算在要求一輪兩分鐘內,那也是不可接受的),重新審視是否有疏漏的細節(jié)。首先載入是否可以異步,如果可以異步,而不是在最后一輪才載入,那么就不會納入到分析周期中,因此配置了一個可以調整的比例值,當任務完成到達或者超過這個比例值的時候,將開始并行載入數據,最后一輪等到異步載入后開始分析,發(fā)現果然可行,因此這個時間被排除在周期之外(雖然也帶來了一點內存消耗)。然后再考慮輸出是否可以異步,以前輸出不可以異步的原因是這份數據是下一輪分析的主干,如果異步輸出,下一輪數據開始處理,很難保證下一輪的第一個任務是否會引發(fā)數據修改,導致并發(fā)問題,所以一直鎖定主干輸出,直到完成再開始,但現在每次合并都是空主干開始的,因此輸出完全可以異步,主干可以立刻清空,進入下一輪合并,只要在下一個周期開始載入主干前異步導出主干完成即可,這個時間是很長的,完全可以把控,因此輸出也可以變成異步,不納入分析周期。

                      至此完成了所有的優(yōu)化,分析器高峰期的指標發(fā)生了改變:一輪分析從2分鐘左右降低到了110秒,JVMO區(qū)在合并過程中從5080的占用率下降到2060的占用率,GC次數明顯大幅減少。

     

    總結:

    1.     利用可橫向擴展的系統來分擔縱向擴展系統的工作。

    2.     流程中中間數據的優(yōu)化處理。

    3.     特殊化處理可以特殊處理的流程。

    4.     從整體流程上考慮不同策略的消耗,提高整體處理能力。

    5.     資源的快用快放,提高同一類資源利用率。

    6.     不同階段不同資源的互換,提高不同資源的利用率。

     

    其實很多細節(jié)也許看了代碼才會有更深的體會,分析器只是一個典型的消耗性案例,每一點改進都是在數據和業(yè)務驅動下不斷的考驗。例如縱向的Master也許真的有一天就到了它的極限,那么就交給Slave將數據產出到外部存儲,交由其他系統或者另一個分析集群去做二次分析。對于海量數據的處理來說都需要經歷初次篩選,再次分析,展示關聯幾個階段,Java的應用擺脫不了內存約束帶來對計算的影響,因此就要考慮好自己的頂在什么地方。但優(yōu)化一定是全局的,例如磁盤換內存,磁盤帶來的消耗在總體上來說還是可以接受的化,那么就可以被采納(當然如果用上SSD效果估計會更好)。

    最后還是想說的是,很多事情是簡單做到復雜,復雜再回歸到簡單,對系統提出的挑戰(zhàn)就是如何能夠用最直接的方式簡單的搞定,而不是做一個臃腫依賴龐大的系統,簡單才看的清楚,看的清楚才有機會不斷改進。


    @import url(http://www.tkk7.com/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);
    posted on 2011-09-23 14:03 岑文初 閱讀(5101) 評論(1)  編輯  收藏

    評論

    # re: 細節(jié)優(yōu)化提升資源利用率 2011-09-25 23:23 tb
    測試的不錯   回復  更多評論
      


    只有注冊用戶登錄后才能發(fā)表評論。


    網站導航:
     
    主站蜘蛛池模板: 亚洲国产精品久久久久婷婷老年| 亚洲午夜免费视频| 亚洲av无码不卡久久| 亚洲伊人久久大香线蕉结合| 亚洲情XO亚洲色XO无码| 国产精品免费看久久久久| 激情无码亚洲一区二区三区| 亚洲欧洲国产成人精品| 四虎成人精品在永久免费 | 免费很黄很色裸乳在线观看| 又粗又大又黑又长的免费视频| 无码成A毛片免费| a级片免费观看视频| 亚洲国产成a人v在线观看| 亚洲综合一区二区国产精品| 亚洲精品无码永久在线观看你懂的| 国产一区二区三区免费在线观看 | 色婷婷亚洲十月十月色天| 亚洲韩国精品无码一区二区三区 | 中文字幕日本人妻久久久免费| 免费国产在线精品一区| 美女被爆羞羞网站在免费观看| 亚洲色大情网站www| 亚洲熟妇无码av另类vr影视| 国产午夜亚洲精品| 亚洲熟女综合色一区二区三区 | 人与禽交免费网站视频| 亚洲免费人成视频观看| free哆啪啪免费永久| 免费人成在线观看69式小视频| 亚洲一级毛片免费观看| 亚洲成年人免费网站| 四虎永久在线精品免费观看视频| 国产国产人免费视频成69堂| 91情侣在线精品国产免费| 精品久久久久国产免费| 美女在线视频观看影院免费天天看| 免费人成在线观看视频高潮| 男人都懂www深夜免费网站| 91香焦国产线观看看免费| 国产精品色拉拉免费看|