其实不管是分区还是分桶都是为了更好的管理数据。分区将表的数据分到不同的目录存储,从而在查询的时候可以通过where条件过滤一部分数据,减小查询的数据量从而提高性能。但分区的这种机制往往在数据符合以下条件时才会表现的比较好:
然而,并非所有场景都是符合上面两条的。比如比较常见的按照国家等地理位置去分区的时候,可能几个大国的数据就占了总数据量的百分之七八十,而剩下的所有国家只占了百分之二三十,也就是不符合上面的第二条。这个时候分区的好处就大打折扣了。为了克服这个问题,Hive增加了分桶的机制。我们先介绍一些分桶相关的基础概念,然后通过例子再做说明。
分桶的概念其实比较简单:指定桶的个数,选中表中的若干个列,通过对数据中这些列的值做哈希决定数据应该存储到哪个桶里面去,公式表示为:bucket num = hash_function(bucketing_column) mod num_buckets,不同的数据类型哈希函数不同。关于分桶还有以下一些关键点:
CLUSTERED BY
指定哈希列;分桶也可以带来如下一些好处:
下面我们看一个例子。
场景需求:还是使用上篇文章里面的用户数据UserRecords.txt,但这次只使用国家作为分区列,州做哈希列,也就是分桶列,并且桶内按照城市名排序。
和分区表一样,分桶表也不能使用LOAD DATA
的方式导入数据,所以我们还是像前文一样,先把数据导到一张临时表temp_user里面,这个步骤省略。如果不清楚的,请看上一篇文章(Hive的分区)。然后我们创建分桶表:
# 语法
CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name
[(col_name data_type [COMMENT col_comment], ... [constraint_specification])]
[COMMENT table_comment]
[PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)]
[CLUSTERED BY (col_name, col_name, ...) [SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS]
# 创建bucketed_user表
CREATE TABLE bucketed_user (
firstname VARCHAR(64),
lastname VARCHAR(64),
address STRING,
city VARCHAR(64),
state VARCHAR(64),
post STRING,
phone1 VARCHAR(64),
phone2 STRING,
email STRING,
web STRING
)
COMMENT 'A bucketed sorted user table'
PARTITIONED BY(country VARCHAR(64))
CLUSTERED BY(state) SORTED BY(city) INTO 32 BUCKETS;
我们使用可以看到,不像分区列是一个虚拟列,分桶列是表中的字段。在插入字段之前,我们先介绍两个和分桶相关的配置项:
在Hive 2.x之前,在定义表结构时通过CLUSTERED BY
指定分桶信息,然后在插入数据的时候如果设置hive.enforce.bucketing为true,则会按照表定义里面的桶个数进行自动分桶(dynamic bucket);如果不设置为true,用户也可以指定如何分桶(主要是指定reduce的个数set mapred.reduce.tasks=xx)。这样导致的问题就是表结构里面虽然定义了分桶信息,但实际插入数据的时候可能并没有分桶或者分桶方式与表结构里面定义的不一致。这样后续操作的时候有了很多不确定性,容易产生各种问题,所以在2.x版本之后,去掉了该项配置,直接将默认情况设置为了true。如果你用的Hive是2.x之前的版本,使用分桶的时候记得将这两个选项置为true。另外需要说明的是桶的个数决定了reduce的个数。
下面我们来插入数据:
# 因为我们使用了动态分区,所以和前文一样,设置动态分区相关的参数,参数含义见《Hive的分区》一文
set hive.exec.dynamic.partition=true;
set hive.exec.dynamic.partition.mode=nonstrict;
set hive.exec.max.dynamic.partitions.pernode=1000;
# 覆盖式插入数据
INSERT OVERWRITE TABLE bucketed_user
PARTITION(country)
SELECT firstname, lastname, address, city, state, post, phone1, phone2, email, web,
country
FROM temp_user;
执行插入语句的时候,大家可以看打印的日志,一共有32个reducers,和桶的个数一致。插入完成之后我们来看HDFS上面的数据:
➜ ~ hadoop fs -ls -R /user/hive/warehouse/bucketed_user
drwxrwxr-x - allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000000_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000001_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000002_0
-rwxrwxr-x 1 allan supergroup 806 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000003_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000004_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000005_0
-rwxrwxr-x 1 allan supergroup 17140 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000006_0
-rwxrwxr-x 1 allan supergroup 950 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000007_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000008_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000009_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000010_0
-rwxrwxr-x 1 allan supergroup 11314 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000011_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000012_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000013_0
-rwxrwxr-x 1 allan supergroup 4427 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000014_0
-rwxrwxr-x 1 allan supergroup 6132 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000015_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000016_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000017_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000018_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000019_0
-rwxrwxr-x 1 allan supergroup 12405 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000020_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000021_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000022_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000023_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000024_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000025_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000026_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000027_0
-rwxrwxr-x 1 allan supergroup 15262 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000028_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000029_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000030_0
-rwxrwxr-x 1 allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000031_0
drwxrwxr-x - allan supergroup 0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=CA
-rwxrwxr-x 1 allan supergroup
……省略后续输出……
可以看到每个分区内的数据被分成了32份,因为数据量太小,所以很多桶内是空的。如果数据量大的话,各个桶的数据量会比较均衡。
从大量数据中采样得到少量数据进行测试或者调试是数据分析中非常常见的操作,抛开业务在数据库我们可以使用LIMIT
语句实现该功能,但Hive提供了一个TABLESAMPLE
语法用来更好的实现采样,TABLESAMPLE
语句可以放到任何FROM语句中。目前支持两种采样方式:基于表的采样Sampling Bucketized Table和基于块的采样Block Sampling。
基于分桶表的采样的语法格式如下:
table_sample: TABLESAMPLE (BUCKET x OUT OF y [ON colname] [table_alias])
x out of y:表示要取哪些桶的数据。假设表有32个桶:
一些例子:
# 基于state字段采样
0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(BUCKET 3 OUT OF 32 ON state);
+-------------+----------+--------------+---------------------------------+
| firstname | country | state | city |
+-------------+----------+--------------+---------------------------------+
| Marleen | CA | BC | Abbotsford |
| Carole | CA | BC | Abbotsford |
| Lasandra | CA | BC | Abbotsford |
| Yvette | CA | AB | Big Valley |
| Annamae | CA | BC | Burnaby |
| Adela | CA | BC | Burnaby |
……省略……
| Shelia | UK | Cumbria | Silloth-on-Solway |
| Mauricio | UK | Cumbria | Walney North Ward |
| Quentin | US | MN | Burnsville |
| Cyndy | US | MN | Burnsville |
| Novella | US | HI | Hilo |
| Brandon | US | HI | Honolulu |
| Angella | US | HI | Honolulu |
| Fatima | US | MN | Hopkins |
| Skye | US | MN | Minneapolis |
| Rodolfo | US | MN | Northfield |
| Rolande | US | HI | Pearl City |
| Chantell | US | MN | Saint Paul |
| Matthew | US | MN | Shakopee |
+-------------+----------+--------------+---------------------------------+
131 rows selected (1.06 seconds)
# 使用rand()采样
0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(BUCKET 3 OUT OF 32 ON rand());
+-------------+----------+---------------------+---------------------------------+
| firstname | country | state | city |
+-------------+----------+---------------------+---------------------------------+
| Nenita | AU | NS | Botany |
| Aide | AU | NS | Rhodes |
| Hester | AU | NS | The Risk |
| Annita | AU | NT | Karama |
| Mariko | AU | WA | Hamel |
| Emelda | AU | WA | Nedlands |
| Leatha | AU | WA | Two Rocks |
| Kenny | AU | TA | Nicholls Rivulet |
| Eveline | AU | VI | Camberwell West |
……省略……
| Martina | US | FL | Orlando |
| Billye | US | MS | Pearl |
| Timothy | US | NY | Staten Island |
| Pamella | US | CO | Denver |
| Minna | US | PA | Kulpsville |
| Fabiola | US | PA | York |
| Junita | US | NJ | Cedar Grove |
| Helaine | US | NJ | Jersey City |
| Heike | US | NJ | Little Falls |
| Eladia | US | NJ | Ramsey |
| Felicidad | US | NJ | Riverton |
+-------------+----------+---------------------+---------------------------------+
58 rows selected (0.595 seconds)
需要注意的是这种采样方式并不要求表一定要是分桶的,如果没有分桶或者分桶了但采样的字段不在分桶字段里面,那也是可以正常采样的,只不过采样时会扫描全表数据,不是很高效而已。所以大多数情况这种采样方式都是和分桶一起使用的,采样的字段就是分桶的字段,这样采样时只扫描对应的桶就行,可以大大提高效率。
基于块的采样是后来新增的一项功能(从Hive 0.8版本开始,见HIVE-2121),这里的块指的是HDFS的Block。目前有三种方式,基本语法为:
# 基于百分比
block_sample: TABLESAMPLE (n PERCENT)
# 基于大小
block_sample: TABLESAMPLE (ByteLengthLiteral)
ByteLengthLiteral : (Digit)+ ('b' | 'B' | 'k' | 'K' | 'm' | 'M' | 'g' | 'G')
# 基于行数
block_sample: TABLESAMPLE (n ROWS)
基于百分比采样和基于大小的采样实质是一样的(见HIVE-3401),这两种方式目前不支持一些压缩的格式。如果采样失败了,就会返回整个表或者分区的数据。需要注意的是,因为是基于块采样的,所以最小的采样单位就是HDFS的一个block,也就是说返回的数据可能会比实际的数据多。比如%1的数据是100MB,但HDFS的一个block是256MB,那采样得到的数据将是256MB。一个示例:
# 采样0.1%的数据
0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(0.1 PERCENT) sampled_bucketed_user;
+------------+----------+-------------+---------+
| firstname | country | state | city |
+------------+----------+-------------+---------+
| Soledad | AU | AC | Barton |
| Darell | CA | ON | Ajax |
| Allene | UK | Derbyshire | Barlow |
| Devorah | US | NM | Clovis |
+------------+----------+-------------+---------+
4 rows selected (0.467 seconds)
# 采样100MB数据
0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(100B) sampled_bucketed_user;
+------------+----------+------------------+--------------------+
| firstname | country | state | city |
+------------+----------+------------------+--------------------+
| Santos | AU | NS | Allworth |
| Avery | CA | NS | Amherst |
| Lewis | UK | South Yorkshire | Central Ward |
| Weldon | US | IL | Arlington Heights |
+------------+----------+------------------+--------------------+
4 rows selected (0.187 seconds)
如果我们想保证每次采样的数据一样,可以设置种子:
set hive.sample.seednumber=<INTEGER>;
默认值是0,比如我们改为100,再采一次样:
0: jdbc:hive2://localhost:10000> set hive.sample.seednumber=100;
No rows affected (0.006 seconds)
0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(0.1 PERCENT) sampled_bucketed_user;
+------------+----------+------------------+--------------------+
| firstname | country | state | city |
+------------+----------+------------------+--------------------+
| Santos | AU | NS | Allworth |
| Avery | CA | NS | Amherst |
| Lewis | UK | South Yorkshire | Central Ward |
| Weldon | US | IL | Arlington Heights |
+------------+----------+------------------+--------------------+
4 rows selected (0.26 seconds)
基于行数的采样和前面两种方式不太一样:
# 从输入的每个分片从采5条数据(这里只有一个分片)
0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(5 ROWS) sampled_bucketed_user;
+------------+----------+--------+-----------------+
| firstname | country | state | city |
+------------+----------+--------+-----------------+
| Soledad | AU | AC | Barton |
| Annamae | AU | AC | Civic Square |
| Katheryn | AU | AC | Fyshwick |
| Roy | AU | AC | Red Hill |
| Jamie | AU | AC | Tuggeranong Dc |
+------------+----------+--------+-----------------+
5 rows selected (0.411 seconds)
本文介绍了Hive的分桶和采样,分桶很好的弥补了分区的一些不足。同时分桶之后,可以帮助我们更好的实现采样。需要注意的是不论是分区还是分桶,都是存在计算的,所以分区或者分桶之后,数据导入会比不分区不分桶慢,但换来的是后面查询会更快速。
References
]]>今天就来介绍这两种分区。
假设现在有这样一个场景:我们有一些用户数据(UserRecords.txt)需要存储在Hive里面,数据里面包含了国家和地区信息,根据业务需求我们要将不同国家不同地区的用户存储在不同的分区里面。也就是说我们需要使用国家和的确信息作为分区列。下面我们看如果通过两种分区去实现。
静态分区非常简单,之前的文章中已经提到过,就是我们导入数据的时候指定分区列的值,然后到后台就会用这个值建对应的目录,多个分区列就是嵌套的多级目录(所以Hive里面的分区列的顺序是有意义的)。
首先我们先将UserRecords.txt数据导入到Hive里面的一张临时表temp_user里面:
# 原始表
CREATE TABLE temp_user (
firstname VARCHAR(64),
lastname VARCHAR(64),
address STRING,
country VARCHAR(64),
city VARCHAR(64),
state VARCHAR(64),
post STRING,
phone1 VARCHAR(64),
phone2 STRING,
email STRING,
web STRING
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n';
# 导入原始数据
LOAD DATA LOCAL INPATH '/tmp/UserRecords.txt' INTO TABLE temp_user;
然后创建静态分区表,使用国家和地区作为分区列:
# 创建静态分区表
CREATE TABLE sp_user (
firstname VARCHAR(64),
lastname VARCHAR(64),
address STRING,
city VARCHAR(64),
post STRING,
phone1 VARCHAR(64),
phone2 STRING,
email STRING,
web STRING
)
PARTITIONED BY(country VARCHAR(64), state VARCHAR(64))
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n';
现在的问题来了,如何将temp_user中的数据按照分区导入到sp_user里面?静态分区是用户在导入数据的时候指定的,显然我们需要知道有哪些分区,知道以后,就可以使用下面的语句分多次导入了:
# 这里以导入数据US、CA的数据为例
INSERT INTO TABLE sp_user
PARTITION(country='US', state='CA')
SELECT firstname, lastname, address, city, post, phone1, phone2, email, web
FROM temp_user
WHERE country == 'US' AND state == 'CA';
# 对应HDFS上面的存储
➜ hadoop-2.7.7 hadoop fs -ls -R /user/hive/warehouse/sp_user
drwxrwxr-x - anonymous supergroup 0 2018-09-21 16:31 /user/hive/warehouse/sp_user/country=US
drwxrwxr-x - anonymous supergroup 0 2018-09-21 16:38 /user/hive/warehouse/sp_user/country=US/state=CA
-rwxrwxr-x 1 anonymous supergroup 9430 2018-09-21 16:38 /user/hive/warehouse/sp_user/country=US/state=CA/000000_0
显然,如果国家和地区的组合很多的话,这样做会累死人。这个时候其实便可以考虑使用动态分区了。
动态分区的"动"主要体现在分区列的值不是用户指定的,而是在运行阶段根据列值确定的。对于刚才的需求我们先创建分区表:
# 动态分区表
CREATE TABLE dp_user (
firstname VARCHAR(64),
lastname VARCHAR(64),
address STRING,
city VARCHAR(64),
post STRING,
phone1 VARCHAR(64),
phone2 STRING,
email STRING,
web STRING
)
PARTITIONED BY(country VARCHAR(64), state VARCHAR(64))
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n';
建表语句与静态分区表一模一样,没有任何区别,动态与静态的区别主要在数据导入上面。不过在导入之前,我们需要先简单介绍几个重要的跟动态分区相关的参数:
set hive.exec.dynamic.partition;
命令查看。在我们这个需求里面需要做如下修改:
# 我们的分区列里面全部是动态分区,所以需要修改为nonstrict
set hive.exec.dynamic.partition.mode=nonstrict;
# 创建的分区比较多,100不够用,调整为1000
set hive.exec.max.dynamic.partitions.pernode=1000;
然后就可以导入了:
INSERT INTO TABLE dp_user
PARTITION(country, state)
SELECT firstname, lastname, address, city, post, phone1, phone2, email, web,
country, state
FROM temp_user;
可以看到和静态分区导入的区别主要有两点:
然后我们看一下效果:
# Hive里面
0: jdbc:hive2://localhost:10000> SHOW PARTITIONS dp_user;
+--------------------------------------------+
| partition |
+--------------------------------------------+
| country=AU/state=AC |
| country=AU/state=NS |
| country=AU/state=NT |
| country=AU/state=QL |
| country=AU/state=SA |
| country=AU/state=TA |
| country=AU/state=VI |
| country=AU/state=WA |
| country=CA/state=AB |
| country=CA/state=BC |
| country=CA/state=MB |
| country=CA/state=NB |
| country=CA/state=NL |
| country=CA/state=NS |
| country=CA/state=ON |
| country=CA/state=PE |
| country=CA/state=QC |
| country=CA/state=SK |
| country=UK/state= Uphall and Winchburg%22 |
| country=UK/state= Uplawmoor and Newton%22 |
| country=UK/state=%22Yorkshire |
| country=UK/state=Aberdeenshire |
…… 限于篇幅,省略部分输出 ……
# HDFS上面
➜ hadoop-2.7.7 hadoop fs -ls -R /user/hive/warehouse/dp_user
18/09/21 17:07:38 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
drwxrwxr-x - allan supergroup 0 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU
drwxrwxr-x - allan supergroup 0 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=AC
-rwxrwxr-x 1 allan supergroup 788 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=AC/000000_0
drwxrwxr-x - allan supergroup 0 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=NS
-rwxrwxr-x 1 allan supergroup 16765 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=NS/000000_0
drwxrwxr-x - allan supergroup 0 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=NT
-rwxrwxr-x 1 allan supergroup 929 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=NT/000000_0
drwxrwxr-x - allan supergroup 0 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=QL
-rwxrwxr-x 1 allan supergroup 14926 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=QL/000000_0
drwxrwxr-x - allan supergroup 0 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=SA
-rwxrwxr-x 1 allan supergroup 5997 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=SA/000000_0
drwxrwxr-x - allan supergroup 0 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=TA
-rwxrwxr-x 1 allan supergroup 4331 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=TA/000000_0
drwxrwxr-x - allan supergroup 0 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=VI
-rwxrwxr-x 1 allan supergroup 12132 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=VI/000000_0
drwxrwxr-x - allan supergroup 0 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=WA
-rwxrwxr-x 1 allan supergroup 11068 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=WA/000000_0
…… 限于篇幅,省略部分输出 ……
可以看到,动态分区非常方便的帮我们实现了上面的需求,创建哪些分区是由数据决定,Hive自动帮我们创建的。
动态分区虽然好,但用的不对也会产生非常多的问题:最常见的比如创建太多的目录和文件、耗用内存等,这也是早期版本默认关闭该功能,以及现在的版本虽然默认允许,但也限制为strict的部分原因。这里有一篇文章给出了一些最佳实践,有兴趣的可以看一下:HIVE Dynamic Partitioning tips。
关于分区最后再提两个注意点:
LOAD DATA
的方式导入数据,一般都是使用INSERT
语句导入。注意:在Hive里面,关键字以及各种名字(数据库、分区、字段等)都是不区分大小写的。
除了查看分区的命令意外,其它和分区有关的命令都是操作表语句的一个子选项。前面部分我们已经用到了分区的创建、查看命令,最后再补充一些分区修改的DDL,包括:add、rename、exchanged(moved)、drop、(un)archive。这些都是ALTER TABLE
命令的子选项。
# 语法
ALTER TABLE table_name ADD [IF NOT EXISTS] PARTITION partition_spec [LOCATION 'location'][, PARTITION partition_spec [LOCATION 'location'], ...];
partition_spec:
: (partition_column = partition_col_value, partition_column = partition_col_value, ...)
# 例子
ALTER TABLE sp_user ADD PARTITION (country='CN', state='Shannxi') location '/user/hive/warehouse/sp_user/country=CN/state=Shannxi'
PARTITION (country='CN', state='Shanxi') location '/user/hive/warehouse/sp_user/country=CN/state=Shanxi';
# HDFS上
➜ hadoop-2.7.7 hadoop fs -ls -R /user/hive/warehouse/sp_user
drwxrwxr-x - allan supergroup 0 2018-09-22 11:12 /user/hive/warehouse/sp_user/country=CN
drwxrwxr-x - allan supergroup 0 2018-09-22 11:12 /user/hive/warehouse/sp_user/country=CN/state=Shannxi
drwxrwxr-x - allan supergroup 0 2018-09-22 11:12 /user/hive/warehouse/sp_user/country=CN/state=Shanxi
drwxrwxr-x - allan supergroup 0 2018-09-21 16:31 /user/hive/warehouse/sp_user/country=US
drwxrwxr-x - allan supergroup 0 2018-09-21 16:38 /user/hive/warehouse/sp_user/country=US/state=CA
-rwxrwxr-x 1 allan supergroup 9430 2018-09-21 16:38 /user/hive/warehouse/sp_user/country=US/state=CA/000000_0
# 语法
ALTER TABLE table_name PARTITION partition_spec RENAME TO PARTITION partition_spec;
# 例子
ALTER TABLE sp_user PARTITION(country='CN', state='Shanxi') RENAME TO PARTITION(country='CN', state='Beijing');
# HDFS上
➜ hadoop-2.7.7 hadoop fs -ls -R /user/hive/warehouse/sp_user
drwxrwxr-x - allan supergroup 0 2018-09-22 11:14 /user/hive/warehouse/sp_user/country=CN
drwxrwxr-x - allan supergroup 0 2018-09-22 11:12 /user/hive/warehouse/sp_user/country=CN/state=Beijing
drwxrwxr-x - allan supergroup 0 2018-09-22 11:12 /user/hive/warehouse/sp_user/country=CN/state=Shannxi
drwxrwxr-x - allan supergroup 0 2018-09-21 16:31 /user/hive/warehouse/sp_user/country=US
drwxrwxr-x - allan supergroup 0 2018-09-21 16:38 /user/hive/warehouse/sp_user/country=US/state=CA
-rwxrwxr-x 1 allan supergroup 9430 2018-09-21 16:38 /user/hive/warehouse/sp_user/country=US/state=CA/000000_0
# 语法
-- Move partition from table_name_1 to table_name_2
ALTER TABLE table_name_2 EXCHANGE PARTITION (partition_spec) WITH TABLE table_name_1;
-- multiple partitions
ALTER TABLE table_name_2 EXCHANGE PARTITION (partition_spec, partition_spec2, ...) WITH TABLE table_name_1;
# 例子:将dp_user的country=AU/state=AC分区移动到sp_user
ALTER TABLE sp_user EXCHANGE PARTITION(country='AU', state='AC') WITH TABLE dp_user;
# HDFS上
➜ hadoop-2.7.7 hadoop fs -ls -R /user/hive/warehouse/sp_user
18/09/22 11:20:11 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
drwxrwxr-x - allan supergroup 0 2018-09-22 11:19 /user/hive/warehouse/sp_user/country=AU
drwxrwxr-x - allan supergroup 0 2018-09-21 15:10 /user/hive/warehouse/sp_user/country=AU/state=AC
-rwxrwxr-x 1 allan supergroup 788 2018-09-21 15:10 /user/hive/warehouse/sp_user/country=AU/state=AC/000000_0
drwxrwxr-x - allan supergroup 0 2018-09-22 11:14 /user/hive/warehouse/sp_user/country=CN
drwxrwxr-x - allan supergroup 0 2018-09-22 11:12 /user/hive/warehouse/sp_user/country=CN/state=Beijing
drwxrwxr-x - allan supergroup 0 2018-09-22 11:12 /user/hive/warehouse/sp_user/country=CN/state=Shannxi
drwxrwxr-x - allan supergroup 0 2018-09-21 16:31 /user/hive/warehouse/sp_user/country=US
drwxrwxr-x - allan supergroup 0 2018-09-21 16:38 /user/hive/warehouse/sp_user/country=US/state=CA
-rwxrwxr-x 1 allan supergroup 9430 2018-09-21 16:38 /user/hive/warehouse/sp_user/country=US/state=CA/000000_0
# 由前文的图知道原来第一个分区是country=AU/state=AC,现在被移走了
➜ hadoop-2.7.7 hadoop fs -ls -R /user/hive/warehouse/dp_user | head
drwxrwxr-x - allan supergroup 0 2018-09-22 11:19 /user/hive/warehouse/dp_user/country=AU
drwxrwxr-x - allan supergroup 0 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=NS
-rwxrwxr-x 1 allan supergroup 16765 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=NS/000000_0
drwxrwxr-x - allan supergroup 0 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=NT
-rwxrwxr-x 1 allan supergroup 929 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=NT/000000_0
drwxrwxr-x - allan supergroup 0 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=QL
-rwxrwxr-x 1 allan supergroup 14926 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=QL/000000_0
drwxrwxr-x - allan supergroup 0 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=SA
-rwxrwxr-x 1 allan supergroup 5997 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=SA/000000_0
drwxrwxr-x - allan supergroup 0 2018-09-21 15:10 /user/hive/warehouse/dp_user/country=AU/state=TA
# 语法
ALTER TABLE table_name DROP [IF EXISTS] PARTITION partition_spec[, PARTITION partition_spec, ...]
[IGNORE PROTECTION] [PURGE]; -- (Note: PURGE available in Hive 1.2.0 and later, IGNORE PROTECTION not available 2.0.0 and later)
# 例子
ALTER TABLE sp_user DROP IF EXISTS PARTITION(country='AU', state='AC');
# 语法
ALTER TABLE table_name ARCHIVE PARTITION partition_spec;
ALTER TABLE table_name UNARCHIVE PARTITION partition_spec;
MSCK
命令来同步HDFS上面的数据与MetaStore里面的元数据信息。# 语法
MSCK [REPAIR] TABLE table_name [ADD/DROP/SYNC PARTITIONS];
# 例子
## 现在HDFS上创建分区目录
➜ hadoop-2.7.7 hadoop fs -mkdir /user/hive/warehouse/sp_user/country=CN/state=Shanghai
➜ hadoop-2.7.7 hadoop fs -ls -R /user/hive/warehouse/sp_user
drwxrwxr-x - allan supergroup 0 2018-09-22 11:44 /user/hive/warehouse/sp_user/country=CN
drwxr-xr-x - allan supergroup 0 2018-09-22 11:44 /user/hive/warehouse/sp_user/country=CN/state=Shanghai
drwxrwxr-x - allan supergroup 0 2018-09-22 11:12 /user/hive/warehouse/sp_user/country=CN/state=Shannxi
drwxrwxr-x - allan supergroup 0 2018-09-21 16:31 /user/hive/warehouse/sp_user/country=US
drwxrwxr-x - allan supergroup 0 2018-09-21 16:38 /user/hive/warehouse/sp_user/country=US/state=CA
-rwxrwxr-x 1 allan supergroup 9430 2018-09-21 16:38 /user/hive/warehouse/sp_user/country=US/state=CA/000000_0
# 此时Hive里面的分区信息不包含创建的目录
0: jdbc:hive2://localhost:10000> SHOW PARTITIONS sp_user;
+---------------------------+
| partition |
+---------------------------+
| country=CN/state=Shannxi |
| country=US/state=CA |
+---------------------------+
# 使用MSCK同步元数据
0: jdbc:hive2://localhost:10000> MSCK REPAIR TABLE sp_user;
No rows affected (0.46 seconds)
0: jdbc:hive2://localhost:10000> SHOW PARTITIONS sp_user;
+----------------------------+
| partition |
+----------------------------+
| country=CN/state=Shanghai |
| country=CN/state=Shannxi |
| country=US/state=CA |
+----------------------------+
4 rows selected (0.198 seconds)
需要注意:
Hive的分区在实际中用的比较多,最常用的分区列就是时间、地理位置等,本文就介绍到这里。
References
]]>先来一张官网的架构图:
这幅图清楚的展示了Hive和Hadoop的关系,并且展示了Hive一些重要的组件:
Tips:Hive中Compiler生成的执行计划是一个多阶段的DAG,像MR、Tez、Spark这些计算引擎都可以执行。
这便是Hive里面几个核心的组件。下面我们看看一下一次查询的完整流程(下面的step n对应图中的数组序号):
这便是Hive内部的处理流程。
HiverServer2(以下简称HS2)是Hive里面非常重要的一个模块,基于Thrift开发,所以有时也被称为Thrift Server,主要功能是提供客户端操作Hive的接口,默认端口是10000。最早提供该功能的是HiveServer(为了和HS2区分,有时也叫HS1,也是基于Thrift开发),因为缺乏并发支持和认证机制,在Hive 1.0.0版本中被移除,引入了HS2。HS2解决了HS1缺乏的并发和认证功能,并增加了一些新的特性,有兴趣的可以看一下这篇文章:How HiveServer2 Brings Security and Concurrency to Apache Hive。
需要注意的是:HS2也是Hive的一个模块,和Hive的Driver、Compiler、Execution Engine模块一样。比如看下面的另外两种种Hive的架构图:
这幅图中蓝色的"Hive Server"就是HS2。
这幅图中那个"Thrift Server"就是HS2。
但实际中,会将Hive的HS2、Driver、Compiler、Execution Engine以及一个基于Jetty的Web UI(默认端口是10002)全部实现在一个进程里面,而这个进程对应的程序文件就是$HIVE_HOME/bin/hiveserver2
。所以要注意HS2是一个单独的模块,而hiveserver2是包含HS2模块以及其它一些模块的一个整体的服务,有些地方对于HS2的描述没有对此作区分,但我们心里要清楚。
所以,正常情况我们可以看到Hive最多就三个进程:MetaStore、HiveServer2、WebHCat(可选,做Hive元数据管理的)。
更多关于HS2的信息,可参加:HiveServer2 Overview.
MetaStore是Hive必不可少的一个模块。
MetaStore提供了两个非常重要的功能:数据抽象(data abstraction)和数据发现(data discovery)。
MetaStore里面的存储使用的是JPOX ORM(Data Nucleus) 方案,所以任何支持该方案的存储都可以作为MetaStore后端的存储,比如Apache Derby以及大多数RDBMS都支持。目前MetaStore支持的RDBMS见这里.
MetaStore有两种部署方式:
MetaStore的E/R图见这里。
最后来一张图帮助理解:
MetaStore的核心配置有4个:
;使用MySQL时的值可以为:
jdbc:mysql://org.apache.derby.jdbc.EmbeddedDriver
, MySQL为com.mysql.jdbc.Driver
.配置在hive-site.xml或者hivemetastore-site.xml里面均可,后者优先级高于前者。
HCatalog是Apache下面的一个元数据管理工具,后来被集成到Hive里面,是的一些第三方工具比如Pig、MR、Spark等可以通过HCatalog直接访问HDFS上的数据。而WebHCat(以前称为Templeton)提供访问HCatalog的REST API。对于Hive而言,HCatalog和WebHCat是非必须的。
Hive的架构就介绍到这里。
References:
]]>官方对于Hive的介绍如下:
The Apache Hive™ data warehouse software facilitates reading, writing, and managing large datasets residing in distributed storage and queried using SQL syntax.
对于Hive的总体介绍先到这里,我们先安装使用一下。
Hive的安装也比较简单,和Hadoop那些类似,这里简单介绍一下步骤(本文使用的是Hive 2.3.3版本):
下载二进制安装包并解压,然后设置HIVE_HOME
和PATH
环境变量:
export HIVE_HOME=$HOME/software/apache-hive-2.3.3-bin
export PATH=$HIVE_HOME/bin:$PATH
在HDFS上面创建Hive需要的目录并设置权限:
$HADOOP_HOME/bin/hadoop fs -mkdir /tmp
$HADOOP_HOME/bin/hadoop fs -mkdir /user/hive/warehouse
$HADOOP_HOME/bin/hadoop fs -chmod g+w /tmp
$HADOOP_HOME/bin/hadoop fs -chmod g+w /user/hive/warehouse
初始化Hive的Metastore:
$HIVE_HOME/bin/schematool -dbType derby -initSchema
为了后面能正常使用Hive自带的beeline,需要在$HADOOP_HOME/etc/hadoop/core-site.xml中增加如下设置(将里面的allan改为你自己的用户名):
<property>
<name>hadoop.proxyuser.allan.groups</name>
<value>*</value>
</property>
<property>
<name>hadoop.proxyuser.allan.hosts</name>
<value>*</value>
</property>
同时,为了避免用户权限问题,在$HADOOP_HOME/etc/hadoop/hdfs-site.xml中关闭HDFS的权限校验:
<property>
<name>dfs.permissions.enabled</name>
<value>false</value>
</property>
至此,一个最简单的Hive就安装完成了,我们可以通过Hive提供的CLI(Command Line Interface)访问Hive。Hive目前提供了两个CLI:HiveCLI(对应hive
命令)和Beeline(对应beeline
命令),前者现在已经废弃,不推荐使用;现在推荐使用Beeline。所以本文也就只介绍Beeline了。使用Beeline需要先启动HiveServer2((确保HDFS正常运行)):
$HIVE_HOME/bin/hiveserver2`。
默认HiveServer2监听端口为10000,我们使用Beeline连接Hive:
cd $HIVE_HOME
bin/beeline -u jdbc:hive2://localhost:10000
连接成功之后,我们可以执行一些HQL来感受一下Hive,后面部分再继续介绍涉及到的概念:
# 创建pokes表
0: jdbc:hive2://localhost:10000> CREATE TABLE pokes (foo INT, bar STRING);
No rows affected (0.412 seconds)
0: jdbc:hive2://localhost:10000> desc pokes;
+-----------+------------+----------+
| col_name | data_type | comment |
+-----------+------------+----------+
| foo | int | |
| bar | string | |
+-----------+------------+----------+
2 rows selected (0.195 seconds)
# 创建invites表,该表有一个分区
0: jdbc:hive2://localhost:10000> CREATE TABLE invites (foo INT, bar STRING) PARTITIONED BY (ds STRING);
No rows affected (0.158 seconds)
0: jdbc:hive2://localhost:10000> desc invites;
+--------------------------+-----------------------+-----------------------+
| col_name | data_type | comment |
+--------------------------+-----------------------+-----------------------+
| foo | int | |
| bar | string | |
| ds | string | |
| | NULL | NULL |
| # Partition Information | NULL | NULL |
| # col_name | data_type | comment |
| | NULL | NULL |
| ds | string | |
+--------------------------+-----------------------+-----------------------+
8 rows selected (0.234 seconds)
# 往pokes表里面导入数据
0: jdbc:hive2://localhost:10000> LOAD DATA LOCAL INPATH './examples/files/kv1.txt' OVERWRITE INTO TABLE pokes;
No rows affected (1.016 seconds)
# 查看数据
0: jdbc:hive2://localhost:10000> select * from pokes limit 5;
+------------+------------+
| pokes.foo | pokes.bar |
+------------+------------+
| 238 | val_238 |
| 86 | val_86 |
| 311 | val_311 |
| 27 | val_27 |
| 165 | val_165 |
+------------+------------+
5 rows selected (3.1 seconds)
# 往invites表里面导入数据,并指定分区
0: jdbc:hive2://localhost:10000> LOAD DATA LOCAL INPATH './examples/files/kv2.txt' OVERWRITE INTO TABLE invites PARTITION (ds='2008-08-15');
No rows affected (0.619 seconds)
0: jdbc:hive2://localhost:10000> LOAD DATA LOCAL INPATH './examples/files/kv3.txt' OVERWRITE INTO TABLE invites PARTITION (ds='2008-08-08');
No rows affected (0.619 seconds)
0: jdbc:hive2://localhost:10000> select * from invites limit 5;
+--------------+--------------+-------------+
| invites.foo | invites.bar | invites.ds |
+--------------+--------------+-------------+
| 238 | val_238 | 2008-08-08 |
| NULL | | 2008-08-08 |
| 311 | val_311 | 2008-08-08 |
| NULL | val_27 | 2008-08-08 |
| NULL | val_165 | 2008-08-08 |
+--------------+--------------+-------------+
5 rows selected (0.362 seconds)
# 创建新db
0: jdbc:hive2://localhost:10000> create database new_db;
No rows affected (0.547 seconds)
0: jdbc:hive2://localhost:10000> use new_db;
No rows affected (0.192 seconds)
0: jdbc:hive2://localhost:10000> CREATE TABLE pokes1 (foo INT, bar STRING);
No rows affected (0.216 seconds)
0: jdbc:hive2://localhost:10000> LOAD DATA LOCAL INPATH './examples/files/kv1.txt' OVERWRITE INTO TABLE pokes1;
No rows affected (0.524 seconds)
可以看到HQL和SQL非常的类似,本文的重点不是介绍HQL,所以这里就先不细述了,有兴趣的请移步LanguageManual.
上一节我们看到Hive与传统的RDBMS非常的像,在Hive的数据模型里面主要包含如下一些概念:
default
。invites
表中有一个分区列ds
,里面存储的是时间,这样当我们根据时间查询的时候,就可以只查询某些分区。需要注意的是:分区列名为2008-08-08并不代表里面所有的数据都是这一天的,这个要应用程序自己去保证。注意:分区和桶都是可选的,一个表可以没有分区和桶。
前面提到过,Hive的数据是存储在HDFS上的(存储目录由hive.metastore.warehouse.dir
指定,默认值为/user/hive/warehouse
),所以不同于RDBMS,Hive数据模型中的这些概念对应到后台实质就是一些目录(bucket可能是文件)。比如,我们看一下上一节中我们建的数据库、表以及数据是如何存储的:
➜ hadoop fs -ls -R /user/hive/warehouse
drwxrwxr-x - anonymous supergroup 0 2018-09-12 14:58 /user/hive/warehouse/invites
drwxrwxr-x - anonymous supergroup 0 2018-09-12 14:58 /user/hive/warehouse/invites/ds=2008-08-08
-rwxrwxr-x 1 anonymous supergroup 216 2018-09-12 14:58 /user/hive/warehouse/invites/ds=2008-08-08/kv3.txt
drwxrwxr-x - anonymous supergroup 0 2018-09-12 14:58 /user/hive/warehouse/invites/ds=2008-08-15
-rwxrwxr-x 1 anonymous supergroup 5791 2018-09-12 14:58 /user/hive/warehouse/invites/ds=2008-08-15/kv2.txt
drwxrwxr-x - anonymous supergroup 0 2018-09-12 15:13 /user/hive/warehouse/new_db.db
drwxrwxr-x - anonymous supergroup 0 2018-09-12 15:13 /user/hive/warehouse/new_db.db/pokes1
-rwxrwxr-x 1 anonymous supergroup 5812 2018-09-12 15:13 /user/hive/warehouse/new_db.db/pokes1/kv1.txt
drwxrwxr-x - anonymous supergroup 0 2018-09-12 14:57 /user/hive/warehouse/pokes
-rwxrwxr-x 1 anonymous supergroup 5812 2018-09-12 14:57 /user/hive/warehouse/pokes/kv1.txt
可以看到:
defalut
里面的表对应的目录和其它的数据库目录是平级的。而其它数据库里面的表则在数据库目录之下。如果我们删掉数据库、表或者分区,对应的目录也会删除(外部表例外,以后再介绍)。
因为Hive底层使用Hadoop,同时做了数据抽象封装(库、表、分区、桶),还提供了类SQL的操作,使得上述的目标很容易达成。另外,Hive也支持用户自定义函数,增加了灵活性。
如果看Hive的官方文档的话,一定会被各种版本号搞得吐血。文档里面使用的是一个版本号,而发布的又是另外一个版本号,我也没有逐一梳理过,官方给了几个:
PS:前面为Release的版本号,也就是我们经常说的Hive的版本号,而后面的是内部的版本号,主要在文档里面出现
关于Hive的版本策略参见:Understanding Hive Branches.
首先Hive需要两个临时目录:
/tmp/hive-<username>
(可以通过hive.exec.scratchdir
配置项修改)/tmp/<username>
目录(硬编码,不可更改)这些目录主要用于存储客户端查询时的一些临时数据或者中间数据,比如往Hive表中写数据,都是先写到HDFS上的临时目录,然后再move过去。正常情况下,客户端查询结束后会删掉这些临时数据,如果客户端异常,数据可能会残留。
Hive最主要的配置文件为hive-site.xml,在$HIVE_CONF_DIR
指定的目录,默认为$HIVE_HOME/conf
。现在Hive的安装包里面没有hive-site.xml,只有hive-default.xml.template。后者是根据HiveConf.java文件里面的默认配置生成的,但Hive并不会使用这个配置文件。如果我们需要配置Hive,需要创建hive-site.xml文件,或者直接复制hive-default.xml.template为hive-site.xml,然后修改里面的值。
同时Hive还支持hivemetastore-site.xml和hiveserver2-site.xml两个配置文件,MetaStore启动的时候除了会加载hive-site.xml,还会加载hivemetastore-site.xml;HS2启动的时候除了会加载hive-site.xml,还会加载hiveserver2-site.xml,如果HS2使用的是Embedded的MetaStore,也会加载hivemetastore-site.xml。
整个配置的优先级从低到高依次为:
hive-site.xml -> hivemetastore-site.xml -> hiveserver2-site.xml -> '-hiveconf' commandline parameters -> set命令。
本文主要介绍了Hive的安装和基本使用,以及Hive的数据模型,可以说是非常粗的从“外面”介绍了一下Hive,旨在对Hive有一个直观的感受。下一篇文章会从“里面”介绍一下Hive,有兴趣的可以关注一下。
References:
]]>