Author:放翁(文初)
Email:fangweng@taobao.com
Blog:http://blog.csdn.net/cenwenchu79
閑話:(如果圖片看不清楚可以看另一個(gè)blog,因?yàn)閳D片在家,這里上傳就只能轉(zhuǎn)貼了)
為什么又叫做什么…的點(diǎn)滴,首先現(xiàn)在寫程序就是練手,不論自己經(jīng)歷了多少,如果想成為一個(gè)好的P,那么就要持續(xù)的去學(xué)習(xí),去寫,當(dāng)寫出來(lái)的東西總是一個(gè)樣子,那就要去學(xué)習(xí)一下,當(dāng)覺(jué)得整天飄飄然的和同行在胡侃,那么就要靜下心來(lái)寫點(diǎn)東西。因此我的分享總是這個(gè)點(diǎn)滴那個(gè)點(diǎn)滴的,其實(shí)大家寫程序都大同小異,最寶貴的不是一個(gè)系統(tǒng)如何成功,而是在設(shè)計(jì)和實(shí)現(xiàn)這個(gè)系統(tǒng)的過(guò)程中,有哪些閃光點(diǎn),這些閃光點(diǎn)日積月累就會(huì)讓你寫出來(lái)的東西給人一種“踏實(shí)”的感覺(jué),同時(shí)不斷的多想一步,會(huì)讓你總是比別人做得更加精彩。
起因:
現(xiàn)在手頭工作太忙,所以分享的東西很多,但是沒(méi)有時(shí)間寫,因此這個(gè)MapReduce “單機(jī)版”說(shuō)說(shuō)是練手,但其實(shí)和當(dāng)前TOP的業(yè)務(wù)也很有關(guān)系。過(guò)去在開放平臺(tái)中有重要的一部分內(nèi)容就是日志分析和報(bào)表的功能,由于開放平臺(tái)的API請(qǐng)求里量很大,因此過(guò)去首先采用異步日志結(jié)合MySql分表模式來(lái)做日志分析,后來(lái)演化成為了異步日志結(jié)合Hadoop的分析模式。
TOP當(dāng)前對(duì)于日志系統(tǒng)的需求是:
1. 滿足不穩(wěn)定的需求統(tǒng)計(jì)分析(性能監(jiān)控調(diào)優(yōu),業(yè)務(wù)趨勢(shì)分析,ISV行為統(tǒng)計(jì)等),快速出結(jié)果。(框架靈活度要高)
2. 分析系統(tǒng)配置使用簡(jiǎn)單。(部署簡(jiǎn)單,維護(hù)簡(jiǎn)單,使用簡(jiǎn)單)
3. 硬件資源節(jié)省。(資源投入少,長(zhǎng)期有規(guī)劃上有規(guī)模化的集群分析計(jì)算)
4. 短期內(nèi)上線。(開發(fā)成本低)
5.處理速度可接受。
根據(jù)以上幾點(diǎn)當(dāng)前的需求,起碼現(xiàn)階段的TOP需要的分析器不需要用Hadoop,同時(shí)采用Hadoop在業(yè)務(wù)上不能滿足靈活的需求(發(fā)布要走流程),使用也過(guò)于復(fù)雜,硬件資源投入起碼兩到三臺(tái)PC,開發(fā)流程上由于業(yè)務(wù)的需求變動(dòng)比較大,這樣如果采用比較硬的編碼方式,則很難短期上線。對(duì)于當(dāng)前TOP的日志量用Hadoop的速度優(yōu)勢(shì)暫時(shí)還不明顯。
就上面這些因素,考慮化幾天時(shí)間做一個(gè)單機(jī)版的MapReduce的日志分析器,滿足現(xiàn)有需求。
設(shè)計(jì):

圖1 Simple MapReduce Log Analysis UseCase
圖2 基本流程定義

圖3 角色工作圖
系統(tǒng)簡(jiǎn)化來(lái)看就分成三個(gè)角色:
1. JobManager:負(fù)責(zé)讀取系統(tǒng)配置,和初始化分析規(guī)則引擎,切割文件,創(chuàng)建Worker,協(xié)同Worker并行分析,合并分析結(jié)果,輸出報(bào)表。
2. RuleEngine:根據(jù)配置載入和構(gòu)建日志解析規(guī)則(可定制化Map和Reduce實(shí)現(xiàn)),中間結(jié)果合并規(guī)則,報(bào)表創(chuàng)建規(guī)則,附帶發(fā)送郵件等功能配置。
3. JobWorker:根據(jù)規(guī)則引擎配置,逐行分析日志,每行分析出所有配置需要的結(jié)果,作一次簡(jiǎn)單的MapReduce操作,輸出中間結(jié)果給Manager。
實(shí)現(xiàn):
一.報(bào)表配置及規(guī)則引擎
a. 兩個(gè)層次。在Hadoop的MapReduce的計(jì)算結(jié)果是Key&Value,這通常并不是我們很多分析系統(tǒng)希望要的最終結(jié)果,分析系統(tǒng)希望是得到類似于SQL查詢到的一組結(jié)果,反過(guò)來(lái)看,對(duì)于一組結(jié)果其實(shí)就是一堆Key&Value的組合:
例如:
Select name,address,count(*) form t 得到的結(jié)果就是以name&address組合成為key,然后累加次數(shù)產(chǎn)生的value。
Select name,address,average(age) form t得到的結(jié)果就是以name&address組合成為key,然后平均年齡得到的value。
而這兩個(gè)結(jié)果由于都是以相同的兩個(gè)字段作為索引,因此歸類在一起就會(huì)形成我們通常希望看到的一個(gè)報(bào)表。因此產(chǎn)生了定義的報(bào)表配置的兩個(gè)層次:
1. ReportEntry就是一個(gè)key&value產(chǎn)生的規(guī)則定義。
2. Report就是單個(gè)報(bào)表創(chuàng)建的定義。
b. 五種基本統(tǒng)計(jì)函數(shù),對(duì)于統(tǒng)計(jì)來(lái)說(shuō)大多都是對(duì)數(shù)字的處理,抽象起來(lái)公用的主要有五種:min,max,sum,count,average。同時(shí)為了能夠提供顯示主鍵的功能,提供一個(gè)直接顯示內(nèi)容的plain函數(shù),這樣基本涵蓋了70%的統(tǒng)計(jì)需求。
c. 兩種表達(dá)式創(chuàng)建Value。對(duì)于value的創(chuàng)建可以設(shè)置表達(dá)式,比如說(shuō)每一條記錄的第三列減去第四列的最大值可以配置為value=”$3$ - $4$”,用$符號(hào)分割表示對(duì)列的引用。也可以定義某一統(tǒng)計(jì)結(jié)果是其他列統(tǒng)計(jì)結(jié)果的計(jì)算結(jié)果,例如成功率可以使成功數(shù)量/總量,配置為value=”entry(成功數(shù)列號(hào))/entry(總量列號(hào))”,此類結(jié)果將在報(bào)表創(chuàng)建時(shí)候才被計(jì)算生成,屬于lazy分析。
下圖是具體的類定義圖

具體配置參見(jiàn)附錄說(shuō)明。
二.切割日志文件
單臺(tái)服務(wù)器單日最大的日志有1G多的日志,對(duì)于這么大的日志需要考慮切分一下交由多個(gè)Jobworker來(lái)并行處理提高效率。因此就涉及到了切割文件的工作。切割文件就需要做到高效,數(shù)據(jù)完整性。
高效:一般切割是對(duì)一個(gè)目錄下所有文件切割,因此起一個(gè)線程池并行切割,提高效率。同時(shí)對(duì)于單個(gè)文件的切割,采用FileChannel的方式(MapFile),簡(jiǎn)單按照配置大小切割成子文件。
數(shù)據(jù)完整性:由于TOP的日志文件是以回車換行作為記錄分割符,因此從第二個(gè)文件開始,每一個(gè)文件讀取第一句有回車換行的內(nèi)容到上一個(gè)文件,這樣就可以保證數(shù)據(jù)的完整性(簡(jiǎn)單補(bǔ)償方案),需要注意的就是邊界情況,當(dāng)最后一個(gè)文件就一句話內(nèi)容,那么這句內(nèi)容一旦被提前,需要?jiǎng)h除這個(gè)子文件。
遇到的問(wèn)題:
1. 直接根據(jù)設(shè)置的文件塊來(lái)拷貝,導(dǎo)致多線程并行處理時(shí),native方法消耗內(nèi)存溢出(這個(gè)其實(shí)在很多第三方的開源包中處理不好都會(huì)有這樣的問(wèn)題),由于Jdk提供了內(nèi)存映像文件,提高速度的同時(shí),也為這類內(nèi)存申請(qǐng)使用帶來(lái)了內(nèi)存溢出的隱患(這類內(nèi)存的回收和普通的GC回收不同,回收的時(shí)機(jī)也不一樣),因此當(dāng)機(jī)器速度越快的時(shí)候,可能溢出的情況越容易發(fā)生。于是將inChannel.transferTo(beg,blocksize, outChannel)改成了一段一段的復(fù)制。
2. 單機(jī)磁盤IO瓶頸在某種程度上決定了多線程并行未必會(huì)提高多少處理效率。
3. 有同學(xué)和我說(shuō)你其實(shí)不用切割,直接用RandomAccessFile來(lái)讀取分析就可以了,節(jié)省時(shí)間。感覺(jué)很有道理,前期陷入了思維定勢(shì),但是對(duì)于單機(jī)來(lái)說(shuō)磁盤IO及文件鎖使得虛擬切割的效率還不如單線程處理。(作了一下測(cè)試)
三.JobWorker實(shí)現(xiàn)
對(duì)于通過(guò)數(shù)據(jù)庫(kù)來(lái)做統(tǒng)計(jì)的情況,通常會(huì)需要Select幾次才會(huì)得到一個(gè)報(bào)表的幾項(xiàng)結(jié)果,但對(duì)于逐行掃描處理的情況來(lái)說(shuō),不論配置多少Entry,在一次日志讀取以后就能根據(jù)規(guī)則來(lái)計(jì)算出這個(gè)Entry的結(jié)果,因此對(duì)于海量數(shù)據(jù)的分析,在一次數(shù)據(jù)遍歷以后就可以得到所有的結(jié)果。這點(diǎn)也是去年我和開放平臺(tái)同學(xué)review他的hadoop MapReduce的時(shí)候提出的建議,如果就做一次MapReduce就需要分析一次數(shù)據(jù),那么肯定會(huì)效率很低,通常就是需要定義一個(gè)Map就能夠作很多規(guī)則的分析,這就需要對(duì)于在傳統(tǒng)MapReduce中作較好的層次級(jí)別規(guī)劃,一次數(shù)據(jù)分析能夠被多個(gè)分析共享。而在這里設(shè)計(jì)JobWorker來(lái)說(shuō),本身逐行解析就可以實(shí)現(xiàn)這點(diǎn),這也使得報(bào)表不論定義多少,分析的時(shí)間復(fù)雜度幾乎沒(méi)有增加。

IReportManager作用就是管理整個(gè)分析流程,初始化資源(載入配置,初始化規(guī)則引擎,切割文件),創(chuàng)建協(xié)調(diào)工作者,合并結(jié)果集,出報(bào)表。

定義了Worker兩個(gè)實(shí)現(xiàn),一個(gè)是真實(shí)文件處理的Worker,一個(gè)是虛擬文件處理的Worker。(所謂的虛擬文件,就是上面提到的虛擬切割后生成的虛擬文件)在JobWorker處理中,可以根據(jù)規(guī)則引擎中定義的單個(gè)Entry處理模式和定制化Map或者Reduce實(shí)現(xiàn)來(lái)替換框架已有的Map和Reduce滿足不同的業(yè)務(wù)需求。具體的Map和Reduce參看下面的類圖。
IReportMap就一個(gè)接口generateKeyReportEntry(ReportEntry entry,Sting[] contents),也就是每一個(gè)Map都可以得到當(dāng)前處理的數(shù)據(jù)內(nèi)容以及當(dāng)前Entry的數(shù)據(jù)定義。IReportMap和下面的IReportReduce都是可以通過(guò)運(yùn)行期配置的方式替換現(xiàn)有框架中的業(yè)務(wù)邏輯。

Worker具體流程圖如下:

在流程中允許用自定義的Map和Reduce實(shí)現(xiàn)類替換默認(rèn)的處理類,滿足用戶個(gè)性化需求,同時(shí)降低對(duì)于基礎(chǔ)框架的依賴。
問(wèn)題:
1.多線程池(Executor等)必須控制好線程數(shù)量,防止內(nèi)存溢出。
2.瓶頸在IO,因此效率提高有限。
3.自定義協(xié)議解析替換了J2se 6的js engine。JS引擎很強(qiáng)大,但是效率不高,在大規(guī)模數(shù)據(jù)處理的時(shí)候耗時(shí)嚴(yán)重,成為最大的瓶頸,因此采用簡(jiǎn)單的算法來(lái)替換。
四.報(bào)表生成
最終報(bào)表的輸出類型,選擇了csv,首先由于csv結(jié)果排版簡(jiǎn)單,其次,可以借助excel的強(qiáng)大圖形功能將數(shù)字結(jié)果轉(zhuǎn)換成為更加直觀的圖形結(jié)果。
比較和改進(jìn)
傳統(tǒng)的MapReduce步驟如下:導(dǎo)入數(shù)據(jù)到分布式文件系統(tǒng)(切割文件,文件傳輸?shù)?/span>dataNode,同時(shí)做好容災(zāi)準(zhǔn)備),JobNode在JobTracker的協(xié)調(diào)下開始分析,并在本地作一次reduce(減少數(shù)據(jù)傳輸),再匯總作Reduce,最后生成結(jié)果。
單機(jī)版MapReduce,只是將多機(jī)協(xié)作變成了多線程協(xié)作。
1. 省略數(shù)據(jù)傳輸不用讓數(shù)據(jù)靠近計(jì)算。
2. 通過(guò)配置文件的方式定制報(bào)表,可以靈活的將報(bào)表系統(tǒng)變成隨時(shí)可以根據(jù)需求變動(dòng)的動(dòng)態(tài)分析系統(tǒng)。(每次配置文件可以從遠(yuǎn)端讀取,這樣就可以不發(fā)布而立刻獲得不同的報(bào)表)。
3. 使用便利,通過(guò)一個(gè)系統(tǒng)配置文件設(shè)定系統(tǒng)運(yùn)行參數(shù),然后直接執(zhí)行jar,即可運(yùn)行,不需要配置多機(jī)環(huán)境。
4. 對(duì)于幾十個(gè)G的數(shù)據(jù)處理正合適(效率)
5. 開發(fā)調(diào)試周期短,基本上5個(gè)人日開發(fā)+測(cè)試就搞定了。
但其實(shí)缺陷還是很一目了然的,就是我們?yōu)槭裁匆?/span>MapReduce的多機(jī)配置的初衷,單機(jī)最終在CPU,IO上都成為瓶頸,垂直擴(kuò)容和水平擴(kuò)容已經(jīng)沒(méi)有什么好爭(zhēng)議的了,因此采用多機(jī)合作在規(guī)模化的處理上是必然趨勢(shì)。對(duì)于這個(gè)單機(jī)版作適當(dāng)?shù)恼{(diào)整,成為簡(jiǎn)化型日志分析專用型多機(jī)版MapReduce還是蠻有必要的。那么切入點(diǎn)其實(shí)就是以下幾點(diǎn):
1. 分析數(shù)據(jù)將會(huì)散布到多機(jī),分別切割處理。(可以不考慮容災(zāi))
2. 多機(jī)多線程分析并在本機(jī)Reduce一次,然后通過(guò)配置找到內(nèi)部的Master,交由Master來(lái)最終作Reduce。(workernode之間無(wú)需知道對(duì)方的存在)
3. Master由原來(lái)內(nèi)存結(jié)果合并,轉(zhuǎn)變?yōu)閭鬏斶^(guò)來(lái)的結(jié)果反序列化或者遵照私有消息格式來(lái)合并結(jié)果,最終創(chuàng)建出報(bào)表。
因此簡(jiǎn)單來(lái)說(shuō)就是數(shù)據(jù)分布及中間結(jié)果的傳遞和合并的工作處理一下,單機(jī)版就變成了多機(jī)版。最后就在附錄中詳細(xì)說(shuō)明一下配置及運(yùn)行后的效果。
附錄:
配置實(shí)例說(shuō)明:
下面寫一個(gè)具體的實(shí)際配置來(lái)展示如何配置一個(gè)簡(jiǎn)單的報(bào)表:(其中衍生出一些需求增強(qiáng)的內(nèi)容)
配置文件是xml格式的,配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<top_reports>
<entrys>//定義需要給多個(gè)報(bào)表復(fù)用的Entry
<ReportEntry id="1" name="api_totalCount" key="6" value="count()"/>//id是這個(gè)entry唯一的標(biāo)識(shí)(后面被引用到報(bào)表的依據(jù)),name將會(huì)作為報(bào)表的列名,key表示會(huì)以日志記錄的第幾項(xiàng)內(nèi)容作為索引,可以通過(guò)逗號(hào)分割(組合索引),value表示創(chuàng)建的值是按照什么規(guī)則來(lái)創(chuàng)建,count函數(shù)不需要有內(nèi)部表達(dá)式,average,min,max,plain都需要有表達(dá)式,表達(dá)式內(nèi)部$16$代表日志記錄第幾位作為參數(shù)傳入運(yùn)算,entry(16)代表對(duì)第16的entry結(jié)果作引用計(jì)算(具體參見(jiàn)下面配置)。這句話表示用第六位這個(gè)api名稱字段作為索引,計(jì)算各個(gè)api的總調(diào)用量。
<ReportEntry id="2" name="api_successCount" key="6" value="count()"
mapClass="com.taobao.top.analysis.map.APIErrorCodeMap" mapParams="key=6&errorCode=0"/>//這個(gè)配置和上面不同的就在于mapClass可以自定義實(shí)現(xiàn),其實(shí)也就是實(shí)現(xiàn)了對(duì)于Key產(chǎn)生的規(guī)則實(shí)現(xiàn),也就是MapReduce模型中的Map函數(shù)實(shí)現(xiàn),這里可以用默認(rèn)的(即簡(jiǎn)單的列組合),也可以采用復(fù)雜的方式過(guò)濾和合并key,只需要實(shí)現(xiàn)Map接口即可,這后面詳細(xì)描述。這個(gè)配置表示產(chǎn)生key的過(guò)程中過(guò)濾掉不成功的請(qǐng)求,只統(tǒng)計(jì)成功請(qǐng)求
<ReportEntry id="3" name="api_failCount" key="6" value="count()"
mapClass="com.taobao.top.analysis.map.APIErrorCodeMap" mapParams="key=6&errorCode=1"/>//統(tǒng)計(jì)錯(cuò)誤請(qǐng)求
<ReportEntry id="4" name="api_AverageServiceTimeConsume" key="6" value="average($14$ - $13$)" />//統(tǒng)計(jì)服務(wù)平均相應(yīng)時(shí)間,由于在服務(wù)處理前后有時(shí)間打點(diǎn),因此簡(jiǎn)單的相減即可
<ReportEntry id="5" name="api_AverageTIPTimeConsume" key="6" value="average($16$ - $11$ - $14$ + $13$)" />
<ReportEntry id="6" name="api_MinServiceTimeConsume" key="6" value="min($14$ - $13$)" />//最小時(shí)間消耗
<ReportEntry id="7" name="api_MaxServiceTimeConsume" key="6" value="max($14$ - $13$)" />//最大時(shí)間消耗
……
</entrys>
<reports>
//具體的報(bào)表定義
<report id="2" file="apiReport" mailto="wenchu.cenwc@alibaba-inc.com">
<entryList>
<entry name="APIName" key="6" value="plain($6$)" />//不需要復(fù)用的entry可以直接定義在報(bào)表內(nèi)部,這個(gè)定義表示直接顯示第六列即API的名稱
<entry id="1"/>
<entry id="2"/>
<entry name="APISuccessRatio" key="6" value="plain(entry(2)/entry(1))" /> //可以計(jì)算比例,通過(guò)對(duì)entry1和entry2的結(jié)果相除,不過(guò)這個(gè)就不是在逐行分析過(guò)程中實(shí)現(xiàn),而是在結(jié)果合并時(shí)處理,屬于lazy后處理
<entry id="3"/>
<entry id="4"/>
<entry id="5"/>
<entry name="TIPTimeConsumeRatio" key="6" value="plain(entry(5)/entry(5)+entry(4))" />
<entry id="6"/>
<entry id="7"/>
</entryList>
</report>
</reports>
</top_reports>
