折騰了很久总寻,被領(lǐng)導(dǎo)天天督促&指點(diǎn)枕面,算是有個(gè)最基本的性能優(yōu)化。
1. 背景介紹:
Hive使用hive-hbase-handler建立HBase external table。在hive查詢包含count(*)徒坡、join、以及Predicate Pushdown等操作時(shí)瘤缩,會(huì)調(diào)用MapReduce進(jìn)行處理喇完。本文旨在查詢性能方面的優(yōu)化,算是對(duì)工作中的一點(diǎn)記錄剥啤。
優(yōu)化主要分為兩個(gè)方面:
- HBase預(yù)分區(qū)以及hive–hbase-storage-handler的實(shí)現(xiàn)锦溪。
- HBase參數(shù)調(diào)優(yōu)。
2. 一些基本知識(shí):
- 對(duì)Map過程的基本理解:Map是將原始數(shù)據(jù)拆分成split府怯,根據(jù)split啟動(dòng)Mapper刻诊。
- Hadoop有兩套API,一套是org.apache.hadoop.mapred牺丙,一套是org.apache.hadoop.mapreduce坏逢。前者是舊API,特點(diǎn)是底層基本類是接口赘被,實(shí)現(xiàn)類需implements interface是整,而后者是新API,底層基本類是抽象類民假,實(shí)現(xiàn)類需extends abstractClass浮入。
- hive的hive-storage-handler,使用的是舊mapred API羊异。在handler中事秀,需指定實(shí)現(xiàn)org.apache.hadoop.mapred.InputFormat 接口。
3. org.apache.hadoop.mapred.InputFormat詳解
簡(jiǎn)單來說野舶,InputFormat 主要用于描述輸入數(shù)據(jù)的格式易迹,提供了以下兩個(gè)功能:
- 數(shù)據(jù)切分,按照某個(gè)策略將輸入數(shù)據(jù)且分成若干個(gè) split平道,以便確定 Map Task 的個(gè)數(shù)即 Mapper 的個(gè)數(shù)睹欲,在 MapReduce 框架中,一個(gè) split 就意味著需要一個(gè) Map Task;
- 為 Mapper 提供輸入數(shù)據(jù)一屋,即給定一個(gè) split(使用其中的 RecordReader 對(duì)象)將之解析為一個(gè)個(gè)的 key/value 鍵值對(duì)窘疮。
該類接口定義如下:
public interface InputFormat<K,V>{
public InputSplit[] getSplits(JobConf job,int numSplits) throws IOException;
public RecordReader<K,V> getRecordReader(InputSplit split,JobConf job,Reporter reporter) throws IOException;
}
其中,getSplit() 方法主要用于切分?jǐn)?shù)據(jù)冀墨,每一份數(shù)據(jù)由闸衫,split 只是在邏輯上對(duì)數(shù)據(jù)分片,并不會(huì)在磁盤上將數(shù)據(jù)切分成 split 物理分片诽嘉,實(shí)際上數(shù)據(jù)在 HDFS 上還是以 block 為基本單位來存儲(chǔ)數(shù)據(jù)的蔚出。InputSplit 只記錄了 Mapper 要處理的數(shù)據(jù)的元數(shù)據(jù)信息弟翘,如起始位置、長度和所在的節(jié)點(diǎn)骄酗。
4. HBase預(yù)分區(qū)
在HBase Java API中稀余,創(chuàng)建HBase table是可以指定TableDescriptor的。該TableDescriptor類似于一種預(yù)分區(qū)策略酥筝。默認(rèn)地,如果沒有指定TableDescriptor來創(chuàng)建一張表時(shí)雏门,只有一個(gè)region,正處于混沌時(shí)期嘿歌,start-end key無邊界,可謂海納百川。什么樣的rowKey都可以接受茁影,然而宙帝,當(dāng)數(shù)據(jù)越來越多,region的size越來越大時(shí)募闲,大到一定的閥值步脓,hbase認(rèn)為再往這個(gè)region里塞數(shù)據(jù)已經(jīng)不合適了,就會(huì)找到一個(gè)midKey將region一分為二浩螺,成為2個(gè)region,這個(gè)過程稱為分裂(region-split).而midKey則為這二個(gè)region的臨界靴患,左為N無下界,右為M無上界要出。< midKey則為陰被塞到N區(qū)鸳君,> midKey則會(huì)被塞到M區(qū)。
TableDescriptor是一個(gè)byte[][]數(shù)組患蹂,其中每一個(gè)byte[]相當(dāng)于split key或颊,如指定了63個(gè)split key,就會(huì)分成64個(gè)分區(qū)传于。預(yù)分區(qū)是不會(huì)被HBase Compact所合并的囱挑。
由于HBase是字典排序,所以如果要將表數(shù)據(jù)分散到預(yù)分區(qū)中沼溜,需要在rowkey指定一個(gè)prefix并保證盡量分散平挑。常見的散列設(shè)計(jì)如hash或mod都是可以的。
5. 基于預(yù)分區(qū)的并發(fā)mapper設(shè)計(jì)
在HBase中系草,不同的RegionServer管理著不同的Region弹惦,我們希望能并發(fā)scan所有的region以達(dá)到并行化。由于mapper本身是并行的悄但,所以只需在split上做文章棠隐,也就是改寫getSplits方法。具體做法是:
- 實(shí)現(xiàn)InputSplit接口檐嚣,編寫一個(gè)Split對(duì)象類助泽,在默認(rèn)hive-hbase-handler中已有實(shí)現(xiàn)啰扛。
- 拿到table的region list。
- 遍歷list嗡贺,獲取每一個(gè)region的startKey和endKey隐解。
- 將二者寫入繼承的Split對(duì)象類。有多少region就有多少split诫睬,并且在Split對(duì)象類的readFields()方法中根據(jù)startKey和endKey讀取煞茫,write方法類似。
這樣recordReader就會(huì)讀取設(shè)置的每一個(gè)split摄凡。具體代碼不做贅述续徽,只提供思路。
6. 參數(shù)調(diào)優(yōu)
由于hive查詢hbase的handler亲澡,底層依舊是用HBase的scan實(shí)現(xiàn)的钦扭,所以可以對(duì)HBase client端進(jìn)行參數(shù)調(diào)優(yōu)。比較有用的如:
hbase.scan.cache床绪,可以在集群管理中配置客情,也可以由client端的scan自行設(shè)置:scan.setCaching(),默認(rèn)是1癞己,設(shè)置大些可為一次scan拿回更多數(shù)據(jù)膀斋,減少網(wǎng)絡(luò)I/O。
另有server段參數(shù)如:
hbase.ipc.server.read.threadpool.size痹雅。默認(rèn)值 10概页,Reader 網(wǎng)絡(luò) IO 個(gè)數(shù),reader 的個(gè)數(shù)決定了從網(wǎng)絡(luò) io 里讀取數(shù)據(jù)的速度也就是網(wǎng)絡(luò)I/O练慕。
同樣也可以設(shè)置server端cache大卸璩住(表級(jí)別),減少磁盤I/O铃将。
經(jīng)測(cè)試项鬼,實(shí)現(xiàn)上述優(yōu)化,在我所能訪問到的集群里劲阎,count 一千萬數(shù)據(jù)绘盟,能夠從最初的5分鐘降至最低48秒。