Hive的分桶和采样

栏目: 服务器 · 发布时间: 6年前

内容简介:Hive除了有其实不管是分区还是分桶都是为了更好的管理数据。分区将表的数据分到不同的目录存储,从而在查询的时候可以通过where条件过滤一部分数据,减小查询的数据量从而提高性能。但分区的这种机制往往在数据符合以下条件时才会表现的比较好:然而,并非所有场景都是符合上面两条的。比如比较常见的按照国家等地理位置去分区的时候,可能几个大国的数据就占了总数据量的百分之七八十,而剩下的所有国家只占了百分之二三十,也就是不符合上面的第二条。这个时候分区的好处就大打折扣了。为了克服这个问题,Hive增加了分桶的机制。我们

Hive除了有 分区(Partition) ,还有 分桶(Bucket) ,上一篇文章《Hive的分区》中介绍了分区,本文接着介绍分桶,以及和分桶经常一起使用的 采样(Sampling)

其实不管是分区还是分桶都是为了更好的管理数据。分区将表的数据分到不同的目录存储,从而在查询的时候可以通过where条件过滤一部分数据,减小查询的数据量从而提高性能。但分区的这种机制往往在数据符合以下条件时才会表现的比较好:

  • 分区数目为有限个 :一般也不能太大,不然太多的文件和目录对于HDFS的NameNode会造成比较大的内存压力。
  • 各个分区的数据量比较均衡 :这个好理解,如果90%的数据跑到一个分区去,那分区的意义就不是很大了。

然而,并非所有场景都是符合上面两条的。比如比较常见的按照国家等地理位置去分区的时候,可能几个大国的数据就占了总数据量的百分之七八十,而剩下的所有国家只占了百分之二三十,也就是不符合上面的第二条。这个时候分区的好处就大打折扣了。为了克服这个问题,Hive增加了分桶的机制。我们先介绍一些分桶相关的基础概念,然后通过例子再做说明。

1. 分桶

1.1 基础理论

分桶的概念其实比较简单:指定桶的个数,选中表中的若干个列,通过对数据中这些列的值做哈希决定数据应该存储到哪个桶里面去,公式表示为: bucket num = hash_function(bucketing_column) mod num_buckets ,不同的数据类型哈希函数不同。关于分桶还有以下一些关键点:

CLUSTERED BY

分桶也可以带来如下一些好处:

  • 分桶表比不分桶表的采样(samping)效率高 。Hive提供了采样机制,用于我们从大量数据中提取一部分来做测试或调试,而分桶可以帮助我们更好的采样(见后面的例子)。
  • Map过程中的join会更快 。主要有两方面原因:1. 分桶后,文件大小均衡。2. join的时候左表可以计算对应的右表符合条件的行在哪个桶里面,直接去获取数据即可。
  • 和分区一样,分桶后查询数据会更快
  • 我们在桶内做排序,这样可以让Map里面的join操作更快速。

下面我们看一个例子。

1.2 例子展示

场景需求: 还是使用上篇文章里面的用户数据 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.enforce.bucketing :如果建表时设置了分桶,插入数据的时候是否强制分桶。Hive 2.x版本之前默认为false,之后去掉了该配置,并将默认情况改为true。见 HIVE-12331 .
  • hive.enforce.sorting :如果建表时设置了分桶,插入数据的时候是排序,Hive 2.x版本之前默认为false,之后去掉了该配置,并将默认情况改为true。见 HIVE-12331 .

在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份,因为数据量太小,所以很多桶内是空的。如果数据量大的话,各个桶的数据量会比较均衡。

2. 采样

从大量数据中采样得到少量数据进行测试或者调试是数据分析中非常常见的操作,抛开业务在数据库我们可以使用 LIMIT 语句实现该功能,但Hive提供了一个 TABLESAMPLE 语法用来更好的实现采样, TABLESAMPLE 语句可以放到任何FROM语句中。目前支持两种采样方式:基于表的采样 Sampling Bucketized Table 和基于块的采样 Block Sampling

2.1 Sampling Bucketized Table

基于分桶表的采样的语法格式如下:

table_sample: TABLESAMPLE (BUCKET x OUT OF y [ON colname] [table_alias])
  • colname 表示基于表中哪个字段进行采样,这个字段可以是分桶列中的某个字段,也可以不是;还可以使用 rand() 表示基于整行而不是单独某个字段进行采样。
  • x out of y:表示要取哪些桶的数据。假设表有32个桶:

    • 3 out of 32 表示总共取 32/32=1 个桶的数据,取的是第3个桶的数据;
    • 3 out of 16 表示总共取 32/16=2 个桶的数据,分别取第3个、第19(1+16)个桶的数据;
    • 3 out of 64 表示总共取 32/64=0.5 个桶的数据,取第3个桶一半的数据。
  • table_alias 给表起的别名,类似 MySQL 的AS。

一些例子:

# 基于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)

需要注意的是这种采样方式 并不要求表一定要是分桶的 ,如果没有分桶或者分桶了但采样的字段不在分桶字段里面,那也是可以正常采样的,只不过采样时会扫描全表数据,不是很高效而已。所以大多数情况这种采样方式都是和分桶一起使用的,采样的字段就是分桶的字段,这样采样时只扫描对应的桶就行,可以大大提高效率。

2.2 Block Sampling

基于块的采样是后来新增的一项功能(从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)

基于行数的采样和前面两种方式不太一样:

  • 没有数据格式的限制;
  • 采集的条数n会在每个分片(split)都运行一次,所以采集到的总条数和输入的分片数也有关系。
# 从输入的每个分片从采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

-->

Hive除了有 分区(Partition) ,还有 分桶(Bucket) ,上一篇文章《Hive的分区》中介绍了分区,本文接着介绍分桶,以及和分桶经常一起使用的 采样(Sampling)

其实不管是分区还是分桶都是为了更好的管理数据。分区将表的数据分到不同的目录存储,从而在查询的时候可以通过where条件过滤一部分数据,减小查询的数据量从而提高性能。但分区的这种机制往往在数据符合以下条件时才会表现的比较好:

  • 分区数目为有限个 :一般也不能太大,不然太多的文件和目录对于HDFS的NameNode会造成比较大的内存压力。
  • 各个分区的数据量比较均衡 :这个好理解,如果90%的数据跑到一个分区去,那分区的意义就不是很大了。

然而,并非所有场景都是符合上面两条的。比如比较常见的按照国家等地理位置去分区的时候,可能几个大国的数据就占了总数据量的百分之七八十,而剩下的所有国家只占了百分之二三十,也就是不符合上面的第二条。这个时候分区的好处就大打折扣了。为了克服这个问题,Hive增加了分桶的机制。我们先介绍一些分桶相关的基础概念,然后通过例子再做说明。

1. 分桶

1.1 基础理论

分桶的概念其实比较简单:指定桶的个数,选中表中的若干个列,通过对数据中这些列的值做哈希决定数据应该存储到哪个桶里面去,公式表示为: bucket num = hash_function(bucketing_column) mod num_buckets ,不同的数据类型哈希函数不同。关于分桶还有以下一些关键点:

CLUSTERED BY

分桶也可以带来如下一些好处:

  • 分桶表比不分桶表的采样(samping)效率高 。Hive提供了采样机制,用于我们从大量数据中提取一部分来做测试或调试,而分桶可以帮助我们更好的采样(见后面的例子)。
  • Map过程中的join会更快 。主要有两方面原因:1. 分桶后,文件大小均衡。2. join的时候左表可以计算对应的右表符合条件的行在哪个桶里面,直接去获取数据即可。
  • 和分区一样,分桶后查询数据会更快
  • 我们在桶内做排序,这样可以让Map里面的join操作更快速。

下面我们看一个例子。

1.2 例子展示

场景需求: 还是使用上篇文章里面的用户数据 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.enforce.bucketing :如果建表时设置了分桶,插入数据的时候是否强制分桶。Hive 2.x版本之前默认为false,之后去掉了该配置,并将默认情况改为true。见 HIVE-12331 .
  • hive.enforce.sorting :如果建表时设置了分桶,插入数据的时候是排序,Hive 2.x版本之前默认为false,之后去掉了该配置,并将默认情况改为true。见 HIVE-12331 .

在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份,因为数据量太小,所以很多桶内是空的。如果数据量大的话,各个桶的数据量会比较均衡。

2. 采样

从大量数据中采样得到少量数据进行测试或者调试是数据分析中非常常见的操作,抛开业务在数据库我们可以使用 LIMIT 语句实现该功能,但Hive提供了一个 TABLESAMPLE 语法用来更好的实现采样, TABLESAMPLE 语句可以放到任何FROM语句中。目前支持两种采样方式:基于表的采样 Sampling Bucketized Table 和基于块的采样 Block Sampling

2.1 Sampling Bucketized Table

基于分桶表的采样的语法格式如下:

table_sample: TABLESAMPLE (BUCKET x OUT OF y [ON colname] [table_alias])
  • colname 表示基于表中哪个字段进行采样,这个字段可以是分桶列中的某个字段,也可以不是;还可以使用 rand() 表示基于整行而不是单独某个字段进行采样。
  • x out of y:表示要取哪些桶的数据。假设表有32个桶:

    • 3 out of 32 表示总共取 32/32=1 个桶的数据,取的是第3个桶的数据;
    • 3 out of 16 表示总共取 32/16=2 个桶的数据,分别取第3个、第19(1+16)个桶的数据;
    • 3 out of 64 表示总共取 32/64=0.5 个桶的数据,取第3个桶一半的数据。
  • table_alias 给表起的别名,类似MySQL的AS。

一些例子:

# 基于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)

需要注意的是这种采样方式 并不要求表一定要是分桶的 ,如果没有分桶或者分桶了但采样的字段不在分桶字段里面,那也是可以正常采样的,只不过采样时会扫描全表数据,不是很高效而已。所以大多数情况这种采样方式都是和分桶一起使用的,采样的字段就是分桶的字段,这样采样时只扫描对应的桶就行,可以大大提高效率。

2.2 Block Sampling

基于块的采样是后来新增的一项功能(从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)

基于行数的采样和前面两种方式不太一样:

  • 没有数据格式的限制;
  • 采集的条数n会在每个分片(split)都运行一次,所以采集到的总条数和输入的分片数也有关系。
# 从输入的每个分片从采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


以上所述就是小编给大家介绍的《Hive的分桶和采样》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Numerical Recipes 3rd Edition

Numerical Recipes 3rd Edition

William H. Press、Saul A. Teukolsky、William T. Vetterling、Brian P. Flannery / Cambridge University Press / 2007-9-6 / GBP 64.99

Do you want easy access to the latest methods in scientific computing? This greatly expanded third edition of Numerical Recipes has it, with wider coverage than ever before, many new, expanded and upd......一起来看看 《Numerical Recipes 3rd Edition》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

MD5 加密
MD5 加密

MD5 加密工具