一、 hive 簡介
hive 是一個(gè)基于 hadoop 的開源數(shù)據(jù)倉庫工具,用于存儲(chǔ)和處理海量結(jié)構(gòu)化數(shù)據(jù)。 它把海量數(shù)據(jù)存儲(chǔ)于 hadoop 文件系統(tǒng),而不是數(shù)據(jù)庫,但提供了一套類數(shù)據(jù)庫的數(shù)據(jù)存儲(chǔ)和處理機(jī)制,并采用 HQL (類 SQL )語言對(duì)這些數(shù)據(jù)進(jìn)行自動(dòng)化管理和處理。我們可以把 hive 中海量結(jié)構(gòu)化數(shù)據(jù)看成一個(gè)個(gè)的表,而實(shí)際上這些數(shù)據(jù)是分布式存儲(chǔ)在 HDFS 中的。 Hive 經(jīng)過對(duì)語句進(jìn)行解析和轉(zhuǎn)換,最終生成一系列基于 hadoop 的 map/reduce 任務(wù),通過執(zhí)行這些任務(wù)完成數(shù)據(jù)處理。
Hive 誕生于 facebook 的日志分析需求,面對(duì)海量的結(jié)構(gòu)化數(shù)據(jù), hive 以較低的成本完成了以往需要大規(guī)模數(shù)據(jù)庫才能完成的任務(wù),并且學(xué)習(xí)門檻相對(duì)較低,應(yīng)用開發(fā)靈活而高效。
Hive 自 2009.4.29 發(fā)布第一個(gè)官方穩(wěn)定版 0.3.0 至今,不過一年的時(shí)間,正在慢慢完善,網(wǎng)上能找到的相關(guān)資料相當(dāng)少,尤其中文資料更少,本文結(jié)合業(yè)務(wù)對(duì) hive 的應(yīng)用做了一些探索,并把這些經(jīng)驗(yàn)做一個(gè)總結(jié),所謂前車之鑒,希望讀者能少走一些彎路。
Hive 的官方 wiki 請參考這里 :
http://wiki.apache.org/hadoop/Hive
官方主頁在這里:
http://hadoop.apache.org/hive/
hive-0.5.0 源碼包和二進(jìn)制發(fā)布包的下載地址
http://labs.renren.com/apache-mirror/hadoop/hive/hive-0.5.0/
二、 部署
由于 Hive 是基于 hadoop 的工具,所以 hive 的部署需要一個(gè)正常運(yùn)行的 hadoop 環(huán)境。以下介紹 hive 的簡單部署和應(yīng)用。
部署環(huán)境:
操作系統(tǒng): Red Hat Enterprise Linux AS release 4 (Nahant Update 7)
Hadoop : hadoop-0.20.2 ,正常運(yùn)行
部署步驟如下:
1、 下載最新版本發(fā)布包 hive-0.5.0-dev.tar.gz ,傳到 hadoop 的 namenode 節(jié)點(diǎn)上,解壓得到 hive 目錄。假設(shè)路徑為: /opt/hadoop/hive-0.5.0-bin
2、 設(shè)置環(huán)境變量 HIVE_HOME ,指向 hive 根目錄 /opt/hadoop/hive-0.5.0-bin 。由于 hadoop 已運(yùn)行,檢查環(huán)境變量 JAVA_HOME 和 HADOOP_HOME 是否正確有效。
3、 切換到 $HIVE_HOME 目錄, hive 配置默認(rèn)即可,運(yùn)行 bin/hive 即可啟動(dòng) hive ,如果正常啟動(dòng),將會(huì)出現(xiàn)“ hive> ”提示符。
4、 在命令提示符中輸入“ show tables; ”,如果正常運(yùn)行,說明已部署成功,可供使用。
常見問題:
1、 執(zhí)行“ show tables; ”命令提示“ FAILED: Error in metadata: java.lang.IllegalArgumentException: URI: does not have a scheme ”,這是由于 hive 找不到存放元數(shù)據(jù)庫的數(shù)據(jù)庫而導(dǎo)致的,修改 conf/ hive-default.xml 配置文件中的 hive.metastore.local 為 true 即可。由于 hive 把結(jié)構(gòu)化數(shù)據(jù)的元數(shù)據(jù)信息放在第三方數(shù)據(jù)庫,此處設(shè)置為 true , hive 將在本地創(chuàng)建 derby 數(shù)據(jù)庫用于存放元數(shù)據(jù)。當(dāng)然如果有需要也可以采用 mysql 等第三方數(shù)據(jù)庫存放元數(shù)據(jù),不過這時(shí) hive.metastore.local 的配置值應(yīng)為 false 。
2、 如果你已有一套 nutch1.0 系統(tǒng)正在跑,而你不想單獨(dú)再去部署一套 hadoop 環(huán)境,你可以直接使用 nutch1.0 自帶的 hadoop 環(huán)境,但這樣的部署會(huì)導(dǎo)致 hive 不能正常運(yùn)行,提示找不到某些方法。這是由于 nutch1.0 使用了 commons-lang-2.1.jar 這個(gè)包,而 hive 需要的是 commons-lang-2.4.jar ,下載一個(gè) 2.4 版本的包替換掉 2.1 即可, nutch 和 hive 都能正常運(yùn)行。
三、 應(yīng)用場景
本文主要講述使用 hive 的實(shí)踐,業(yè)務(wù)不是關(guān)鍵,簡要介紹業(yè)務(wù)場景,本次的任務(wù)是對(duì)搜索日志數(shù)據(jù)進(jìn)行統(tǒng)計(jì)分析。
集團(tuán)搜索剛上線不久,日志量并不大 。這些日志分布在 5 臺(tái)前端機(jī),按小時(shí)保存,并以小時(shí)為周期定時(shí)將上一小時(shí)產(chǎn)生的數(shù)據(jù)同步到日志分析機(jī),統(tǒng)計(jì)數(shù)據(jù)要求按小時(shí)更新。這些統(tǒng)計(jì)項(xiàng),包括關(guān)鍵詞搜索量 pv ,類別訪問量,每秒訪問量 tps 等等。
基于 hive ,我們將這些數(shù)據(jù)按天為單位建表,每天一個(gè)表,后臺(tái)腳本根據(jù)時(shí)間戳將每小時(shí)同步過來的 5 臺(tái)前端機(jī)的日志數(shù)據(jù)合并成一個(gè)日志文件,導(dǎo)入 hive 系統(tǒng),每小時(shí)同步的日志數(shù)據(jù)被追加到當(dāng)天數(shù)據(jù)表中,導(dǎo)入完成后,當(dāng)天各項(xiàng)統(tǒng)計(jì)項(xiàng)將被重新計(jì)算并輸出統(tǒng)計(jì)結(jié)果。
以上需求若直接基于 hadoop 開發(fā),需要自行管理數(shù)據(jù),針對(duì)多個(gè)統(tǒng)計(jì)需求開發(fā)不同的 map/reduce 運(yùn)算任務(wù),對(duì)合并、排序等多項(xiàng)操作進(jìn)行定制,并檢測任務(wù)運(yùn)行狀態(tài),工作量并不小。但使用 hive ,從導(dǎo)入到分析、排序、去重、結(jié)果輸出,這些操作都可以運(yùn)用 hql 語句來解決,一條語句經(jīng)過處理被解析成幾個(gè)任務(wù)來運(yùn)行,即使是關(guān)鍵詞訪問量增量這種需要同時(shí)訪問多天數(shù)據(jù)的較為復(fù)雜的需求也能通過表關(guān)聯(lián)這樣的語句自動(dòng)完成,節(jié)省了大量工作量。
四、 Hive 實(shí)戰(zhàn)
初次使用 hive ,應(yīng)該說上手還是挺快的。 Hive 提供的類 SQL 語句與 mysql 語句極為相似,語法上有大量相同的地方,這給我們上手帶來了很大的方便,但是要得心應(yīng)手地寫好這些語句,還需要對(duì) hive 有較好的了解,才能結(jié)合 hive 特色寫出精妙的語句。
關(guān)于 hive 語言的詳細(xì)語法可參考官方 wiki 的語言手冊 :
http://wiki.apache.org/hadoop/Hive/LanguageManual
雖然語法風(fēng)格為我們提供了便利,但初次使用遇到的問題還是不少的,下面針對(duì)業(yè)務(wù)場景談?wù)勎覀冇龅降膯栴},和對(duì) hive 功能的定制。
1、 分隔符問題
首先遇到的是日志數(shù)據(jù)的分隔符問題,我們的日志數(shù)據(jù)的大致格式如下:
2010-05-24 00:00:02@$_$@QQ2010@$_$@all@$_$@NOKIA_1681C@$_$@1@$_$@10@$_$@@$_$@-1@$_$@10@$_$@application@$_$@1
從格式可見其分隔符是“ @$_$@ ”,這是為了盡可能防止日志正文出現(xiàn)與分隔符相同的字符而導(dǎo)致數(shù)據(jù)混淆。本來 hive支持在建表的時(shí)候指定自定義分隔符的,但經(jīng)過多次測試發(fā)現(xiàn)只支持單個(gè)字符的自定義分隔符,像“ @$_$@ ”這樣的分隔符是不能被支持的,但是我們可以通過對(duì)分隔符的定制解決這個(gè)問題, hive 的內(nèi)部分隔符是“ \001 ”,只要把分隔符替換成“\001 ”即可。
經(jīng)過探索我們發(fā)現(xiàn)有兩條途徑解決這個(gè)問題。
a) 自定義 outputformat 和 inputformat 。
Hive 的 outputformat/inputformat 與 hadoop 的 outputformat/inputformat 相當(dāng)類似, inputformat 負(fù)責(zé)把輸入數(shù)據(jù)進(jìn)行格式化,然后提供給 hive , outputformat 負(fù)責(zé)把 hive 輸出的數(shù)據(jù)重新格式化成目標(biāo)格式再輸出到文件,這種對(duì)格式進(jìn)行定制的方式較為底層,對(duì)其進(jìn)行定制也相對(duì)簡單,重寫 InputFormat 中 RecordReader 類中的 next 方法即可,示例代碼如下:
public boolean next(LongWritable key, BytesWritable value)
throws IOException {
while ( reader .next(key, text ) ) {
String strReplace = text .toString().toLowerCase().replace( "@$_$@" , "\001" );
Text txtReplace = new Text();
txtReplace.set(strReplace );
value.set(txtReplace.getBytes(), 0, txtReplace.getLength());
return true ;
}
return false ;
}
重寫 HiveIgnoreKeyTextOutputFormat 中 RecordWriter 中的 write 方法,示例代碼如下:
public void write (Writable w) throws IOException {
String strReplace = ((Text)w).toString().replace( "\001" , "@$_$@" );
Text txtReplace = new Text();
txtReplace.set(strReplace);
byte [] output = txtReplace.getBytes();
bytesWritable .set(output, 0, output. length );
writer .write( bytesWritable );
}
自定義 outputformat/inputformat 后,在建表時(shí)需要指定 outputformat/inputformat ,如下示例:
stored as INPUTFORMAT 'com.aspire.search.loganalysis.hive.SearchLogInputFormat' OUTPUTFORMAT 'com.aspire.search.loganalysis.hive.SearchLogOutputFormat'
b) 通過 SerDe(serialize/deserialize) ,在數(shù)據(jù)序列化和反序列化時(shí)格式化數(shù)據(jù)。
這種方式稍微復(fù)雜一點(diǎn),對(duì)數(shù)據(jù)的控制能力也要弱一些,它使用正則表達(dá)式來匹配和處理數(shù)據(jù),性能也會(huì)有所影響。但它的優(yōu)點(diǎn)是可以自定義表屬性信息 SERDEPROPERTIES ,在 SerDe 中通過這些屬性信息可以有更多的定制行為。
2、 數(shù)據(jù)導(dǎo)入導(dǎo)出
a) 多版本日志格式的兼容
由于 hive 的應(yīng)用場景主要是處理冷數(shù)據(jù)(只讀不寫),因此它只支持批量導(dǎo)入和導(dǎo)出數(shù)據(jù),并不支持單條數(shù)據(jù)的寫入或更新,所以如果要導(dǎo)入的數(shù)據(jù)存在某些不太規(guī)范的行,則需要我們定制一些擴(kuò)展功能對(duì)其進(jìn)行處理。
我們需要處理的日志數(shù)據(jù)存在多個(gè)版本,各個(gè)版本每個(gè)字段的數(shù)據(jù)內(nèi)容存在一些差異,可能版本 A 日志數(shù)據(jù)的第二個(gè)列是搜索關(guān)鍵字,但版本 B 的第二列卻是搜索的終端類型,如果這兩個(gè)版本的日志直接導(dǎo)入 hive 中,很明顯數(shù)據(jù)將會(huì)混亂,統(tǒng)計(jì)結(jié)果也不會(huì)正確。我們的任務(wù)是要使多個(gè)版本的日志數(shù)據(jù)能在 hive 數(shù)據(jù)倉庫中共存,且表的 input/output 操作能夠最終映射到正確的日志版本的正確字段。
這里我們不關(guān)心這部分繁瑣的工作,只關(guān)心技術(shù)實(shí)現(xiàn)的關(guān)鍵點(diǎn),這個(gè)功能該在哪里實(shí)現(xiàn)才能讓 hive 認(rèn)得這些不同格式的數(shù)據(jù)呢?經(jīng)過多方嘗試,在中間任何環(huán)節(jié)做這個(gè)版本適配都將導(dǎo)致復(fù)雜化,最終這個(gè)工作還是在 inputformat/outputformat 中完成最為優(yōu)雅,畢竟 inputformat 是源頭, outputformat 是最終歸宿。具體來說,是在前面提到的 inputformat 的 next 方法中和在 outputformat 的 write 方法中完成這個(gè)適配工作。
b) Hive 操作本地?cái)?shù)據(jù)
一開始,總是把本地?cái)?shù)據(jù)先傳到 HDFS ,再由 hive 操作 hdfs 上的數(shù)據(jù),然后再把數(shù)據(jù)從 HDFS 上傳回本地?cái)?shù)據(jù)。后來發(fā)現(xiàn)大可不必如此, hive 語句都提供了“ local ”關(guān)鍵字,支持直接從本地導(dǎo)入數(shù)據(jù)到 hive ,也能從 hive 直接導(dǎo)出數(shù)據(jù)到本地,不過其內(nèi)部計(jì)算時(shí)當(dāng)然是用 HDFS 上的數(shù)據(jù),只是自動(dòng)為我們完成導(dǎo)入導(dǎo)出而已。
3、 數(shù)據(jù)處理
日志數(shù)據(jù)的統(tǒng)計(jì)處理在這里反倒沒有什么特別之處,就是一些 SQL 語句而已,也沒有什么高深的技巧,不過還是列舉一些語句示例,以示 hive 處理數(shù)據(jù)的方便之處,并展示 hive 的一些用法。
a) 為 hive 添加用戶定制功能,自定義功能都位于 hive_contrib.jar 包中
add jar /opt/hadoop/hive-0.5.0-bin/lib/hive_contrib.jar;
b) 統(tǒng)計(jì)每個(gè)關(guān)鍵詞的搜索量,并按搜索量降序排列,然后把結(jié)果存入表 keyword_20100603 中
create table keyword_20100603 as select keyword,count(keyword) as count from searchlog_20100603 group by keyword order by count desc;
c) 統(tǒng)計(jì)每類用戶終端的搜索量,并按搜索量降序排列,然后把結(jié)果存入表 device_20100603 中
create table device_20100603 as select device,count(device) as count from searchlog_20100603 group by device order by count desc;
d) 創(chuàng)建表 time_20100603 ,使用自定義的 INPUTFORMAT 和 OUTPUTFORMAT ,并指定表數(shù)據(jù)的真實(shí)存放位置在 '/LogAnalysis/results/time_20100603' ( HDFS 路徑),而不是放在 hive 自己的數(shù)據(jù)目錄中
create external table if not exists time_20100603(time string, count int) stored as INPUTFORMAT 'com.aspire.search.loganalysis.hive.XmlResultInputFormat' OUTPUTFORMAT 'com.aspire.search.loganalysis.hive.XmlResultOutputFormat' LOCATION '/LogAnalysis/results/time_20100603';
e) 統(tǒng)計(jì)每秒訪問量 TPS ,按訪問量降序排列,并把結(jié)果輸出到表 time_20100603 中,這個(gè)表我們在上面剛剛定義過,其真實(shí)位置在 '/LogAnalysis/results/time_20100603' ,并且由于 XmlResultOutputFormat 的格式化,文件內(nèi)容是 XML 格式。
insert overwrite table time_20100603 select time,count(time) as count from searchlog_20100603 group by time order by count desc;
f) 計(jì)算每個(gè)搜索請求響應(yīng)時(shí)間的最大值,最小值和平均值
insert overwrite table response_20100603 select max(responsetime) as max,min(responsetime) as min,avg(responsetime) as avg from searchlog_20100603;
g) 創(chuàng)建一個(gè)表用于存放今天與昨天的關(guān)鍵詞搜索量和增量及其增量比率,表數(shù)據(jù)位于 '/LogAnalysis/results/keyword_20100604_20100603' ,內(nèi)容將是 XML 格式。
create external table if not exists keyword_20100604_20100603(keyword string, count int, increment int, incrementrate double) stored as INPUTFORMAT 'com.aspire.search.loganalysis.hive.XmlResultInputFormat' OUTPUTFORMAT 'com.aspire.search.loganalysis.hive.XmlResultOutputFormat' LOCATION '/LogAnalysis/results/keyword_20100604_20100603';
h) 設(shè)置表的屬性,以便 XmlResultInputFormat 和 XmlResultOutputFormat 能根據(jù) output.resulttype 的不同內(nèi)容輸出不同格式的 XML 文件。
alter table keyword_20100604_20100603 set tblproperties ('output.resulttype'='keyword');
i) 關(guān)聯(lián)今天關(guān)鍵詞統(tǒng)計(jì)結(jié)果表( keyword_20100604 )與昨天關(guān)鍵詞統(tǒng)計(jì)結(jié)果表( keyword_20100603 ),統(tǒng)計(jì)今天與昨天同時(shí)出現(xiàn)的關(guān)鍵詞的搜索次數(shù),今天相對(duì)昨天的增量和增量比率,并按增量比率降序排列,結(jié)果輸出到剛剛定義的 keyword_20100604_20100603 表中,其數(shù)據(jù)文件內(nèi)容將為 XML 格式。
insert overwrite table keyword_20100604_20100603 select cur.keyword, cur.count, cur.count-yes.count as increment, (cur.count-yes.count)/yes.count as incrementrate from keyword_20100604 cur join keyword_20100603 yes on (cur.keyword = yes.keyword) order by incrementrate desc;
j)
4、 用戶自定義函數(shù) UDF
部分統(tǒng)計(jì)結(jié)果需要以 CSV 的格式輸出,對(duì)于這類文件體全是有效內(nèi)容的文件,不需要像 XML 一樣包含 version , encoding 等信息的文件頭,最適合用 UDF(user define function) 了。
UDF 函數(shù)可直接應(yīng)用于 select 語句,對(duì)查詢結(jié)構(gòu)做格式化處理之后,再輸出內(nèi)容。自定義 UDF 需要繼承 org.apache.hadoop.hive.ql.exec.UDF ,并實(shí)現(xiàn) evaluate 函數(shù), Evaluate 函數(shù)支持重載,還支持可變參數(shù)。我們實(shí)現(xiàn)了一個(gè)支持可變字符串參數(shù)的 UDF ,支持把 select 得出的任意個(gè)數(shù)的不同類型數(shù)據(jù)轉(zhuǎn)換為字符串后,按 CSV 格式輸出,由于代碼較簡單,這里給出源碼示例:
public String evaluate(String... strs) {
StringBuilder sb = new StringBuilder();
for ( int i = 0; i < strs. length ; i++) {
sb.append(ConvertCSVField(strs[i])).append( ',' );
}
sb.deleteCharAt(sb.length()-1);
return sb.toString();
}
需要注意的是,要使用 UDF 功能,除了實(shí)現(xiàn)自定義 UDF 外,還需要加入包含 UDF 的包,示例:
add jar /opt/hadoop/hive-0.5.0-bin/lib/hive_contrib.jar;
然后創(chuàng)建臨時(shí)方法,示例:
CREATE TEMPORARY FUNCTION Result2CSv AS ‘com.aspire.search.loganalysis.hive. Result2CSv';
使用完畢還要 drop 方法,示例:
DROP TEMPORARY FUNCTION Result2CSv;
5、 輸出 XML 格式的統(tǒng)計(jì)結(jié)果
前面看到部分日志統(tǒng)計(jì)結(jié)果輸出到一個(gè)表中,借助 XmlResultInputFormat 和 XmlResultOutputFormat 格式化成 XML 文件,考慮到創(chuàng)建這個(gè)表只是為了得到 XML 格式的輸出數(shù)據(jù),我們只需實(shí)現(xiàn) XmlResultOutputFormat 即可,如果還要支持 select 查詢,則我們還需要實(shí)現(xiàn) XmlResultInputFormat ,這里我們只介紹 XmlResultOutputFormat 。
前面介紹過,定制 XmlResultOutputFormat 我們只需重寫 write 即可,這個(gè)方法將會(huì)把 hive 的以 ’\001’ 分隔的多字段數(shù)據(jù)格式化為我們需要的 XML 格式,被簡化的示例代碼如下:
public void write(Writable w) throws IOException {
String[] strFields = ((Text) w).toString().split( "\001" );
StringBuffer sbXml = new StringBuffer();
if ( strResultType .equals( "keyword" )) {
sbXml.append( "<record><keyword>" ).append(strFields[0]).append(
"</keyword><count>" ).append(strFields[1]).append( "</count><increment>" ).append(strFields[2]).append(
"</increment><rate>" ).append(strFields[3]).append(
"</rate></result>" );
}
Text txtXml = new Text();
byte [] strBytes = sbXml.toString().getBytes( "utf-8" );
txtXml.set(strBytes, 0, strBytes. length );
byte [] output = txtXml.getBytes();
bytesWritable .set(output, 0, output. length );
writer .write( bytesWritable );
}
其中的 strResultType .equals( "keyword" ) 指定關(guān)鍵詞統(tǒng)計(jì)結(jié)果,這個(gè)屬性來自以下語句對(duì)結(jié)果類型的指定,通過這個(gè)屬性我們還可以用同一個(gè) outputformat 輸出多種類型的結(jié)果。
alter table keyword_20100604_20100603 set tblproperties ('output.resulttype'='keyword');
仔細(xì)看看 write 函數(shù)的實(shí)現(xiàn)便可發(fā)現(xiàn),其實(shí)這里只輸出了 XML 文件的正文,而 XML 的文件頭和結(jié)束標(biāo)簽在哪里輸出呢?所幸我們采用的是基于 outputformat 的實(shí)現(xiàn),我們可以在構(gòu)造函數(shù)輸出 version , encoding 等文件頭信息,在 close() 方法中輸出結(jié)束標(biāo)簽。
這也是我們?yōu)槭裁床皇褂?/span> UDF 來輸出結(jié)果的原因,自定義 UDF 函數(shù)不能輸出文件頭和文件尾,對(duì)于 XML 格式的數(shù)據(jù)無法輸出完整格式,只能輸出 CSV 這類所有行都是有效數(shù)據(jù)的文件。
五、 總結(jié)
Hive 是一個(gè)可擴(kuò)展性極強(qiáng)的數(shù)據(jù)倉庫工具,借助于 hadoop 分布式存儲(chǔ)計(jì)算平臺(tái)和 hive 對(duì) SQL 語句的理解能力,我們所要做的大部分工作就是輸入和輸出數(shù)據(jù)的適配,恰恰這兩部分 IO 格式是千變?nèi)f化的,我們只需要定制我們自己的輸入輸出適配器, hive將為我們透明化存儲(chǔ)和處理這些數(shù)據(jù),大大簡化我們的工作。本文的重心也正在于此,這部分工作相信每一個(gè)做數(shù)據(jù)分析的朋友都會(huì)面對(duì)的,希望對(duì)您有益。
本文介紹了一次相當(dāng)簡單的基于 hive 的日志統(tǒng)計(jì)實(shí)戰(zhàn),對(duì) hive 的運(yùn)用還處于一個(gè)相對(duì)較淺的層面,目前尚能滿足需求。對(duì)于一些較復(fù)雜的數(shù)據(jù)分析任務(wù),以上所介紹的經(jīng)驗(yàn)很可能是不夠用的,甚至是 hive 做不到的, hive 還有很多進(jìn)階功能,限于篇幅本文未能涉及,待日后結(jié)合具體任務(wù)再詳細(xì)闡述。
如您對(duì)本文有任何建議或指教,請?jiān)u論,謝謝。