您现在的位置 >> Hadoop教程 >> Hadoop实战 >> 专题  
 

hive开发指导(2)

【作者:Hadoop实战专家】【关键词:序列化 配置文件 】 【点击:89102次】【2014-01-0】
* 并不是所有的聚合操作都需要在 Reduce 端完成,很多聚合操作都可以先在 Map 端进行部分聚合,最后在 Reduce 端得出最终结果。当用户的数据文件格式不能被当前 Hive 所识别的时候,可以自定义文件格式。即参数声明覆盖命令行参数,命令行参数覆盖配置文件设定。  

相关热门搜索:hadoop2配置文件详解 hdfs 配置文件

大数据标签:hadoop hdfs hive bigdata

REGEX Column Specification

SELECT 语句可以使用正则表达式做列选择,下面的语句查询除了 ds 和 hr 之外的所有列:

SELECT `(ds|hr)?+.+` FROM sales

Join

Syntax

join_table:
table_reference JOIN table_factor [join_condition]
| table_reference {LEFT|RIGHT|FULL} [OUTER] JOIN table_reference join_condition
| table_reference LEFT SEMI JOIN table_reference join_condition

table_reference:
table_factor
| join_table

table_factor:
tbl_name [alias]
| table_subquery alias
| ( table_references )

join_condition:
ON equality_expression ( AND equality_expression )*

equality_expression:
expression = expression

Hive 只支持等值连接(equality joins)、外连接(outer joins)和(left semi joins???)。Hive 不支持所有非等值的连接,因为非等值连接非常难转化到 map/reduce 任务。另外,Hive 支持多于 2 个表的连接。

写 join 查询时,需要注意几个关键点:

1. 只支持等值join,例如:

SELECT a.* FROM a JOIN b ON (a.id = b.id)
SELECT a.* FROM a JOIN b
ON (a.id = b.id AND a.department = b.department)

是正确的,然而:

SELECT a.* FROM a JOIN b ON (a.id  b.id)

是错误的。

2. 可以 join 多于 2 个表,例如

SELECT a.val, b.val, c.val FROM a JOIN b
ON (a.key = b.key1) JOIN c ON (c.key = b.key2)

如果join中多个表的 join key 是同一个,则 join 会被转化为单个 map/reduce 任务,例如:

SELECT a.val, b.val, c.val FROM a JOIN b
ON (a.key = b.key1) JOIN c
ON (c.key = b.key1)

被转化为单个 map/reduce 任务,因为 join 中只使用了 b.key1 作为 join key。

SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key1)
JOIN c ON (c.key = b.key2)

而这一 join 被转化为 2 个 map/reduce 任务。因为 b.key1 用于第一次 join 条件,而 b.key2 用于第二次 join。

join 时,每次 map/reduce 任务的逻辑是这样的:reducer 会缓存 join 序列中除了最后一个表的所有表的记录,再通过最后一个表将结果序列化到文件系统。这一实现有助于在 reduce 端减少内存的使用量。实践中,应该把最大的那个表写在最后(否则会因为缓存浪费大量内存)。例如:

SELECT a.val, b.val, c.val FROM a
JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key1)

所有表都使用同一个 join key(使用 1 次 map/reduce 任务计算)。Reduce 端会缓存 a 表和 b 表的记录,然后每次取得一个 c 表的记录就计算一次 join 结果,类似的还有:

SELECT a.val, b.val, c.val FROM a
JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key2)

这里用了 2 次 map/reduce 任务。第一次缓存 a 表,用 b 表序列化;第二次缓存第一次 map/reduce 任务的结果,然后用 c 表序列化。

LEFT,RIGHT和FULL OUTER关键字用于处理join中空记录的情况,例如:

SELECT a.val, b.val FROM a LEFT OUTER JOIN b ON (a.key=b.key)

对应所有 a 表中的记录都有一条记录输出。输出的结果应该是 a.val, b.val,当 a.key=b.key 时,而当 b.key 中找不到等值的 a.key 记录时也会输出 a.val, NULL。“FROM a LEFT OUTER JOIN b”这句一定要写在同一行——意思是 a 表在 b 表的左边,所以 a 表中的所有记录都被保留了;“a RIGHT OUTER JOIN b”会保留所有 b 表的记录。OUTER JOIN 语义应该是遵循标准 SQL spec的。

Join 发生在 WHERE 子句之前。如果你想限制 join 的输出,应该在 WHERE 子句中写过滤条件——或是在 join 子句中写。这里面一个容易混淆的问题是表分区的情况:

SELECT a.val, b.val FROM a LEFT OUTER JOIN b ON (a.key=b.key)
WHERE a.ds='2009-07-07' AND b.ds='2009-07-07'

会 join a 表到 b 表(OUTER JOIN),列出 a.val 和 b.val 的记录。WHERE 从句中可以使用其他列作为过滤条件。但是,如前所述,如果 b 表中找不到对应 a 表的记录,b 表的所有列都会列出 NULL,包括 ds 列。也就是说,join 会过滤 b 表中不能找到匹配 a 表 join key 的所有记录。这样的话,LEFT OUTER 就使得查询结果与 WHERE 子句无关了。解决的办法是在 OUTER JOIN 时使用以下语法:

SELECT a.val, b.val FROM a LEFT OUTER JOIN b
ON (a.key=b.key AND b.ds='2009-07-07' AND a.ds='2009-07-07')

这一查询的结果是预先在 join 阶段过滤过的,所以不会存在上述问题。这一逻辑也可以应用于 RIGHT 和 FULL 类型的 join 中。

Join 是不能交换位置的。无论是 LEFT 还是 RIGHT join,都是左连接的。

SELECT a.val1, a.val2, b.val, c.val
FROM a
JOIN b ON (a.key = b.key)
LEFT OUTER JOIN c ON (a.key = c.key)

先 join a 表到 b 表,丢弃掉所有 join key 中不匹配的记录,然后用这一中间结果和 c 表做 join。这一表述有一个不太明显的问题,就是当一个 key 在 a 表和 c 表都存在,但是 b 表中不存在的时候:整个记录在第一次 join,即 a JOIN b 的时候都被丢掉了(包括a.val1,a.val2和a.key),然后我们再和 c 表 join 的时候,如果 c.key 与 a.key 或 b.key 相等,就会得到这样的结果:NULL, NULL, NULL, c.val。

LEFT SEMI JOIN 是 IN/EXISTS 子查询的一种更高效的实现。Hive 当前没有实现 IN/EXISTS 子查询,所以你可以用 LEFT SEMI JOIN 重写你的子查询语句。LEFT SEMI JOIN 的限制是, JOIN 子句中右边的表只能在 ON 子句中设置过滤条件,在 WHERE 子句、SELECT 子句或其他地方过滤都不行。

SELECT a.key, a.value
FROM a
WHERE a.key in
(SELECT b.key
FROM B);

可以被重写为:

SELECT a.key, a.val
FROM a LEFT SEMI JOIN b on (a.key = b.key)

Hive 针对不同的查询进行了优化,优化可以通过配置进行控制,本文将介绍部分优化的策略以及优化控制选项。

列裁剪(Column Pruning)
在读数据的时候,只读取查询中需要用到的列,而忽略其他列。例如,对于查询:
SELECT a,b FROM T WHERE e < 10;其中,T 包含 5 个列 (a,b,c,d,e),列 c,d 将会被忽略,只会读取a, b, e 列
这个选项默认为真: hive.optimize.cp = true

分区裁剪(Partition Pruning)

在查询的过程中减少不必要的分区。例如,对于下列查询:
SELECT * FROM (SELECT c1, COUNT(1)
FROM T GROUP BY c1) subq
WHERE subq.prtn = 100;

SELECT * FROM T1 JOIN
(SELECT * FROM T2) subq ON (T1.c1=subq.c2)
WHERE subq.prtn = 100;会在子查询中就考虑 subq.prtn = 100 条件,从而减少读入的分区数目。
此选项默认为真:hive.optimize.pruner=true
Join

在使用写有 Join 操作的查询语句时有一条原则:应该将条目少的表/子查询放在 Join 操作符的左边。原因是在 Join 操作的 Reduce 阶段,位于 Join 操作符左边的表的内容会被加载进内存,将条目少的表放在左边,可以有效减少发生 OOM 错误的几率。
对于一条语句中有多个 Join 的情况,如果 Join 的条件相同,比如查询:
INSERT OVERWRITE TABLE pv_users
SELECT pv.pageid, u.age FROM page_view p
JOIN user u ON (pv.userid = u.userid)
JOIN newuser x ON (u.userid = x.userid);
* 如果 Join 的 key 相同,不管有多少个表,都会则会合并为一个 Map-Reduce
* 一个 Map-Reduce 任务,而不是 ‘n’ 个
* 在做 OUTER JOIN 的时候也是一样

如果 Join 的条件不相同,比如:
INSERT OVERWRITE TABLE pv_users
SELECT pv.pageid, u.age FROM page_view p
JOIN user u ON (pv.userid = u.userid)
JOIN newuser x on (u.age = x.age);Map-Reduce 的任务数目和 Join 操作的数目是对应的,上述查询和以下查询是等价的:
INSERT OVERWRITE TABLE tmptable
SELECT * FROM page_view p JOIN user u
ON (pv.userid = u.userid);

INSERT OVERWRITE TABLE pv_users
SELECT x.pageid, x.age FROM tmptable x
JOIN newuser y ON (x.age = y.age);Map JoinJoin 操作在 Map 阶段完成,不再需要Reduce,前提条件是需要的数据在 Map 的过程中可以访问到。比如查询:
INSERT OVERWRITE TABLE pv_users
SELECT /*+ MAPJOIN(pv) */ pv.pageid, u.age
FROM page_view pv
JOIN user u ON (pv.userid = u.userid);可以在 Map 阶段完成 Join,如图所示:

5.PNG (60.78 KB, 下载次数: 0)

  

2013-12-22 19:51 上传

相关的参数为:

* hive.join.emit.interval = 1000 How many rows in the right-most join operand Hive should buffer before emitting the join result.
* hive.mapjoin.size.key = 10000
* hive.mapjoin.cache.numrows = 10000

Group By
* Map 端部分聚合:

* 并不是所有的聚合操作都需要在 Reduce 端完成,很多聚合操作都可以先在 Map 端进行部分聚合,最后在 Reduce 端得出最终结果。
* 基于 Hash
* 参数包括:

* hive.map.aggr = true 是否在 Map 端进行聚合,默认为 True
* hive.groupby.mapaggr.checkinterval = 100000 在 Map 端进行聚合操作的条目数目

* 有数据倾斜的时候进行负载均衡

* hive.groupby.skewindata = false

当选项设定为 true,生成的查询计划会有两个 MR Job。第一个 MR Job 中,Map 的输出结果集合会随机分布到 Reduce 中,每个 Reduce 做部分聚合操作,并输出结果,这样处理的结果是相同的 Group By Key 有可能被分发到不同的 Reduce 中,从而达到负载均衡的目的;第二个 MR Job 再根据预处理的数据结果按照 Group By Key 分布到 Reduce 中(这个过程可以保证相同的 Group By Key 被分布到同一个 Reduce 中),最后完成最终的聚合操作。

合并小文件文件数目过多,会给 HDFS 带来压力,并且会影响处理效率,可以通过合并 Map 和 Reduce 的结果文件来消除这样的影响:

* hive.merge.mapfiles = true 是否和并 Map 输出文件,默认为 True
* hive.merge.mapredfiles = false 是否合并 Reduce 输出文件,默认为 False
* hive.merge.size.per.task = 256*1000*1000 合并文件的大小

Hive 是一个很开放的系统,很多内容都支持用户定制,包括:

* 文件格式:Text File,Sequence File
* 内存中的数据格式: Java Integer/String, Hadoop IntWritable/Text
* 用户提供的 map/reduce 脚本:不管什么语言,利用 stdin/stdout 传输数据
* 用户自定义函数: Substr, Trim, 1 – 1
* 用户自定义聚合函数: Sum, Average…… n – 1

File Format

TextFile

SequenceFIle

RCFFile

Data type

Text Only

Text/Binary

Text/Binary

Internal Storage Order

Row-based

Row-based

Column-based

Compression

File Based

Block Based

Block Based

Splitable

YES

YES

YES

Splitable After Compression

No

YES

YES

CREATE TABLE mylog ( user_id BIGINT, page_url STRING, unix_time INT)
STORED AS TEXTFILE;当用户的数据文件格式不能被当前 Hive 所识别的时候,可以自定义文件格式。可以参考 contrib/src/java/org/apache/hadoop/hive/contrib/fileformat/base64 中的例子。写完自定义的格式后,在创建表的时候指定相应的文件格式就可以:
CREATE TABLE base64_test(col1 STRING, col2 STRING)
STORED AS
INPUTFORMAT 'org.apache.hadoop.hive.contrib.
fileformat.base64.Base64TextInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.contrib.
fileformat.base64.Base64TextOutputFormat';SerDeSerDe 是 Serialize/Deserilize 的简称,目的是用于序列化和反序列化。序列化的格式包括:

* 分隔符(tab、逗号、CTRL-A)
* Thrift 协议

反序列化(内存内):

* Java Integer/String/ArrayList/HashMap
* Hadoop Writable 类
* 用户自定义类

目前存在的 Serde 见下图:

6.PNG (35.17 KB, 下载次数: 0)

  

2013-12-22 19:51 上传

其中,LazyObject 只有在访问到列的时候才进行反序列化。 BinarySortable:保留了排序的二进制格式。
当存在以下情况时,可以考虑增加新的 SerDe:

* 用户的数据有特殊的序列化格式,当前的 Hive 不支持,而用户又不想在将数据加载至 Hive 前转换数据格式。
* 用户有更有效的序列化磁盘数据的方法。

用户如果想为 Text 数据增加自定义 Serde ,可以参照 contrib/src/java/org/apache/hadoop/hive/contrib/serde2/RegexSerDe.java 中的例子。RegexSerDe 利用用户提供的正则表倒是来反序列化数据,例如:
CREATE TABLE apache_log(
host STRING,
identity STRING,
user STRING,
time STRING,
request STRING,
status STRING,
size STRING,
referer STRING,
agent STRING)
ROW FORMAT
SERDE 'org.apache.hadoop.hive.contrib.serde2.RegexSerDe'
WITH SERDEPROPERTIES
( "input.regex" = "([^ ]*) ([^ ]*) ([^ ]*) (-|\\[[^\\]]*\\])
([^ \"]*|\"[^\"]*\") (-|[0-9]*) (-|[0-9]*)(?: ([^ \"]*|\"[^\"]*\")
([^ \"]*|\"[^\"]*\"))?",
"output.format.string" = "%1$s %2$s %3$s %4$s %5$s %6$s %7$s %8$s %9$s";)
STORED AS TEXTFILE;用户如果想为 Binary 数据增加自定义的 SerDE,可以参考例子:serde/src/java/org/apache/hadoop/hive/serde2 /binarysortable,例如:
CREATE TABLE mythrift_table
ROW FORMAT SERDE
'org.apache.hadoop.hive.contrib.serde2.thrift.ThriftSerDe'
WITH SERDEPROPERTIES (
"serialization.class" = "com.facebook.serde.tprofiles.full",
"serialization.format" = "com.facebook.thrift.protocol.TBinaryProtocol";);

Map/Reduce 脚本(Transform)

用户可以自定义 Hive 使用的 Map/Reduce 脚本,比如:
FROM (
SELECT TRANSFORM(user_id, page_url, unix_time)
USING 'page_url_to_id.py'
AS (user_id, page_id, unix_time)
FROM mylog
DISTRIBUTE BY user_id
SORT BY user_id, unix_time)
mylog2
SELECT TRANSFORM(user_id, page_id, unix_time)
USING 'my_python_session_cutter.py' AS (user_id, session_info);Map/Reduce 脚本通过 stdin/stdout 进行数据的读写,调试信息输出到 stderr。

UDF(User-Defined-Function)
用户可以自定义函数对数据进行处理,例如:
add jar build/ql/test/test-udfs.jar;
CREATE TEMPORARY FUNCTION testlength
AS 'org.apache.hadoop.hive.ql.udf.UDFTestLength';

SELECT testlength(src.value) FROM src;

DROP TEMPORARY FUNCTION testlength;UDFTestLength.java 为:
package org.apache.hadoop.hive.ql.udf;

public class UDFTestLength extends UDF {
public Integer evaluate(String s) {
if (s == null) {
return null;
}
return s.length();
}
}自定义函数可以重载:
add jar build/contrib/hive_contrib.jar;
CREATE TEMPORARY FUNCTION example_add
AS 'org.apache.hadoop.hive.contrib.udf.example.UDFExampleAdd';

SELECT example_add(1, 2) FROM src;
SELECT example_add(1.1, 2.2) FROM src;UDFExampleAdd.java:
public class UDFExampleAdd extends UDF {
public Integer evaluate(Integer a, Integer b) {
if (a = null || b = null)
return null;
return a + b;
}

public Double evaluate(Double a, Double b) {
if (a = null || b = null)
return null;
return a + b;
}
}%%
在使用 UDF 的时候,会自动进行类型转换,这个 java 或者 C 中的类型转换有些类似,比如:
SELECT example_add(1, 2.1) FROM src;的结果是 3.1,这是因为 UDF 将类型为 Int 的参数 “1″ 转换为 double。
类型的隐式转换是通过 UDFResolver 来进行控制的,并且可以根据不同的 UDF 进行不同的控制。
UDF 还可以支持变长的参数,例如 UDFExampleAdd.java:
public class UDFExampleAdd extends UDF {
public Integer evaluate(Integer... a) {
int total = 0;
for (int i=0; i

大数据系列相关文章:

最新评论
sange2014-09-10 12:08:44
必须是国家同意的证才能考试,
慧慧2014-09-09 04:05:49
加油小伙,明天给我们做个汇报
字母2014-09-09 05:43:38
安装 vmware tools
chenly2014-09-08 01:55:21
concurrent mark sweek 好像是这样拼的...忘记了
陈晟-Sean2014-09-07 06:12:44
然后运行的是这个例子
 
  • Hadoop生态系统资料推荐