天天看點

日志系統之HBase日志存儲設計優化簡介基于HBase自建索引的缺陷HBase存儲日志的查詢優化

本人部落格文章如未特别注明皆為原創!如有轉載請注明出處:http://blog.csdn.net/yanghua_kobe/article/details/46482319

繼續談論最近接手的日志系統,上篇關于日志收集相關的内容,這篇我們談談日志存儲相關的話題。

我們首先來總結一下日志這種資料的業務特點:它幾乎沒有更新的需求,一個元件或一個系統通常有一個固定的日志格式,但就多個元件或系統而言它會存在各種五花八門的自定義的tag,這些tag建立的目的通常是為了後期查詢/排查線上問題的需要,是以日志的檢索字段也靈活多變。

我們的日志存儲選擇是HBase,這主要是因為我們認為HBase的如下特點非常适合日志資料:

(1)HBase的qualifier相當靈活,可以動态建立,非常适合日志這種tag不固定的半結構化資料(這裡的靈活性主要針對tag的存儲)

(2)HBase歸屬于Hadoop生态體系,友善後面做離線分析、資料挖掘

結合上面我們提到的日志資料的特點,由于tag靈活多變,是以對基于tag的查詢HBase顯得有些力不從心。這主要是因為HBase本身并不提供二級索引,你無法基于Column進行搜尋。如果無法确定rowKey或rowKey的範圍并且也沒有輔助索引,那麼将不得不進行全表掃描。從這一點上來看,你可以将其看作是一個Key-Value形式的資料庫(比如redis)。

因為HBase自身不提供二級索引機制,是以很常見的做法是在外部自己建構索引,我在接手日志系統時的實作就是這麼做的。基本思路是日志存儲在日志表,人為建構基于tag的索引資訊存入索引中繼資料表,中繼資料表中一條索引資訊對應一個索引表,在索引表中利用Column-Family的橫向擴充來存儲日志的rowKey。總結如下:

(1)log表:存儲日志記錄

(2)meta表:存儲索引中繼資料(其中包含動态索引表的表名稱)

(3)動态index表:存儲索引的具體資訊,一個索引對應一張表

下面我們來看一下這幾張表的Schema設計:

日志系統之HBase日志存儲設計優化簡介基于HBase自建索引的缺陷HBase存儲日志的查詢優化

這裡我可以談談原先建立動态索引表的大緻邏輯,它需要三個參數:

(1)indexName:索引名稱

(2)tags:需要為其建立索引的tag數組

(3)span:時間間隔

先将tags數組轉換為(fast-json#toJSONBytes)byte[]并将其作為rowKey,在meta表中檢查是否已存在為該tag組合建構的索引名。(HBase識别的rowKey格式是byte[]形式的,meta表的rowKey即為tags的json數組序列化為byte[]後的表示形式)。

如果該索引的中繼資料不存在,則建立動态索引表,該索引表的表名為indexName。

而索引表的rowKey對象的設計包含了兩個屬性:

(1)time:日志的“準”記錄時間,注意此處不是真實的記錄時間,而是間接真實事件的一個時間點(timestamp / span *span)

(2)tags:tag的字元串數組

然後對log(日志表)進行全表掃描,對每一條日志記錄進行如下操作:

(1)擷取日志産生的時間

(2)然後内部存在一個周遊tags的循環,對每一個tag:判斷該條日志是否存在該tag,如果不存在則直接跳出該循環,如果tag都能比對上tags裡的每一條,則才為其建立索引

(3)如果需要建立索引,則往索引表内添加一條資料

總得來說,這裡建立的索引就是比對tags集合以及時間分片,将滿足條件的日志向最靠近它的時間點聚攏。

(1)索引表的建立效率很低,需要一個兩層的嵌套循環,最外層做的事情是全表掃描,如果資料量龐大後,這種處理方式很難被接受。其實,這種方式類似于資料已在,事後補償的機制。而通常的做法是:索引表建立時隻是個空表,資料入庫時動态分析其是否有必要建構索引(這句的後半句原來也有實作,是通過storm實作的)

(2)通過索引進行查找的時候,還需要兩層循環,外層是查找動态索引表裡的行集合,内層是擷取列簇裡所有的日志表裡相關記錄的rowKey。如果查詢的時間範圍比較長或者時間分片的間隔比較端,那麼時間點會非常多,而時間點一多,外層循環次數将會非常多,是以為了避免這一點,實作時做了時間片段限制,也就是片段不能大于一定的範圍;如果該時間片的日志非常密集,那麼這些日志就都會落到該時間點上,那麼内層循環次數将會非常多。

(3)查詢的效率将非常依賴于索引建立的健全程度,這種情況下建立索引的tag集合必須小而全,如果大而廣,那麼建構索引的條件比對度就會變低。如果沒有針對要查詢的tag的索引資訊,将不得不進行全表掃描。

(4)日志表ID采用的是分布式自增ID,其他表用的是json對象的字元串形式,沒有注意rowKey對HBase查詢的重要性。

産生這些問題的原因是自建索引的實作方式,我們必須對日志系統的查詢進行優化,在此之前我們首先要對HBase的查詢有一些基本的了解。通路HBase的行記錄有以下三種方式:

(1)通過rowKey作唯一比對

(2)通過rowKey的range比對一個範圍,然後通過多種filter在範圍内篩選

(3)全表掃描

從程式設計角度來看,HBase的查詢實作隻支援兩種方式:

(1)get:指定rowkey擷取唯一一條記錄

(2)scan:按指定的條件獲得一批記錄(它包含了上面的2,3兩種方式)

通常情況下,全表掃描很少是我們期望的做法。是以我們如果我們想提升查詢效率,必須精心設計rowKey。

從上面自建索引産生的問題以及我們對HBase查詢的基本了解。問題主要有兩個方面:

(1)自建索引的實作方式不夠高效

(2)沒有對rowKey進行良好的設計(日志記錄的ID采用分布式自增ID)

下面我們針對這兩點來談談優化政策。

rowKey在這裡絕對不能像傳統的RDBMS處理主鍵那樣,簡單地用UUID或自增ID來處理。HBase的rowKey是基于字典排序的,具體來說是基于key的ASCII碼來排序,我們的思路是要往rowKey中加入我們想要查詢的條件因子,通過多個因子互相組合,來一步步确定查找範圍。比如時間肯定是我們應該加到rowKey裡的一個查詢因子,一個開始時間跟一個截止時間就形成了一個時間段範圍,就能固定一個結果集範圍。

你很容易看出來rowkey裡加入的查詢因子越多,查詢範圍定位的精确度越高。但查詢因子其實是從衆多日志中抽象而來(比如host,level,timestamp等),這要求它們是每條日志記錄中共性的東西,就我們目前的日志系統而言,大緻劃分為兩種日志類型:

(1)定格式的業務系統/架構日志(比如業務架構/web app等)

(2)不定格式的技術系統/元件/架構日志(比如nginx、redis、RabbitMQ等)

針對定格式的日志,我們的rowKey的規則是:

日志系統之HBase日志存儲設計優化簡介基于HBase自建索引的缺陷HBase存儲日志的查詢優化

針對不定格式的日志,我們的rowKey規則是:

日志系統之HBase日志存儲設計優化簡介基于HBase自建索引的缺陷HBase存儲日志的查詢優化

因為各種技術元件的日志格式多樣,導緻我們無法從中解析出時間,是以這裡我們選擇日志的收集時間作為鑒别時間戳。這裡我們隻能假設:整個日志系統一直都良好運轉,也就是說日志産生時間給收集時間相近。但毫無疑問這樣的假設有時是不準确的,但我們不會以真實的時間作為基準,因為這種類型的日志是通過離線批處理進行解析後重新轉存的,是以最終還是會得出精确的日志時間戳。

rowkey最好被設計為定長的,而且最好将rowkey的每個分段都轉化成純數字或純字母這種很容易轉化為ASCII碼并且容易人為設定最大值與最小值的形式。舉例來說:假如前面幾位都固定,最後三位是不定的,如果是數字,那麼區間的範圍會在XXXXX000-XXXXX999之間。

通常我們想加入rowKey的查詢因子,其值不為數字或者字母是很正常的,這時我們可以通過碼表來映射,比如上面我們針對AppLog的logLevel因子就是通過碼表來進行映射的,目前我們用兩位數來映射可能存在的level。

在通過rowKey的範圍确定對結果集的掃描範圍之後,下一步就是通過内置的filter來進行更精确的篩選,HBase預設提供多種filter供使用者針對rowKey、column-family、qualifier等進行篩選。當然如果rowKey的篩選條件取值跨度比較大,還會産生接近類似于全表掃描的影響。我們能做的事情就隻剩下對查詢條件進行限制了,比如:

(1)查詢時間區間的跨度隻能限制在一定的範圍

(2)分頁給出查詢結果

既然索引是優化查詢非常關鍵的一環,是以建索引的思路是沒有問題的。但是,無論如何自建索引還是需要精心設計rowKey,不管是資料表的rowKey還是索引表的rowKey。有時為了查詢效率,甚至會固定某段rowKey的前幾位,并讓其代表的資料落在同一個region中。精心設計rowKey的原因,還是因為HBase的查詢特征:你獲得的rowKey範圍越精确,查找的速度越快。

通常情況下,索引表建立時不應該進行全表掃描,但我們應該對日志表的每條資料進行處理來生成最終的索引資料。在我們現在的系統中,是通過storm進行分析、插入的。這裡我們上storm的目的也不是為了做這件事,最主要的目的是實時過去logLevel為Error的日志,并做到準實時通知。那麼問題來了,如果我們不存在這個需求,我們是不是為了要計算索引而要上一個storm叢集?答案是:大可不必。

其實這裡主要是在往HBase裡插入資料時,獲得一個hock(鈎子)或者說callback來攔截每條資料,分析是否應該将其rowKey加入索引表中去。HBase在0.92版本之後提供了一個稱之為協處理器(coprocessor)的技術,允許裡編寫運作在HBase Server上的代碼攔截資料,協處理器大緻分為兩類:

(1)Observer(類比于RDBMS中的觸發器)

(2)EndPoint(類比于RDBMS中的存儲過程)

我們可以通過Observer來攔截日志記錄,并加入代碼處理邏輯來為其建構索引。由于介紹HBase技術細節不是本文的重點,是以這裡就提及一下,如果後面有機會,再來繼續探讨。

這裡有一張整體架構模式圖:

日志系統之HBase日志存儲設計優化簡介基于HBase自建索引的缺陷HBase存儲日志的查詢優化
日志系統之HBase日志存儲設計優化簡介基于HBase自建索引的缺陷HBase存儲日志的查詢優化