漫談千億級資料優化實踐:一次資料優化實錄
即使沒有資料傾斜,千億級的資料查詢對於系統也是一種巨大負擔,對於資料開發來說,如何來優化它,既是挑戰,也是機遇!
本文將分享千億級資料優化的一點:如何使用資料。
注意:
- 本文會限定一些業務場景和技術架構,因此解決方法會侷限於此。很多問題可以通過換架構或者引入新的元件來解決,但是成本可能會很高,因此暫不考慮。
- 本文不是一篇Hive使用和優化文件,更側重於梳理筆者的思路,讓大家少走些坑。
文章主題
在流行的大資料領域中,Hive絕對佔據了很大的一片天地,不管是資料倉庫和資料分析,還是資料探勘和機器學習,凡是需要和大資料量打交道的童鞋們,基本上都要接觸hive。因此,本文將側重於千億級資料在Hive中的使用,並通過一個典型的資料使用難題來總結一些在大規模資料場景下的優化方式。
本文主要以一個具體的使用場景為切入點,為了解決該場景下的使用難題,筆者經歷了一次次的嘗試+失敗,最終找到了一種相對比較合適的方式。
文章結構
本文可以看過是一種記錄和思考,完全還原筆者在遇到問題時的解決方式。因此全文會以事情的發展為主線,每次嘗試一種解決方法,失敗後繼續查詢新的方法,中間會穿插一些技術細節。
文章主線如下:
- 明確使用場景和困難。
- 如何解決,這是一個不斷推翻重來的過程。
- 回顧總結。
問題來了!
本章作用主要有二:
- 明確業務背景和使用場景
- 明確困難所在
1. 業務背景和使用場景
按照慣例,我又來到了一家電商網站來工作,我們有一張十分重要的表:使用者和購買過的商品表。
如下圖,該表只有三個欄位,分別是使用者ID、商品ID。我們可以簡單地理解這是一張事實表。對事實表沒印象的可以參考這篇《漫談資料倉庫之維度建模》。
我們暫且不管當初為何這樣設計的,現在的情況就是:
這張表有哪些使用場景呢:
- 輸入一批使用者,找到和他們有相似購買行為的使用者
- 統計使用者購買商品的資料區間
- ……
總之這張表能用到的地方是極多的,相信資料分析和資料探勘的童鞋們肯定能想到很多場景,這裡就不展開講了。
問題來了: 資料量太大,隨便一個查詢就是五六個小時,有沒有辦法優化?
2. 困難
先說明一下問題在哪。
資料量大
這張表裡面儲存了我站來自全球的50億使用者和他們購買過的商品,粗略估計一下,人均會購買60件商品,也就是說這張表有 3000億 的資料。
3千億條記錄是什麼概念呢,如果存成沒有壓縮的txt檔案的話,大致有30T以上。如果做一個壓縮,我們保守一點估計,要有接近10T的資料。
查詢速度慢
這麼大資料量,查詢起來的確比較慢。可能隨便跑一個數據,就要3到5個小時。
我們可以大致地分析一下慢的原因:
- 掃描資料量大
- join的時候時間長
- 因為我們的reduce數量右限制,每個reduce需要處理的資料量太多
- shuffle的時候效率太低。
解決過程
我們在解決這個難題的時候是圍繞一些出發點的:
- 減少掃描的資料量
- 加快關聯查詢的速度
1. 分割槽
第一個思路就是分割槽,我們可以根據使用者的賬號分佈來進行分割槽,然後在掃描的時候,只掃描部分分割槽就行。 好,我們做一個設計。
我們美好的願望是:假設有一個需求需要查詢一定的使用者的購買記錄,我們不用掃描全量的資料,只掃描其中一部分即可。
下面我們基於幾個設定來設計我們的分割槽規則:
假設我們的使用者id都是數字型別的,如下圖。
- 我們按照賬號的id來設計分割槽函式,比如說前四位相同的放在一個分割槽中。
- 寫入資料,和查詢資料使用相同的分割槽函式
這樣我們就有了1萬個分割槽,每個分割槽中有30萬用戶的購買記錄,也就是說每個分割槽中會有1800萬的記錄數,總計約1G的檔案大小。
下面就是我們設計出來的分割槽。
我們的想法是好的,下面舉幾個場景:
- 比如現在需要查100個使用者的資料,不分割槽的話,我們需要掃描全量的資料,現在我們可能只要掃描10個分割槽,最多100個分割槽,也就是我們的速度回提升100倍以上。
- 需要查1萬個使用者的資料,我們假設會命中1000個分割槽。
- 需要查10萬個使用者的資料,我們假設會命中5000個分割槽。
例子我都舉不下去了,實際情況是,如果使用者分佈比較分散的話,超過20萬個使用者的話,基本上就命中了所有了分割槽了。 這個感興趣的可以測一下。
增加分割槽數?
這個方案是可以的,比如我們變成10萬個分割槽,這樣當然可以,但是讓需要查詢的使用者多的話,效果照樣變弱,而且更多的分割槽意味著每個分割槽的資料會變少,這樣小檔案就會多很多。
結論
分割槽的方式不靠譜!
2. 索引
注意: Hive的索引也是個坑,怪不得沒人用,但是我們還是要設計一下。
基於“減少掃描的資料量”這點來講,索引是一種極妙的方式,有了索引,我們就不必全量掃描所有的資料,速度肯定就快了呀。 但是, Hive的索引是個坑。
下面講一下Hive索引的機制就明白了。
Hive索引機制
在指定列上建立索引,會產生一張索引表(Hive的一張物理表),裡面的欄位包括,索引列的值、該值對應的HDFS檔案路徑、該值在檔案中的偏移量。
如下,是Hive的索引表。其中,索引表中key欄位,就是原表中key欄位的值,_bucketname 欄位,代表資料檔案對應的HDFS檔案路徑,_offsets 代表該key值在檔案中的偏移量,有可能有多個偏移量,因此,該欄位型別為陣列。
在執行索引欄位查詢時候,首先額外生成一個MR job,根據對索引列的過濾條件,從索引表中過濾出索引列的值對應的hdfs檔案路徑及偏移量,輸出到hdfs上的一個檔案中,然後根據這些檔案中的hdfs路徑和偏移量,篩選原始input檔案,生成新的split,作為整個job的split,這樣就達到不用全表掃描的目的。
注意: 按照上面的說明,我們的索引其實就是另一張Hive的表,而且資料量還是很大。 下面從兩個點說明Hive的索引方案不能用。
- 經過測試,索引表就有4、5T,我們在查詢的時候,要先和這張索引表做關聯,然後再和原表做關聯,損失太大了。
- HDFS檔案系統的設計問題。會導致最終我們掃描的還是全表。為什麼?下面講解。
HDFS的設計
我們預設大家對HDFS原理有所認知。這裡只說一下這次我們優化的內容。
假設我們10T的資料,按照128M一個檔案塊,那就是我們有七八萬個檔案塊。和前面的分割槽的情況類似,當需要查詢的使用者數量到一定程度,基本上還是要掃描所有的檔案塊。
結論
索引的方式不靠譜,至少Hive中不可用。
索引的使用方式,就不再描述了,看官網還是挺簡單的:Hive官網:Index。
3. 分桶
分桶就不再說了,和前面說的問題類似,也不可用。
到這裡我就絕望了,有打算不解決了。準備用最初的一招:按活躍度區分。
4. 區分活躍使用者
這也是一種很值得考慮的方式,因為我們大部分對資料的使用都會考慮活躍使用者,這裡我們把30億使用者中活躍的10億的使用者抽出來放一個分割槽,這樣的話,我們的查詢效率能提高3倍左右。
問題
- 活躍使用者不好定義,每個業務方的定義不一樣。
- 執行成本太大,跑這個資料挺耗時間。
結論
這是一種方法,如果沒有更好的方法就用這個了。
5. 資料結構
受大神的指點,我們更換了一種在Hive中的儲存方式,現在更新表如下:
這是一個很簡單的轉表,我們使用了Hive中的資料結構Array,把一個使用者的所有購買過的商品放入到一個欄位中,這樣的話,我們的總資料量就只有30億了,在做關聯查詢的時候速度必然很快。
實踐
經過實踐,這樣的儲存,只需佔用之前儲存量的1/2左右,也就是隻需要不到5T的大小,查詢速度從平均一個任務4個小時縮減到半個小時。
問題
這裡有兩個問題:
- 資料更新,資料結構的改變導致在更新資料的時候有一些障礙,這點不再展開,方法總是有的,筆者是保留了兩份資料,這一份專門供查詢用。
- 使用成本增加,因為資料結構變了,相應的查詢sql也要調整。 這裡就需要用到
lateral view explode
,詳情可看官網。
總結
總的來接,這種方式還是可行的,目前的反饋還都是正向的,額外的sql複雜度不是很大,大部分童鞋都能接收。
總結
本文主要是描述了一次千億級資料量查詢優化的過程,回頭來看,其實也聽簡單的,但是身在其中未必能想清楚,這也和經驗有關,因此走了很多的彎路。
總的來講,筆者嘗試了5種方式:分割槽、索引、分桶、活躍度區分、新的資料結構。最後一種方式基本解決了遇到的問題。
本文對一些技術細節沒有過多描述,比如建索引,建表,這些在官網很容易找到,因此不再過多涉及。思想到位了,其它的問題都不大。
參考
- https://cwiki.apache.org//confluence/display/Hive/LanguageManual+Indexing
- https://cwiki.apache.org/confluence/display/Hive/LanguageManual+LateralView
- http://lxw1234.com/archives/2015/05/207.htm