Hive 快速入門

Hive 快速入門
作者:鳴宇淳

前言

我寫這篇文章的目的是儘可能全面地對Hive進行入門介紹,這篇文章是基於hive-1.0.0版本介紹的,這個版本的Hive是運行在MapReduce上的,新的版本可以運行在Tez上,會有一些不同。

Hive是對數據倉庫進行管理和分析數據的工具。但是大家不要被“數據倉庫”這個詞所嚇倒,數據倉庫是很複雜的東西,但是如果你會MYSQL或者MSSQL,就會發現Hive是那麼的簡單,簡單到甚至不用學就可以使用Hive做出業務所需要的東西。

但是Hive和MYSQL畢竟不同,執行原理、優化方法,底層架構都完全不相同。 大數據離線分析使用Hive已經成爲主流,基於工作中Hive使用的經驗,我整理了這個入門級別的文章,希望能給想入門的同學提供一些幫助。

一、Hive簡介

Facebook爲了解決海量日誌數據的分析而開發了Hive,後來開源給了Apache軟件基金會。

官網定義:

The Apache Hive ™ data warehouse software facilitates reading, writing, and managing large datasets residing in distributed storage using SQL.

Hive是一種用類SQL語句來協助讀寫、管理那些存儲在分佈式存儲系統上大數據集的數據倉庫軟件。

Hive的幾個特點

  • Hive最大的特點是通過類SQL來分析大數據,而避免了寫MapReduce程序來分析數據,這樣使得分析數據更容易。

  • 數據是存儲在HDFS上的,Hive本身並不提供數據的存儲功能

  • Hive是將數據映射成數據庫和一張張的表,庫和表的元數據信息一般存在關係型數據庫上(比如MySQL)。

  • 數據存儲方面:它能夠存儲很大的數據集,並且對數據完整性、格式要求並不嚴格。

  • 數據處理方面:因爲Hive語句最終會生成MapReduce任務去計算,所以不適用於實時計算的場景,它適用於離線分析。

二、Hive架構

Hive的核心

Hive的核心是驅動引擎,驅動引擎由四部分組成:

  • 解釋器:解釋器的作用是將HiveSQL語句轉換爲語法樹(AST)。

  • 編譯器:編譯器是將語法樹編譯爲邏輯執行計劃。

  • 優化器:優化器是對邏輯執行計劃進行優化。

  • 執行器:執行器是調用底層的運行框架執行邏輯執行計劃。

Hive的底層存儲

Hive的數據是存儲在HDFS上的。Hive中的庫和表可以看作是對HDFS上數據做的一個映射。所以Hive必須是運行在一個Hadoop集羣上的。

Hive語句的執行過程

Hive中的執行器,是將最終要執行的MapReduce程序放到YARN上以一系列Job的方式去執行。

Hive的元數據存儲

Hive的元數據是一般是存儲在MySQL這種關係型數據庫上的,Hive和MySQL之間通過MetaStore服務交互。

元數據項 說明
Owner 庫、表的所屬者
LastAccessTime 最後修改時間
Table Type 表類型(內部表、外部表)
CreateTime 創建時間
Location 存儲位置
  表的字段信息

Hive客戶端

Hive有很多種客戶端。

  • cli命令行客戶端:採用交互窗口,用hive命令行和Hive進行通信。

  • HiveServer2客戶端:用Thrift協議進行通信,Thrift是不同語言之間的轉換器,是連接不同語言程序間的協議,通過JDBC或者ODBC去訪問Hive。

  • HWI客戶端:hive自帶的一個客戶端,但是比較粗糙,一般不用。

  • HUE客戶端:通過Web頁面來和Hive進行交互,使用的比較多。

三、基本數據類型

Hive支持關係型數據中大多數基本數據類型,同時Hive中也有特有的三種複雜類型。

下面的表列出了Hive中的常用基本數據類型:

數據類型 長度 備註
Tinyint 1字節的有符號整數 -128~127
SmallInt 1個字節的有符號整數 -32768~32767
Int 4個字節的有符號整數 -2147483648 ~ 2147483647
BigInt 8個字節的有符號整數  
Boolean 布爾類型,true或者false true、false
Float 單精度浮點數  
Double 雙精度浮點數  
String 字符串  
TimeStamp 整數 支持Unix timestamp,可以達到納秒精度
Binary 字節數組  
Date 日期 0000-01-01 ~ 9999-12-31,常用String代替
--- --- ---

四、DDL語法

創建數據庫

創建一個數據庫會在HDFS上創建一個目錄,Hive裏數據庫的概念類似於程序中的命名空間,用數據庫來組織表,在大量Hive的情況下,用數據庫來分開可以避免表名衝突。Hive默認的數據庫是default。

創建數據庫例子:

hive> create database if not exists user_db;

查看數據庫定義

Describe 命令來查看數據庫定義,包括:數據庫名稱、數據庫在HDFS目錄、HDFS用戶名稱。

hive> describe database user_db;
OK
user_db        hdfs://bigdata-51cdh.chybinmy.com:8020/user/hive/warehouse/user_db.db    hadoop    USER

user_db是數據庫名稱。

hdfs://bigdata-51cdh.chybinmy.com:8020/user/hive/warehouse/user_db.db 是user_db庫對應的存儲數據的HDFS上的根目錄。

查看數據庫列表

hive> show databases;   
OK      
user_db 
default

刪除數據庫

刪除數據庫時,如果庫中存在數據表,是不能刪除的,要先刪除所有表,再刪除數據庫。添加上cascade後,就可以先自動刪除所有表後,再刪除數據庫。(友情提示:慎用啊!)刪除數據庫後,HDFS上數據庫對應的目錄就被刪除掉了。

hive> drop database if exists testdb cascade;

切換當前數據庫

hive> use user_db;

創建普通表

hive> create table if not exists userinfo  
    > (
    >   userid int,
    >   username string,
    >   cityid int,
    >   createtime date    
    > )
    > row format delimited fields terminated by '\t'
    > stored as textfile;
    OK
    Time taken: 2.133 seconds

以上例子是創建表的一種方式,如果表不存在,就創建表userinfo。row format delimited fields terminated by '\t' 是指定列之間的分隔符;stored as textfile是指定文件存儲格式爲textfile。

創建表一般有幾種方式:

  • create table 方式:以上例子中的方式。

  • create table as select 方式:根據查詢的結果自動創建表,並將查詢結果數據插入新建的表中。

  • create table like tablename1 方式:是克隆表,只複製tablename1表的結構。複製表和克隆表會在下面的Hive數據管理部分詳細講解。

創建外部表

外部表是沒有被hive完全控制的表,當表刪除後,數據不會被刪除。

hive> create external table iislog_ext (
    >  ip string,
    >  logtime string    
    > )
    > ;

創建分區表

Hive查詢一般是掃描整個目錄,但是有時候我們關心的數據只是集中在某一部分數據上,比如我們一個Hive查詢,往往是隻是查詢某一天的數據,這樣的情況下,可以使用分區表來優化,一天是一個分區,查詢時候,Hive只掃描指定天分區的數據。

普通表和分區表的區別在於:一個Hive表在HDFS上是有一個對應的目錄來存儲數據,普通表的數據直接存儲在這個目錄下,而分區表數據存儲時,是再劃分子目錄來存儲的。一個分區一個子目錄。主要作用是來優化查詢性能。

--創建經銷商操作日誌表
create table user_action_log
(
companyId INT comment   '公司ID',
userid INT comment   '銷售ID',
originalstring STRING comment   'url', 
host STRING comment   'host',
absolutepath STRING comment   '絕對路徑',
query STRING comment   '參數串',
refurl STRING comment   '來源url',
clientip STRING comment   '客戶端Ip',
cookiemd5 STRING comment   'cookiemd5',
timestamp STRING comment   '訪問時間戳'
)
partitioned by (dt string)
row format delimited fields terminated by ','
stored as textfile;

這個例子中,這個日誌表以dt字段分區,dt是個虛擬的字段,dt下並不存儲數據,而是用來分區的,實際數據存儲時,dt字段值相同的數據存入同一個子目錄中,插入數據或者導入數據時,同一天的數據dt字段賦值一樣,這樣就實現了數據按dt日期分區存儲。

當Hive查詢數據時,如果指定了dt篩選條件,那麼只需要到對應的分區下去檢索數據即可,大大提高了效率。所以對於分區表查詢時,儘量添加上分區字段的篩選條件。

創建桶表

桶表也是一種用於優化查詢而設計的表類型。創建通表時,指定桶的個數、分桶的依據字段,hive就可以自動將數據分桶存儲。查詢時只需要遍歷一個桶裏的數據,或者遍歷部分桶,這樣就提高了查詢效率。舉例:

------創建訂單表
create table user_leads
(
leads_id string,
user_id string,
user_id string,
user_phone string,
user_name string,
create_time string
)
clustered by (user_id) sorted by(leads_id) into 10 buckets 
row format delimited fields terminated by '\t' 
stored as textfile;

對這個例子的說明:

  • clustered by是指根據user_id的值進行哈希後模除分桶個數,根據得到的結果,確定這行數據分入哪個桶中,這樣的分法,可以確保相同user_id的數據放入同一個桶中。而經銷商的訂單數據,大部分是根據user_id進行查詢的。這樣大部分情況下是只需要查詢一個桶中的數據就可以了。

  • sorted by 是指定桶中的數據以哪個字段進行排序,排序的好處是,在join操作時能獲得很高的效率。

  • into 10 buckets是指定一共分10個桶。

  • 在HDFS上存儲時,一個桶存入一個文件中,這樣根據user_id進行查詢時,可以快速確定數據存在於哪個桶中,而只遍歷一個桶可以提供查詢效率。

分桶表讀寫過程

enter image description here

查看有哪些表

--查詢庫中表
show tables;
Show TABLES '*info';  --可以用正則表達式篩選要列出的表

查看錶定義

查看簡單定義:

describe userinfo;

查看錶詳細信息:

describe formatted userinfo;

執行結果如下所示:

備註 col_name data_type comment
列信息 # col_name data_type comment
  NULL NULL
userid int  
username string  
cityid int  
createtime date  
    NULL NULL
  # Detailed Table Information NULL NULL
所在庫 Database: user_db NULL
所屬HUE用戶 Owner: admin NULL
表創建時間 CreateTime: Tue Aug 16 06:05:14 PDT 2016 NULL
最後訪問時間 LastAccessTime: UNKNOWN NULL
  Protect Mode: None NULL
  Retention: 0 NULL
表數據文件在HDFS上路徑 Location: hdfs://bigdata-51cdh.chybinmy.com:8020/user/hive/warehouse/user_db.db/userinfo NULL
表類型(內部表或者外部表) Table Type: MANAGED_TABLE NULL
表分區信息 Table Parameters: NULL NULL
    transient_lastDdlTime 1471352714
    NULL NULL
  # Storage Information NULL NULL
序列化反序列化類 SerDe Library: org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe NULL
mapreduce中的輸入格式 InputFormat: org.apache.hadoop.mapred.TextInputFormat NULL
mapreduce中的輸出格式 OutputFormat: org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat NULL
壓縮 Compressed: No NULL
總佔用數據塊個數 Num Buckets: -1 NULL
  Bucket Columns: [] NULL
  Sort Columns: [] NULL
  Storage Desc Params: NULL NULL
    field.delim \t
    serialization.format \t
--- --- --- ---

修改表

對錶的修改操作有:修改表名、添加字段、修改字段。

修改表名:

--將表名從userinfo改爲user_info
alter table userinfo rename to user_info;

添加字段:

---在user_info表添加一個字段provinceid,int 類型
alter table user_info add columns (provinceid int );

修改字段:

alter table user_info replace columns (userid int,username string,cityid int,joindate date,provinceid int);

修改字段,只是修改了Hive表的元數據信息(元數據信息一般是存儲在MySql中),並不對存在於HDFS中的表數據做修改。

並不是所有的Hive表都可以修改字段,只有使用了native SerDe (序列化反序列化類型)的表才能修改字段

刪除表

--如果表存在,就刪除。
drop table if exists user_info;

五、DML語法

向Hive中加載數據

加載到普通表

可以將本地文本文件內容批量加載到Hive表中,要求文本文件中的格式和Hive表的定義一致,包括:字段個數、字段順序、列分隔符都要一致。

這裏的user_info表的表定義是以\t作爲列分隔符,所以準備好數據後,將文本文件拷貝到hive客戶端機器上後,執行加載命令。

load data local inpath '/home/hadoop/userinfodata.txt' overwrite into table user_info;
  • local關鍵字表示源數據文件在本地,源文件可以在HDFS上,如果在HDFS上,則去掉local,inpath後面的路徑是類似”hdfs://namenode:9000/user/datapath”這樣的HDFS上文件的路徑。

  • overwrite關鍵字表示如果hive表中存在數據,就會覆蓋掉原有的數據。如果省略overwrite,則默認是追加數據。

加載完成數據後,在HDFS上就會看到加載的數據文件。

加載到分區表

load data local inpath '/home/hadoop/actionlog.txt' overwrite into table user_action_log 
PARTITION (dt='2017-05-26');

partition 是指定這批數據放入分區2017-05-26中。

加載到分桶表

------先創建普通臨時表
create table user_leads_tmp
(
leads_id string,
user_id string,
user_id string,
user_phone string,
user_name string,
create_time string
)
row format delimited fields terminated by ',' 
stored as textfile;
------數據載入臨時表
load data local inpath '/home/hadoop/lead.txt' overwrite into table user_leads_tmp;
------導入分桶表
set hive.enforce.bucketing = true;
insert overwrite table user_leads select * from  user_leads_tmp;

set hive.enforce.bucketing = true; 這個配置非常關鍵,爲true就是設置爲啓用分桶。

導出數據

 --導出數據,是將hive表中的數據導出到本地文件中。
insert overwrite local directory '/home/hadoop/user_info.bak2016-08-22 '
select * from user_info;

去掉local關鍵字,也可以導出到HDFS上。

插入數據

insert select 語句

上一節分桶表數據導入,用到從user_leads_tmp表向user_leads表中導入數據,用到了insert數據。

insert overwrite table user_leads select * from  user_leads_tmp;

這裏是將查詢結果導入到表中,overwrite關鍵字是覆蓋目標表中的原來數據。如果缺省,就是追加數據。

如果是插入數據的表是分區表,那麼就如下所示:

insert overwrite table user_leads PARTITION (dt='2017-05-26') 
select * from  user_leads_tmp;

一次遍歷多次插入

from user_action_log
insert overwrite table log1 select companyid,originalstring  where companyid='100006'
insert overwrite table log2 select companyid,originalstring  where companyid='10002'

每次hive查詢,都會將數據集整個遍歷一遍。當查詢結果會插入多個表中時,可以採用以上語法,將一次遍歷寫入多個表,以達到提高效率的目的。

複製表

複製表是將源表的結構和數據複製並創建爲一個新表,複製過程中,可以對數據進行篩選,列可以進行刪減。

create table user_leads_bak
row format delimited fields terminated by '\t'
stored as textfile
as
select leads_id,user_id,'2016-08-22' as bakdate
from user_leads
where create_time<'2016-08-22';

上面這個例子是對user_leads表進行復製備份,複製時篩選了2016-08-22以前的數據,減少幾個列,並添加了一個bakdate列。

克隆表

克隆表時會克隆源表的所有元數據信息,但是不會複製源表的數據。

--克隆表user_leads,創建新表user_leads_like
create table user_leads_like like  user_leads;

備份表

備份是將表的元數據和數據都導出到HDFS上。

export table user_action_log partition (dt='2016-08-19')
to '/user/hive/action_log.export'

這個例子是將user_action_log表中的一個分區,備份到HDFS上,to後面的路徑是HDFS上的路徑。

還原表

將備份在HDFS上的文件,還原到user_action_log_like表中。

import table user_action_log_like from '/user/hive/action_log.export';

六、HQL語法

Select 查詢

指定列表

select * from user_leads;

select leads_id,user_id,create_time from user_leads;
select e.leads_id from user_leads e;

函數列

select companyid,upper(host),UUID(32) from user_action_log;

可以使用hive自帶的函數,也可以是使用用戶自定義函數。

上面這個例子upper()就是hive自帶函數,UUID()就是用戶自定義函數。

關於函數詳細介紹,可以參考後面的章節。

算數運算列

select companyid,userid, (companyid + userid) as sumint from user_action_log;

可以進行各種算數運算,運算結果做爲結果列。

運算符 描述 運算符 描述
A+B 數字相加 A-B 數字相減
A*B 相乘 A/B 相除
A%B 模除  

限制返回條數

類似於sql server裏的top N,或者mysql裏的limit。

select * from user_action_log limit 100;

Case When Then語句

---case when 兩種寫法
select case companyid when 0 then '未登錄' else companyid end from user_action_log;

select case  when companyid=0 then '未登錄' else companyid end from user_action_log;

這個例子,判斷companyid值,如果爲0則顯示爲未登錄,如果不爲0,則返回companyid的值。

Where篩選

操作符 說明 操作符 說明
A=B A等於B就返回true,適用於各種基本類型 A<=>B 都爲Null則返回True,其他和=一樣
A<>B 不等於 A!=B 不等於
A<B 小於 A<=B 小於等於
A>B 大於 A>=B 大於等於
A Between B And C 篩選A的值處於B和C之間 A Not Between B And C 篩選A的值不處於B和C之間
A Is NULL 篩選A是NULL的 A Is Not NULL 篩選A值不是NULL的
A Link B %一個或者多個字符_一個字符 A Not Like B %一個或者多個字符_一個字符
A RLike B 正則匹配    
--- --- --- ---

Group By 分組

Hive不支持having語句,有對group by 後的結果進行篩選的需求,可以先將篩選條件放入group by的結果中,然後在包一層,在外邊對條件進行篩選。

如果需要進行如下查詢:

SELECT col1 FROM t1 GROUP BY col1 HAVING SUM(col2) > 10
可以用下面這種方式實現:
SELECT col1 FROM 
(SELECT col1, SUM(col2) AS col2sum
       FROM t1 GROUP BY col1
) t2
WHERE t2.col2sum > 10

子查詢

Hive對子查詢的支持有限,只允許在 select from 後面出現。比如:

---只支持如下形式的子查詢
select * from (
  select userid,username from user_info i where i.userid='10595'
  ) a;

---不支持如下的子查詢
select 
(select username from user_info i where i.userid=d.user_id) 
from user_leads d where d.user_id='10595';

七、JOIN

Hive Join的限制

只支持等值連接

Hive支持類似SQL Server的大部分Join操作,但是注意只支持等值連接,並不支持不等連接。原因是Hive語句最終是要轉換爲MapReduce程序來執行的,但是MapReduce程序很難實現這種不等判斷的連接方式。

----等值連接
select lead.* from user_leads lead
left join user_info info 
on lead.user_id=info.userid;
---不等連接(不支持)
select lead.* from user_leads lead
left join user_info info 
on lead.user_id!=info.userid;

連接謂詞中不支持or

---on 後面的表達式不支持or
select lead.* from user_leads lead
left join user_info info 
on lead.user_id=info.userid or lead.leads_id=0;

Inner join

內連接同SQL Sever中的一樣,連接的兩個表中,只有同時滿足連接條件的記錄纔會放入結果表中。

Left join

同SQL Server中一樣,兩個表左連接時,符合Where條件的左側表的記錄都會被保留下來,而符合On條件的右側的表的記錄纔會被保留下來。

Right join

同Left Join相反,兩個表左連接時,符合Where條件的右側表的記錄都會被保留下來,而符合On條件的左側的表的記錄纔會被保留下來。

Full join

Full Join會將連接的兩個表中的記錄都保留下來。

Left Semi-Join ( exists 語句)

SQL Server中有exists語句,類似下面的語句,但是Hive中不支持 Exists語句。

--SQL Sever中的exists語句,但是hive中不支持
SELECT i.* FROM  userInfo i
WHERE EXISTS (SELECT 1 FROM userScopeRelation s WHERE s.userInfoId=i.userInfoID AND s.CompanyID=i.CompanyID
)

對於這種需求,Hive使用Left Semi-Join(左半開連接)來解決。

SELECT i.* from userInfo i left semi-join userScopeRelation s 
on i.userInfoId=s.userInfoId and i.CompanyID=s.CompanyID

但是這裏注意,select 後面的列,不能有left semi-join右邊表的字段,只能是左邊表的字段。

八、排序

Order By

select * from user_leads order by user_id

Hive中的Order By達到的效果和SQL Server中是一樣的,會對查詢結果進行全局排序,但是Hive語句最終要轉換爲MapReduce程序放到Hadoop分佈式集羣上去執行,Order By這樣的操作,肯定要在Map後彙集到一個Reduce上執行,如果結果數據量大,那就會造成Reduce執行相當漫長。

所以,Hive中儘量不要用Order By,除非非常確定結果集很小。

但是排序的需求總是有的,Hive中使用下面的幾種排序來滿足需求。

Sort By

select * from user_leads sort by user_id

這個例子中,Sort By是在每個reduce中進行排序,是一個局部排序,可以保證每個Reduce中是按照user_id進行排好序的,但是全局上來說,相同的user_id可以被分配到不同的Reduce上,雖然在各個Reduce上是排好序的,但是全局上不一定是排好序的。

Distribute By 和 Sort By

--Distribute By 和Sort By實例
select * from user_leads where user_id!='0' 
Distribute By cast(user_id as int) Sort by cast(user_id as int);

Distribute By 指定map輸出結果怎麼樣劃分後分配到各個Reduce上去,比如Distribute By user_id,就可以保證user_id字段相同的結果被分配到同一個reduce上去執行。然後再指定Sort By user_id,則在Reduce上進行按照user_id進行排序。

但是這種還是不能做到全局排序,只能保證排序字段值相同的放在一起,並且在reduce上局部是排好序的。

需要注意的是Distribute By 必須寫在Sort By前面。

Cluster By

如果Distribute By和Sort By的字段是同一個,可以簡寫爲 Cluster By.

select * from user_leads where user_id!='0' 
Cluster By cast(user_id as int) ;

常見全局排序需求

常見的排序需求有兩種:要求最終結果是有序的、按某個字段排序後取出前N條數據。

最終結果是有序的

最終分析結果往往是比較小的,因爲客戶不太可能最終要的是一個超級大數據集。所以實現方式是先得到一個小結果集,然後在得到最終的小結果集上使用order by 進行排序。

select * from (
select user_id,count(leads_id) cnt from user_leads  
where user_id!='0' 
group by user_id
) a order by a.cnt;

這個語句讓程序首先執行group by語句獲取到一個小結果集,group by 過程中是不指定排序的,然後再對小結果集進行排序,這樣得到的最終結果是全局排序的。

取前N條

select a.leads_id,a.user_name from (
  select leads_id,user_name  from user_leads  
distribute by length(user_name)  sort by length(user_name) desc limit 10
 ) a order by length(a.user_name) desc limit 10;

這個語句是查詢user_name最長的10條記錄,實現是先根據user_name的長度在各個Reduce上進行排序後取各自的前10個,然後再從10*N條的結果集裏用order by取前10個。

這個例子一定要結合MapReduce的執行原理和執行過程才能很好的理解,所以這個最能體現:看Hive語句要以MapReduce的角度看。

九、自定義函數

Hive內置函數

Hive中自帶了大量的內置函數,詳細可參看如下資源:

官方文檔:

https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF#LanguageManualUDF-Built-inFunctions.

網友整理的中文文檔:http://blog.csdn.net/wisgood/article/details/17376393.

開發過程中應該儘量使用Hive內置函數,畢竟Hive內置函數經過了大量的測試,性能普遍較好,任何一點性能上的問題在大數據量上跑時候都會被放大。

自定義函數

同SQL Server一樣,Hive也允許用戶自定義函數,這大大擴展了Hive的功能,Hive是用Java語言寫的,所以自定義函數也需要用Java來寫。

編寫一個Hive的自定義函數,需要新建一個Java類來繼承UDF類並實現evaluate()函數,evaluate()函數中編寫自定義函數的實現邏輯,返回值給Hive使用,需要注意的是,evaluate()函數的輸入輸出都必須是Hadoop的數據類型,以便可以被MapReduce程序來進行序列化反序列化。編寫完成後將Java程序打成Jar包,在Hive會話中載入Jar包來使用自定義函數。

在執行Hive語句時,遇到一個自定義函數就會實例化一個類,並執行對應的evaluate()函數,每行輸入都會調用一次evaluate()函數,所以在編寫自定義函數時,一定要注意大數據量時的資源佔用問題。

Hive中的自定義函數依據輸入輸出數據的個數,分爲以下幾類:

UDF用戶自定義函數(一進一出)

這種是最普通最常見的自定義函數,類似內置函數length()、yaer()等函數,輸入爲一個值,輸出也爲一個值。下面是一個獲取唯一ID的自定義函數例子:

package com.autohome.ics.bigdata.common.Date;
import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.hadoop.hive.ql.udf.UDFType;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import java.util.Date;

/*
 生成一個指定長度的隨機字符串(最長爲36位)
 */
@org.apache.hadoop.hive.ql.exec.Description(name = "UUID",
        extended = "示例:select UUID(32) from src;",
        value = "_FUNC_(leng)-生成一個指定長度的隨機字符串(最長爲36位)")
@UDFType(deterministic = false)
public class UUID extends UDF {
    public Text evaluate(IntWritable leng) {
        String uuid = java.util.UUID.randomUUID().toString();
        int le = leng.get();
        le = le > uuid.length() ? uuid.length() : le;
        return new Text(uuid.substring(0, le));
    }

/*
 生成一個隨機字符串
*/
public Text evaluate() {
    String uuid = java.util.UUID.randomUUID().toString();
    return new Text(uuid);
}
}
  • 這個實例是獲取一個指定長度的隨機字符串自定義函數,這個自定義函數創建了一個類UUID,繼承於UDF父類。

  • UUID類要實現evaluate函數,獲取一個指定長度的隨機字符串。

  • evaluate函數是可以有多個重載的。

  • Description是自定義函數的描述信息。

  • 這裏有一個參數deterministic,是標識這個自定義函數是否是那種輸入確定時輸出就確定的函數,默認是true,比如length函數就是如果輸入同一個值,那麼輸出肯定是一致的,但是我們這裏的UUID就算輸入確定,但是輸出也是不確定的,所以要將deterministic設置爲false。

UDAF用戶自定義聚合函數(多進一出)

UDAF是自定義聚合函數,類似於sum()、avg(),這一類函數輸入是多個值,輸出是一個值。

UDAF是需要hive sql語句和group by聯合使用的。

聚合函數常常需要對大量數組進行操作,所以在編寫程序時,一定要注意內存溢出問題。

UDAF分爲兩種:簡單UDAF和通用UDAF。簡單UDAF寫起來比較簡單,但是因爲使用了JAVA的反射機制導致性能有所損失,另外有些特性不能使用,如可變參數列表,通用UDAF可以使用所有功能,但是寫起來比較複雜。

(1) 簡單UDAF實例

package com.autohome.ics.bigdata.common.number;
import org.apache.hadoop.hive.ql.exec.UDAF;
import org.apache.hadoop.hive.ql.exec.UDAFEvaluator;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import java.util.regex.Pattern;
/**
 * Created by 鳴宇淳 on 2016/8/30.
*對傳入的字符串列表,按照數字大小進行排序後,找出最大的值
*/
public class MaxIntWithString extends UDAF {
    public static  class MaxIntWithStringUDAFEvaluator implements  UDAFEvaluator{
        //最終結果最大的值
        private IntWritable MaxResult;
        @Override
        public void init() {
            MaxResult=null;
        }
        //每次對一個新值進行聚集計算都會調用iterate方法
        //這裏將String轉換爲int後,進行比較大小
        public boolean iterate(Text value)
        {
            if(value==null)
                return false;
            if(!isInteger(value.toString()))
            {
                return false;
            }
            int number=Integer.parseInt(value.toString());
            if(MaxResult==null)
                MaxResult=new IntWritable(number);
            else
                MaxResult.set(Math.max(MaxResult.get(), number));
            return true;
        }
        //Hive需要部分聚集結果的時候會調用該方法
        //會返回一個封裝了聚集計算當前狀態的對象
        public IntWritable terminatePartial()
        {
            return MaxResult;
        }
        //合併兩個部分聚集值會調用這個方法
        public boolean merge(Text other)
        {
            return iterate(other);
        }
        //Hive需要最終聚集結果時候會調用該方法
        public IntWritable terminate()
        {
            return MaxResult;
        }
        private static boolean isInteger(String str) {
            Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$");
            return pattern.matcher(str).matches();
        }
    }
}
  • UDAF要繼承於UDAF父類org.apache.hadoop.hive.ql.exec.UDAF。

  • 內部類要實現org.apache.hadoop.hive.ql.exec.UDAFEvaluator接口。

  • MaxIntWithStringUDAFEvaluator類裏需要實現init、iterate、terminatePartial、merge、terminate這幾個函數,是必不可少的.

  • init()方法用來進行全局初始化的。

  • iterate()中實現比較邏輯。

  • terminatePartial是Hive部分聚集時調用的,類似於MapReduce裏的Combiner,這裏能保證能得到各個部分的最大值。

  • merge是多個部分合並時調用的,得到了參與合併的最大值。

  • terminate是最終Reduce合併時調用的,得到最大值。

這裏參考了:

http://computerdragon.blog.51cto.com/6235984/1288567

http://blog.csdn.net/xch_w/article/details/16886179

(2) 通用UDAF實例

開發通用UDAF有兩個步驟,第一個是編寫resolver類,第二個是編寫evaluator類。resolver負責類型檢查,操作符重載。evaluator真正實現UDAF的邏輯。通常來說,頂層UDAF類繼承org.apache.hadoop.hive.ql.udf.GenericUDAFResolver2,裏面編寫嵌套類evaluator 實現UDAF的邏輯。 通用UDAF使用場景較少,詳情可以參看內置函數的源碼,或者官方文檔。

UDTF自定義表生成函數(一進多出)

UDTF是將一個輸入值轉變爲一個數組。

下面這個例子是從nginx日誌中的agent信息中提取瀏覽器名稱和版本號的自定義函數,輸入參數類似於:

”Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML like Gecko) Chrome/31.0.1650.63 Safari/537.36”,輸出爲:Chrome 31.0.1650.63。

package com.autohome.ics.bigdata.common.String;
import cz.mallat.uasparser.OnlineUpdater;
import cz.mallat.uasparser.UASparser;
import cz.mallat.uasparser.UserAgentInfo;
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;
import org.apache.hadoop.hive.serde2.objectinspector.*;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
 * 解析瀏覽器信息UDTF
 *
 * 將日誌中的http_user_agent,得到 browser_name,browser_version兩個字段
 *  Created by ad on 2016/7/29.
 */
public class ParseUserAgentUDTF  extends GenericUDTF{
    private static UASparser uaSparser;
    static{
        try {
            uaSparser = new UASparser(OnlineUpdater.getVendoredInputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private final ListObjectInspector listIO = null;
    private final Object[] forwardObj = new Object[2];
    /**
     * 聲明解析出來的字段名稱和類型
     * @param argOIs
     * @return
     * @throws UDFArgumentException
     */
    @Override
    public StructObjectInspector initialize(StructObjectInspector argOIs) throws UDFArgumentException {
        if(argOIs.getAllStructFieldRefs().size() != 1){
            throw new UDFArgumentException("args error!");
        }
        ArrayList<String> fieldNames = new ArrayList<String>();
        ArrayList<ObjectInspector> fieldOIs = new ArrayList<>();
        fieldNames.add("browser_name");
        fieldNames.add("browser_version");
        fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
        fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
        return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames,fieldOIs);
    }
    @Override
    public void process(Object[] args) throws HiveException {
        // 真正解析的地方
      /*
      輸入字符串:"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36"
      解析爲:
       */
        String userAgent = args[0].toString();
        try {
            UserAgentInfo userAgentInfo = uaSparser.parse(userAgent);
            List<String> bws = new ArrayList<>();
            bws.add(userAgentInfo.getUaFamily());
            bws.add(userAgentInfo.getBrowserVersionInfo());
            super.forward(bws.toArray(new String[0]));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void close() throws HiveException {
    }
}

自定義函數使用方式:

  1. 打包爲jar包(例如包名爲:common.jar),因爲這個程序引用了依賴uasparser,所以打包時注意應該將依賴uasparser打進去。

  2. hive中添加jar包

    add jar lib/common.jar;

  3. 聲明函數

    create temporary function ParseUserAgent as 'com.autohome.ics.bigdata.common.String. ParseUserAgentUDTF';

  4. 查詢 select ParseUserAgent(agent) from user_action_log_like;

  5. 結果 Chrome 31.0.1650.63 Chrome 31.0.1650.63 Chrome 31.0.1650.63 Chrome 31.0.1650.63 Chrome 31.0.1650.63

參考:cwiki.apache.org

十、MapReduce執行過程

Hive語句最終是要轉換爲MapReduce程序放到Hadoop上去執行的,如果想深入瞭解Hive,並能夠很好地優化Hive語句,瞭解MapReduce的執行過程至關重要,因爲只有知道了MapReduce程序是怎麼執行的,才能瞭解Hive語句是怎麼執行的,纔能有針對性地優化。

執行過程簡介

MapReduce過程大體分爲兩個階段:map函數階段和reduce函數階段,兩個階段之間有有個shuffle。

  • Hadoop將MapReduce輸入的數據劃分爲等長的小分片,一般每個分片是128M,因爲HDFS的每個塊是128M。Hadoop1.X中這個數是64M。

  • map函數是數據準備階段,讀取分片內容,並篩選掉不需要的數據,將數據解析爲鍵值對的形式輸出,map函數核心目的是形成對數據的索引,以供reduce函數方便對數據進行分析。

  • 在map函數執行完後,進行map端的shuffle過程,map端的shuffle是將map函數的輸出進行分區,不同分區的數據要傳入不同的Reduce裏去。

  • 各個分區裏的數據傳入Reduce後,會先進行Reduce端的Shuffle過程,這裏會將各個Map傳遞過來的相同分區的進行排序,然後進行分組,一個分組的數據執行一次reduce函數。

  • reduce函數以分組的數據爲數據源,對數據進行相應的分析,輸出結果爲最終的目標數據。

  • 由於map任務的輸出結果傳遞給reduce任務過程中,是在節點間的傳輸,是佔用帶寬的,這樣帶寬就制約了程序執行過程的最大吞吐量,爲了減少map和reduce間的數據傳輸,在map後面添加了combiner函數來就map結果進行預處理,combiner函數是運行在map所在節點的。

下面的示例圖描述了整個MapReduce的執行過程:

enter image description here

工作機制

enter image description here

分片

HDFS上的文件要用很多mapper進程處理,而map函數接收的輸入是鍵值對的形式,所以要先將文件進行切分並組織成鍵值對的形式,這個切分和轉換的過程就是數據分片。

在編寫MapReduce程序時,可以通過job.setInputFormatClass()方法設置分片規則,如果沒有指定默認是用TextInputFormat類。分片規則類都必須繼承於FileInputFormat。

Map過程

每個數據分片將啓動一個Map進程來處理,分片裏的每個鍵值對運行一次map函數,根據map函數裏定義的業務邏輯處理後,得到指定類型的鍵值對。

Map Shuffle過程

Map過程後要進行Map端的Shuffle階段,Map端的Shuffle數據處理過程如下圖所示:

enter image description here

環形緩衝區

Map輸出結果是先放入內存中的一個環形緩衝區,這個環形緩衝區默認大小爲100M(這個大小可以在io.sort.mb屬性中設置),當環形緩衝區裏的數據量達到閥值時(這個值可以在io.sort.spill.percent屬性中設置)就會溢出寫入到磁盤,環形緩衝區是遵循先進先出原則,Map輸出一直不停地寫入,一個後臺進程不時地讀取後寫入磁盤,如果寫入速度快於讀取速度導致環形緩衝區裏滿了時,map輸出會被阻塞直到寫磁盤過程結束。

分區

從環形緩衝區溢出到磁盤過程,是將數據寫入mapred.local.dir屬性指定目錄下的特定子目錄的過程。 但是在真正寫入磁盤之前,要進行一系列的操作,首先就是對於每個鍵,根據規則計算出來將來要輸出到哪個reduce,根據reduce不同分不同的區,分區是在內存裏分的,分區的個數和將來的reduce個數是一致的。

排序

在每個分區上,會根據鍵進行排序。

Combiner

combiner方法是對於map輸出的結果按照業務邏輯預先進行處理,目的是對數據進行合併,減少map輸出的數據量。

排序後,如果指定了conmbiner方法,就運行combiner方法使得map的結果更緊湊,從而減少寫入磁盤和將來網絡傳輸的數據量。

合併溢出文件

環形緩衝區每次溢出,都會生成一個文件,所以在map任務全部完成之前,會進行合併成爲一個溢出文件,每次溢出的各個文件都是按照分區進行排好序的,所以在合併文件過程中,也要進行分區和排序,最終形成一個已經分區和排好序的map輸出文件。

在合併文件時,如果文件個數大於某個指定的數量(可以在min.num.spills.for.combine屬性設置),就會進再次combiner操作,如果文件太少,效果和效率上,就不值得花時間再去執行combiner來減少數據量了。

壓縮

Map輸出結果在進行了一系列的分區、排序、combiner合併、合併溢出文件後,得到一個map最終的結果後,就應該真正存儲這個結果了,在存儲之前,可以對最終結果數據進行壓縮,一是可以節約磁盤空間,而是可以減少傳遞給reduce時的網絡傳輸數據量。

默認是不進行壓縮的,可以在mapred.compress.map.output屬性設置爲true就啓用了壓縮,而壓縮的算法有很多,可以在mapred.map.output.compression.codec屬性中指定採用的壓縮算法,具體壓縮詳情,可以看本文的後面部分的介紹。

Reduce Shuffle過程

Map端Shuffle完成後,將處理結果存入磁盤,然後通過網絡傳輸到Reduce節點上,Reduce端首先對各個Map傳遞過來的數據進行Reduce 端的Shuffle操作,Reduce端的Shuffle過程如下所示:

enter image description here

複製數據

各個map完成時間肯定是不同的,只要有一個map執行完成,reduce就開始去從已完成的map節點上覆制輸出文件中屬於它的分區中的數據,reduce端是多線程並行來複制各個map節點的輸出文件的,線程數可以在mapred.reduce.parallel.copies屬性中設置。

reduce將複製來的數據放入內存緩衝區(緩衝區大小可以在mapred.job.shuffle.input.buffer.percent屬性中設置)。當內存緩衝區中數據達到閥值大小或者達到map輸出閥值,就會溢寫到磁盤。

寫入磁盤之前,會對各個map節點來的數據進行合併排序,合併時如果指定了combiner,則會再次執行combiner以儘量減少寫入磁盤的數據量。爲了合併,如果map輸出是壓縮過的,要在內存中先解壓縮後合併。

合併排序

合併排序其實是和複製文件同時並行執行的,最終目的是將來自各個map節點的數據合併並排序後,形成一個文件。

分組

分組是將相同key的鍵值對分爲一組,一組是一個列表,列表中每一組在一次reduce方法中處理。

執行Reduce方法

Reduce端的Shuffle完成後,就交由reduce方法來進行處理了。

Reduce過程

Reduce端的Shuffle過程後,最終形成了分好組的鍵值對列表,相同鍵的數據分爲一組,分組的鍵是分組的鍵,值是原來值得列表,然後每一個分組執行一次reduce函數,根據reduce函數裏的業務邏輯處理後,生成指定格式的鍵值對。

十一、性能優化

Hadoop啓動開銷大,如果每次只做小數量的輸入輸出,利用率將會很低。所以用好Hadoop的首要任務是增大每次任務所搭載的數據量。Hadoop的核心能力是parition和sort,因而這也是優化的根本。 Hive優化時,把hive Sql當做mapreduce程序來讀,而不是當做SQL來讀。

HiveQL層面優化

利用分區表優化

分區表是在某一個或者某幾個維度上對數據進行分類存儲,一個分區對應於一個目錄。在這中的存儲方式,當查詢時,如果篩選條件裏有分區字段,那麼Hive只需要遍歷對應分區目錄下的文件即可,不用全局遍歷數據,使得處理的數據量大大減少,提高查詢效率。

當一個Hive表的查詢大多數情況下,會根據某一個字段進行篩選時,那麼非常適合創建爲分區表。

利用桶表優化

桶表的概念在前面有詳細介紹,就是指定桶的個數後,存儲數據時,根據某一個字段進行哈希後,確定存儲在哪個桶裏,這樣做的目的和分區表類似,也是使得篩選時不用全局遍歷所有的數據,只需要遍歷所在桶就可以了。

  • hive.optimize.bucketmapJOIN 爲true

  • sort-merge JOIN hive.input.format=org.apache.hadoop.hive.ql.io.bucketizedHiveInputFormat; hive.optimize.bucketmapjoin=true; hive.optimize.bucketmapjoin.sortedmerge=true;

join優化

  • 優先過濾後再join,最大限度地減少參與Join的數據量。

  • 小表join大表原則。 應該遵守小表join大表原則,原因是Join操作在reduce階段,位於join左邊的表內容會被加載進內存,將條目少的表放在左邊,可以有效減少發生內存溢出的機率。join中執行順序是從左到右生成Job,應該保證連續查詢中的表的大小從左到右是依次增加的。

  • join on 條件相同的放入一個job. hive中,當多個表進行join時,如果join on的條件相同,那麼他們會合併爲一個MapReduce Job,所以利用這個特性,可以將相同的join on的放入一個job來節省執行時間。

select pt.page_id,count(t.url) PV
from rpt_page_type pt
join
(
    select url_page_id,url from trackinfo where ds='2016-10-11'
) t    on pt.page_id=t.url_page_id
join
(
    select page_id from rpt_page_kpi_new where ds='2016-10-11'
) r    on t.url_page_id=r.page_id
group by pt.page_id;

啓用mapjoin

mapjoin是將join雙方比較小的表直接分發到各個map進程的內存中,在map進程中進行join操作,這樣就省掉了reduce步驟,提高了速度。

mapjoin相關參數如下:

<property>
<name>hive.auto.convert.join</name>
    <value>true</value>
</property>

<property>
<name>hive.auto.convert.join</name>
    <value>true</value>
</property>  

<property>
 <name>hive.auto.convert.join.noconditionaltask.size</name>
    <value>10000000</value>
</property>  

<property>
<name>hive.auto.convert.join.use.nonstaged</name>
    <value>false</value>   
</property>
  • hive.auto.convert.join

    爲true時,join方數據量小的表會整體分發到各個map進程的內存中,在map進程本地進行join操作,這樣能大大提高運算效率,犧牲的是內存容量,所以數據量小於某一個值的才允許用mapjoin分發到各個map節點裏,而這個值用以下參數來配置。

  • hive.auto.convert.join.noconditionaltask

    設置爲true,hive才基於輸入文件大小進行自動轉換爲mapjoin.

  • hive.auto.convert.join.noconditionaltask.size

    指定小於多少的表數據放入map內存,使用mapjoin,默認是10M.

  • 這個優化只對join有效,對left join、right join 無效。

桶表mapjoin

當兩個分桶表join時,如果join on的是分桶字段,小表的分桶數時大表的倍數時,可以啓用map join來提高效率。啓用桶表mapjoin要啓用hive.optimize.bucketmapjoin參數。

<property>
   <name>hive.optimize.bucketmapjoin</name>
   <value>true</value>
   <description>Whether to try bucket mapjoin</description>
</property>

Group By數據傾斜優化

Group By很容易導致數據傾斜問題,因爲實際業務中,通常是數據集中在某些點上,這也符合常見的2/8原則,這樣會造成對數據分組後,某一些分組上數據量非常大,而其他的分組上數據量很小,而在mapreduce程序中,同一個分組的數據會分配到同一個reduce操作上去,導致某一些reduce壓力很大,其他的reduce壓力很小,這就是數據傾斜,整個job執行時間取決於那個執行最慢的那個reduce。 解決這個問題的方法是配置一個參數:set hive.groupby.skewindata=true。

當選項設定爲 true,生成的查詢計劃會有兩個 MR Job。第一個 MR Job 中, Map的輸出結果會隨機分佈到 Reduce 中,每個 Reduce做部分聚合操作,並輸出結果,這樣處理的結果是相同的 Group By Key 有可能被分發到不同的 Reduce 中,從而達到負載均衡的目的,在第一個Job中通過聚合操作減少了數據量;第二個 MR Job 再根據預處理的數據結果按照 Group By Key 分佈到 Reduce 中(這個過程可以保證相同的 GroupBy Key 被分佈到同一個 Reduce 中),最後完成最終的聚合操作。

Order By 優化

因爲order by只能是在一個reduce進程中進行的,所以如果對一個大數據集進行order by,會導致一個reduce進程中處理的數據相當大,造成查詢執行超級緩慢。在要有進行order by 全局排序的需求時,用以下幾個措施優化:

  • 在最終結果上進行order by,不要在中間的大數據集上進行排序。如果最終結果較少,可以在一個reduce上進行排序時,那麼就在最後的結果集上進行order by。

  • 如果需求是取排序後前N條數據,那麼可以使用distribute by和sort by在各個reduce上進行排序後取前N條,然後再對各個reduce的結果集合並後在一個reduce中全局排序,再取前N條,因爲參與全局排序的Order By的數據量最多有reduce個數*N,所以速度很快。 例子:

select a.leads_id,a.user_name from 
(
  select leads_id,user_name  from user_leads  
distribute by length(user_name)  sort by length(user_name) desc limit 10
 ) a order by length(a.user_name) desc limit 10;

Group By Map端聚合

並不是所有的聚合操作都需要在 Reduce 端完成,很多聚合操作都可以先在 Map端進行部分聚合,最後在 Reduce 端得出最終結果。

hive.map.aggr = true 是否在 Map 端進行聚合,默認爲 True。

hive.groupby.mapaggr.checkinterval = 100000 在 Map 端進行聚合操作的條目數目

一次讀取多次插入

有些場景是從一個表讀取數據後,要多次利用,這時候就可以使用multi insert語法:

from user_action_log
 insert overwrite table log1 select companyid,originalstring  where companyid='100006'
 insert overwrite table log2 select companyid,originalstring  where companyid='10002'

每次hive查詢,都會將數據集整個遍歷一遍。當查詢結果會插入多個表中時,可以採用以上語法,將一次遍歷寫入多個表,以達到提高效率的目的。

Join字段顯示類型轉換

當參與join的字段類型不一致時,Hive會自動進行類型轉換,但是自動轉換有時候效率並不高,可以根據實際情況通過顯示類型轉換來避免HIVE的自動轉換。

使用orc、parquet等列式存儲格式

創建表時,儘量使用orc、parquet這些列式存儲格式,因爲列式存儲的表,每一列的數據在物理上是存儲在一起的,Hive查詢時會只遍歷需要列數據,大大減少處理的數據量。

Hive架構層面優化

不執行MapReduce

hive中有個參數:hive.fetch.task.conversion,定義如下:

<property>
  <name>hive.fetch.task.conversion</name>
  <value>minimal</value>
  <description>
    Some select queries can be converted to single FETCH task minimizing latency.
    Currently the query should be single sourced not having any subquery and should not have
    any aggregations or distincts (which incurs RS), lateral views and joins.
    1. minimal : SELECT STAR, FILTER on partition columns, LIMIT only
    2. more    : SELECT, FILTER, LIMIT only (TABLESAMPLE, virtual columns)
  </description>
</property>

Hive從HDFS讀取數據,有兩種方式:啓用MapReduce讀取、直接抓取。

很顯然直接抓取數據比MapReduce讀取數據要快的多,但是隻有少數操作可以直接抓取數據,hive.fetch.task.conversion參數就是設置什麼情況下采用直接抓取方法,它的值有兩個:

  • minimal:只有 select * 、在分區字段上where過濾、有limit這三種場景下才啓用直接抓取方式。

  • more:在select、where篩選、limit時,都啓用直接抓取方式。

啓用fetch more模式: set hive.fetch.task.conversion=more;

實例:

set hive.fetch.task.conversion=more;
select userid,username from user_info where cityid is not null;

這個例子中,如果set hive.fetch.task.conversion=minimal,那麼下面的查詢語句會以MapReduce方法執行,運行時間比較長,但是改爲more後,發現查詢速度非常快。

本地模式執行MapReduce

Hive在集羣上查詢時,默認是在集羣上N臺機器上運行,需要多個機器進行協調運行,這個方式很好地解決了大數據量的查詢問題。但是當Hive查詢處理的數據量比較小時,其實沒有必要啓動分佈式模式去執行,因爲以分佈式方式執行就涉及到跨網絡傳輸、多節點協調等,並且消耗資源。這個時間可以只使用本地模式來執行mapreduce job,只在一臺機器上執行,速度會很快。

啓動本地模式涉及到三個參數:

參數名 默認值 備註
hive.exec.mode.local.auto false 讓hive決定是否在本地模式自動運行
hive.exec.mode.local.auto.input.files.max 4 不啓用本地模式的task最大個數
hive.exec.mode.local.auto.inputbytes.max 128M 不啓動本地模式的最大輸入文件大小

各個參數定義如下:

<property>
  <name>hive.exec.mode.local.auto</name>
  <value>false</value>
  <description> Let Hive determine whether to run in local mode automatically </description>
</property>

<property>
    <name>hive.exec.mode.local.auto.input.files.max</name>
    <value>4</value>
    <description>When hive.exec.mode.local.auto is true, the number of tasks should less than this for local mode.</description>
</property>

<property>
    <name>hive.exec.mode.local.auto.inputbytes.max</name>
    <value>134217728</value>
    <description>When hive.exec.mode.local.auto is true, input bytes should less than this for local mode.</description>
</property>

set hive.exec.mode.local.auto=true是打開hive自動判斷是否啓動本地模式的開關,但是隻是打開這個參數並不能保證啓動本地模式,要當map任務數不超過hive.exec.mode.local.auto.input.files.max的個數並且map輸入文件大小不超過hive.exec.mode.local.auto.inputbytes.max所指定的大小時,才能啓動本地模式。

JVM重用

因爲Hive語句最終要轉換爲一系列的MapReduce Job的,而每一個MapReduce Job是由一系列的Map Task和Reduce Task組成的,默認情況下,MapReduce中一個Map Task或者一個Reduce Task就會啓動一個JVM進程,一個Task執行完畢後,JVM進程就退出。這樣如果任務花費時間很短,又要多次啓動JVM的情況下,JVM的啓動時間會變成一個比較大的消耗,這個時候,就可以通過重用JVM來解決。

set mapred.job.reuse.jvm.num.tasks=5

這個設置就是制定一個jvm進程在運行多次任務之後再退出,這樣一來,節約了很多的JVM的啓動時間。

並行化

一個hive sql語句可能會轉爲多個mapreduce Job,每一個job就是一個stage,這些job順序執行,這個在hue的運行日誌中也可以看到。但是有時候這些任務之間並不是是相互依賴的,如果集羣資源允許的話,可以讓多個並不相互依賴stage併發執行,這樣就節約了時間,提高了執行速度,但是如果集羣資源匱乏時,啓用並行化反倒是會導致各個job相互搶佔資源而導致整體執行性能的下降。

啓用並行化:

set hive.exec.parallel=true;

十二、後記

還是那句話,Hive入門使用很容易,這得益於它採用了類似SQL語句的方式與用戶交互,這也是Hive被大量使用的原因,但是最好還是要理解Hive背後的執行原理,這樣才能開發出高效的程序。 以上就是對Hive入門者的建議。

我第一次參與GitChat的分享,不知效果如何,如果大家對本次Hive還算滿意的話,我接下來進行一次Hive進階和MapReduce的分享,感謝大家的參與。

參考資料

  1. 《Hive編程指南》 Eduard Capriolo、Dean Wampler、Jason Rutberglen (著),曹坤(譯)。

  2. Hive官方文檔:https://cwiki.apache.org/confluence/display/Hive/GettingStarted.

  3. 互聯網上其他資源。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章