日誌在計算機系統中是一個很是普遍的概念,任何程序都有可能輸出日誌:操做系統內核、各類應用服務器等等。日誌的內容、規模和用途也各不相同,很難一律而論。前端
本文討論的日誌處理方法中的日誌,僅指Web日誌。其實並無精確的定義,可能包括但不限於各類前端Web服務器——apache、lighttpd、tomcat等產生的用戶訪問日誌,以及各類Web應用程序本身輸出的日誌。正則表達式
在Web日誌中,每條日誌一般表明着用戶的一次訪問行爲,例以下面就是一條典型的apache日誌:算法
211.87.152.44 – - [18/Mar/2005:12:21:42 +0800] 「GET / HTTP/1.1″ 200 899 「http://www.baidu.com/」 「Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Maxthon)」數據庫
從上面這條日誌中,咱們能夠獲得不少有用的信息,例如訪問者的IP、訪問的時間、訪問的目標網頁、來源的地址以及訪問者所使用的客戶端的UserAgent信息等。若是須要更多的信息,則要用其它手段去獲取:例如想獲得用戶屏幕的分辨率,通常須要使用js代碼單獨發送請求;而若是想獲得諸如用戶訪問的具體新聞標題等信息,則可能須要Web應用程序在本身的代碼裏輸出。apache
爲何要分析日誌
毫無疑問,Web日誌中包含了大量人們——主要是產品分析人員會感興趣的信息,最簡單的,咱們能夠從中獲取網站每類頁面的PV值(PageView,頁面訪問量)、獨立IP數(即去重以後的IP數量)等;稍微複雜一些的,能夠計算得出用戶所檢索的關鍵詞排行榜、用戶停留時間最高的頁面等;更復雜的,構建廣告點擊模型、分析用戶行爲特徵等等。編程
既然這些數據是如此的有用,那麼固然已經有無數現成的工具能夠幫助咱們來分析它們,例如awstats、Webalizer,都是專門用於統計分析Web服務器日誌的免費程序。tomcat
另外還有一類產品,它們不分析直接日誌,而是經過讓用戶在頁面中嵌入js代碼的方式來直接進行數據統計,或者說咱們能夠認爲它是直接讓日誌輸出到了它們的服務器。典型的表明產品——大名鼎鼎的Google Analytics,另外還有國內的cnzz、百度統計等。服務器
不少人可能會說,既然如此,咱們爲何還須要本身來分析日誌,有必要嗎?固然有。咱們的用戶(產品分析人員)需求是無窮盡的,上面說的這幾類工具雖然很好很強大,但顯然沒辦法知足所有的需求。併發
不管是本地分析的工具,仍是在線的分析服務,它們雖然提很豐富的的統計分析功能,能夠作必定程度的配置,可是依然頗有限的。要進行稍複雜點的分析,或者要作基於日誌的數據挖掘,依然須要本身來完成。app
另外絕大多很多天志分析工具都是隻能用於單機的,數據量稍大就沒轍了。同時那些提供在線分析的服務對於單個站點一般也都有最大流量的限制——這是很容易理解的,他們也須要考慮服務器的負載。
因此,不少時候仍是得靠本身。
怎麼進行日誌分析
這並非一個簡單的問題。即便咱們把「日誌」限定爲Web日誌,依然包含了成千上萬種可能的格式和數據,而是「分析」更是難以定義,也許是簡單的統計值的計算,也許是複雜的數據挖掘算法。
下面並不打算討論這些複雜的問題,而只是籠統的討論如何構建進行日誌分析工做的基礎。有了這些基礎會讓基於日誌的簡單統計分析變得很簡單,並讓複雜的分析挖掘等變得可行。
少許數據的狀況
先考慮最簡單的狀況,在數據規模比較小的時候,也許是幾十MB、幾百MB或者幾十GB,總之就是在單機處理尚能忍受的時候。一切都很好辦,現成的各類Unix/Linux工具——awk、grep、sort、join等都是日誌分析的利器,若是僅僅是想知道某個頁面的PV,一個wc+grep就能搞定。若是有稍複雜的邏輯,那就使用各類腳本語言,尤爲是perl,配合偉大的正則表達式,基本就能夠解決全部的問題。
例如,咱們想從上面提到的apache日誌中獲得訪問量最高前100個IP,實現很簡單:
cat logfile | awk ‘{a[$1]++} END {for(b in a) print b」\t」a[b]}’|sort -k2 -r|head -n 100
不過當咱們須要頻繁去分析日誌的時候,上面的作法在一段時間以後可能就會讓咱們頭疼如何進行各類日誌文件、用於分析的腳本文件、crontab文件等等的維護,而且可能會存在大量重複的代碼來作數據格式的解析和清洗,這個時候也許就須要更合適的東西,好比——數據庫。
固然,要使用數據庫來進行日誌分析仍是須要一些代價的,最主要的就是如何將各類異構的日誌文件導入的數據庫中——這個過程一般稱爲ETL(Extraction-Transformation-Loading)。幸虧依然有各類現成的開源、免費的工具來幫助咱們作這件事情,而且在日誌種類不太多的時候,本身寫幾個簡單的腳原本完成這項工做也並不困難。例如能夠將上面的日誌去掉沒必要要的字段,而後導入以下的數據庫中:
如今須要考慮一下用什麼數據庫來存儲這些數據。MySQL是一個很經典的開源數據庫,它的傳統引擎(MyISAM或者InnoDB,行存儲)也許並不很是的適合日誌數據的存儲,可是在小數據量的時候仍是很夠用的。並且,在這方面如今已經有了更好的選擇,例如開源且免費的Infobright、Infinidb,都是專門爲數據倉庫應用而進行了優化的數據引擎,採用列存儲,有良好的數據壓縮,處理幾百GB的數據基本上不是問題。
使用數據庫的好處之一就是,偉大的SQL能夠幫咱們很簡單的完成絕大部分的統計分析工做——PV只須要SELECT+COUNT,計算搜索詞排行只須要SELECT+COUNT+GROUP+ORDER+LIMIT。此外,數據庫自己的結構化存儲模式也讓日誌數據的管理變的更簡單,減小運維代價。
一樣仍是上面的那個例子,簡單的一個SQL就能夠搞定:
SELECT * FROM (SELECT ip, COUNT(*) AS ip_count FROM apache_log GROUP BY ip) a ORDER BY ip_count DESC LIMIT 100
至於性能問題,數據庫的索引和各類優化機制一般會讓咱們的統計分析工做變得更快,而且上面提到的Infobright和Infinidb都專門爲相似SUM、COUNt之類的彙集應用作了優化。固然也不是絕對的會快,例如在數據庫中進行LIKE操做,一般會比grep一個文件還要慢不少。
更進一步的,使用基於數據庫的存儲,能夠很容易的進行OLAP(聯機分析處理)應用,從日誌中挖掘價值會變的更加簡單。
更多的數據怎麼辦
一個好的數據庫彷佛會讓事情變的很簡單,可是別忘了前面提到的都是單機數據庫。一臺單機在存儲容量、併發性上毫無疑問都是有很大限制的。而日誌數據的特色之一就是隨時間持續增加,而且因爲不少分析過程每每須要歷史數據。短期內的增加也許能夠經過分庫、分表或者數據壓縮等來解決,不過很顯然並非長久之計。
想要完全解決數據規模增加帶來的問題,很天然的會想到使用分佈式技術,結合上面的結論,也許使用某個分佈式數據庫是一個好選擇,那麼對最終用戶就能夠徹底透明瞭。這個的確是很理想的狀況,不過現實每每是殘酷的。
首先,實現比較完美的分佈式數據庫(受限於CAP原則)是一個很是複雜的問題,所以在這裏並不像單機數據庫那樣,有那麼多開源的好東西能夠用,甚至於商用的也並非太多。固然,也並不是絕對,若是有錢,仍是能夠考慮一下Oracle RAC、Greenplum之類東西。
其次,絕大多數分佈式數據庫都是NoSQL的,因此想繼續用上SQL的那些優勢基本上是沒期望,取而代之的都是一些簡單、難以使用的接口。單從這點看來,使用這些數據庫的價值已經下降不少了。
因此,仍是先現實一點,先退一步考慮如何解決的超大規模的日誌的分析問題,而不是想如何讓它變的像在小數據規模時那樣簡單。單單想作到這點,目前看來並非太難,而且依然有免費的午飯能夠吃。
Hadoop是偉大的Apache基金會下面的一套分佈式系統,包括分佈式文件系統(HDFS)、MapReduce計算框架、HBase等不少組件——這些基本都是Google的GFS/MapReduce/BigTable的克隆產品。
Hadoop通過數年的發展,目前已經很成熟了,尤爲是其中的HDFS和MapReduce計算框架組件。數百臺機器的集羣已經被證實可使用,能夠承擔PB級別的數據。
Hadoop項目中的HBase是一個按列存儲的NoSQL分佈式數據庫,它提供的功能和接口都很是簡單,只能進行簡單的K-V查詢,所以並不直接適用於大多很多天志分析應用。因此通常使用Hadoop來作日誌分析,首先仍是須要將日誌存儲在HDFS中,而後再使用它提供的MapReduce API編寫日誌分析程序。
MapReduce是一種分佈式編程模型,並不難學習,可是很顯然使用它來處理日誌的代價依然遠大於單機腳本或者SQL。一個簡單的詞頻統計計算可能都須要上百代碼——SQL只須要一行,另外還有複雜的環境準備和啓動腳本。
例如一樣仍是上面的例子,實現就要複雜的多,一般須要兩輪MapReduce來完成。首先要在第一輪的mapper中計算部分ip的訪問次數之和,並以ip爲key輸出:
//遍歷輸入,並聚合結果
foreach(record in input) {
ip = record.ip;
dict[ip]++;
}
//用emit輸出,第一個參數爲key,用於reduce的分發
foreach(<ip, count> in dict) {
emit(ip, count);
}
而後在第一輪的reduce中就能夠獲得每一個ip完整的計數,能夠順便排個序,而且只保留前100個。
count = 0;
//對於每一個key(ip),遍歷全部的values(count),並累加
while(input.values.hasNext()) {
count += input.values.next();
}
//插入到大小爲100的堆中
heap_insert(input.key, count);
在reduce結束的時候輸出:
//輸出當前reduce中count最高的100個ip
foreach(<ip, count> in dict) {
emit(ip, count);
}
因爲reduce通常會有不少個,因此最後還須要將全部reduce的輸出進行合併、再排序,並獲得最終的前100個IP以及對應的訪問量。
因此,使用Hadoop來作日誌分析很顯然不是一件簡單事情,它帶來了不少的額外的學習和運維成本,可是至少,它讓超大規模的日誌分析變成了可能。
怎樣變得更簡單
在超大規模的數據上作任何事情都不是一件容易的事情,包括日誌分析,但也並非說分佈式的日誌分析就必定要去寫MapReduce代碼,老是能夠去作進一步的抽象,在特定的應用下讓事情變得更簡單。
也許有人會很天然的想到若是能用SQL來操做Hadoop上的數據該有多好。事實上,不只僅只有你一我的會這麼想,不少人都這麼想,而且他們實現了這個想法,因而就有了Hive。
Hive如今也是Hadoop項目下面的一個子項目,它可讓咱們用SQL的接口來執行MapReduce,甚至提供了JDBC和ODBC的接口。有了這個以後,Hadoop基本上被包裝成一個數據庫。固然實際上Hive的SQL最終仍是被翻譯成了MapReduce代碼來執行,所以即便最簡單的SQL可能也要執行好幾十秒。幸虧在一般的離線日誌分析中,這個時間仍是能夠接受的。更重要的是,對於上面提到的例子,咱們又能夠用同樣的SQL來完成分析任務了。
固然Hive並非徹底的兼容SQL語法,並且也不能作到徹底的對用戶屏蔽細節。不少時候爲了執行性能的優化,依然須要用戶去了解一些MapReduce的基本知識,根據本身的應用模式來設置一些參數,不然咱們可能會發現一個查詢執行很慢,或者壓根執行不出來。
另外,很顯然Hive也並不能覆蓋全部的需求,因此它依然保留插入原始MapReduce代碼的接口,以便擴展。
更多的問題
即便有了Hive這樣一個相似於數據庫的東西,咱們依然還有不少事情須要作。例如時間久了,可能會有愈來愈多的須要例行執行的SQL,而這些SQL中,也許有一些是作了重複的事情;也許有一些的執行效率很是低下,一個複雜的SQL就佔滿了全部的計算資源。這樣的系統會變得愈來愈難以維護的,直到有一天例行的SQL終於跑不完了。而最終用戶每每不會去關心這些事情,他們只關心本身提交的查詢是否是能即時獲得響應,怎麼樣才能儘快的拿到結果。
舉個簡單的例子,若是發如今使用apache_log的全部查詢中,幾乎沒有人用其中的user_agent字段,那麼咱們徹底能夠把這個字段去除掉,或者拆分紅兩張表,以減小多數查詢的IO時間,提升執行的效率。
爲了系統化的解決這些問題,咱們可能須要引入例行任務的調度機制,可能須要去分析全部的SQL來發現哪些是能夠合併的、哪些的性能須要優化,使用的數據表是否是須要作水平或者垂直分表等等。根據實際狀況的不一樣,這時事情多是人工來完成,也多是寫程序來自動分析並調整。
再者隨着日誌類型、分析需求的不斷增加。用戶會愈來愈多的抱怨很難找到想要的數據在哪份日誌裏,或者跑的好好的查詢由於日誌格式的變化而忽然不能用了。另外上面提到的ETL過程也會變得複雜,簡單的轉換導入腳本極可能已經解決不了問題。這時候可能須要構建一個數據管理系統,或者乾脆考慮創建一個所謂的數據倉庫。
總之,隨着日誌數據量、日誌類型、用戶數量、分析需求等等的不斷增加,愈來愈多的問題會逐漸浮現出來,日誌分析這件事情可能就再也不像咱們最初想的那麼簡單,會變得愈來愈有價值,也愈來愈有挑戰。