首先直接上干货:步骤

导入elasticsearch-hadoop-hive-5.5.2.jar包

创建hive外部表,数据源设置为es中,添加相应的配置

创建内部表拉取数据

add jar file:///home/hadoop/liubx/elasticsearch-hadoop-hive-5.5.2.jar;
add jar  file:///home/hadoop/hive-1.2.1/lib/es-hadoop.jar;   #代码见后面

use estest;
drop table if exists ext_es_test;
CREATE EXTERNAL TABLE ext_es_test(
typeid string,
cs_app string,
ipAddress string,
createTime  timestamp
)
row format delimited fields terminated by '\t'  
collection items terminated by ',' 
map keys terminated by ':'
STORED BY 'org.elasticsearch.hadoop.hive.EsStorageHandler'
TBLPROPERTIES(
'es.nodes' = 'master:9200',
'es.index.auto.create' = 'false',
'es.mapping.date.rich' = 'true',
'es.date.format' = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd", 
'es.ser.reader.value.class' = 'fjjf.es.EsValueReader',
'es.resource' = 'buriedpointinfo_bumsg/',
'es.read.metadata' = 'true',
'es.mapping.names' = 'typeid:_metadata._type, cs_app:cs_app,ipAddress:ipAddress,createTime:createTime');

#注释:
'es.nodes'                  -- es连接node信息
'es.index.auto.create'      -- 是否自动创建索引 
'es.mapping.date.rich'      -- 是否启用date自动转换,json中没有时间类型,es封装了一个Date类型存放特定格式的时间字符串。可以通过mapping指定format
'es.date.format'            -- 自定义dateformat
'es.ser.reader.value.class'  -- 自定义reader,由于本地es中存放时间采用了("yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"),导出时hive只能选择一种类型存取timestamp或者date
'es.resource'            -- es索引        
'es.read.metadata'       -- 是否读取es元数据字段         
'es.mapping.names'         -- es 和hive  字段映射

#分页查询
select * from estest.ext_es_test_bumsg limit 10;

#创建内部表分页查询
drop table if exists itn_es_test;
create table itn_es_testas 
select * from estest.itn_es_test;
遇到的问题:
  1. es中字段较多,不需要导出所有字段。 解决: 通过'es.mapping.names' 配置筛选所需要的字段。
  2. 在hadoop-2.8版本下导出的时候经常会报Error: java.lang.ClassNotFoundException: org.apache.commons.httpclient.util.xxxx,但是hive/lib目录下确实有该类。手动添加后还是无法解决 解决: 将版本切到2.7不会出现该问题,但是根本原因还是没有找到,待解决。

20180402更新: 解决方法:

URL:
  http://master:8088/taskdetails.jsp?jobid=job_1517535406767_315524&tipid=task_1517535406767_315524_m_000137
-----
Diagnostic Messages for this Task:
Error: java.lang.ClassNotFoundException: org.apache.commons.httpclient.util.DateParseException
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.getDeclaredConstructors0(Native Method)
    at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
    at java.lang.Class.getConstructor0(Class.java:3075)
    at java.lang.Class.newInstance(Class.java:412)
    at org.elasticsearch.hadoop.util.ObjectUtils.instantiate(ObjectUtils.java:41)
    at org.elasticsearch.hadoop.util.ObjectUtils.instantiate(ObjectUtils.java:52)
    at org.elasticsearch.hadoop.util.ObjectUtils.instantiate(ObjectUtils.java:48)

尝试在hive命令行手动添加jar失效之后,对比了hadoop2.7和hadoop2.8的jar包内容。发现hadoop-2.8.1/share/hadoop/common/lib目录下少了commons-httpclient-3.1.jar,由于本地hadoop2.8是编译安装的,所以怀疑是编译安装的时候没有将该依赖包打进去,对比官方下载的hadoop2.8二进制包发现也没有该jar包。 通过查看hadoop-2.8。0的change-log(https://issues.apache.org/jira/browse/HADOOP-13382)

image.png

接下来,只需将es-hadoop依赖的commons-httpclient jar加入到所有的hadoop节点即可。

  • hadoop classpath | tr ':' '\n'

image.png

  1. es中导出的Date类型数据既有时间戳也有日期字符串,在存取到hive的时候发现以下问题;

1)时间戳无法正常转换为时间戳 37304150-01-31 14:25:515:51.616 37301390-01-10 14:25:515:51.616 37300635-01-05 14:25:515:51.616 37289863-10-16 14:25:515:51.616 2)时间字符串无法正常转换为时间戳,会报类转换异常 TextWritable unable cast to TimestampWritable

解决: 通过指定'es.ser.reader.value.class' ,自定义reader处理该异常,将所有时间格式字符串统一格式输出。

通过查看elasticsearch-hadoop的源码,发现es-hadoop在解析Date类型的时候使用的是DatatypeConverter.parseDateTime(value)。(无法应对我们自定义的各种时间格式,甚至包括UTC时区的时间。)

源码地址:https://github.com/elastic/elasticsearch-hadoop/blob/master/hive/src/main/java/org/elasticsearch/hadoop/hive/HiveValueReader.java

@Override
    protected Object parseDate(Long value, boolean richDate) {
        return (richDate ? new TimestampWritable(new Timestamp(value)) : processLong(value));
    }

    @Override
    protected Object parseDate(String value, boolean richDate) {
        return (richDate ? new TimestampWritable(new Timestamp(DatatypeConverter.parseDateTime(value).getTimeInMillis())) : parseString(value));
    }

源码修改(默认情况下richDate为true,如果为false,则时间戳会当作Long处理,时间字符串当作Text处理)

@Override
    protected Object parseDate(String value, boolean richDate) {
        Date d = null;

        if(StringUtil.isNotEmpty(dateFormat)) {
            try {
                if(value.length() == 13){
                    return new TimestampWritable(new Timestamp(Long.valueOf(value)));
                }else{
                    d = DateUtil.parseDate(value, Arrays.asList(dateFormat.split("\\|\\|")));
                }

            } catch (DateParseException e) {
                e.printStackTrace();
                System.out.println("parse failed please check the dateFormat");
            }
        } else {
            d = DdfatatypeConverter.parseDateTime(value).getTime();
        }
        return new TimestampWritable(new Timestamp(d.getTime()));
    }

    protected Object parseDate(long value, boolean richDate) {
        Date d = new Date(value);
        return new TimestampWritable(new Timestamp(d.getTime()));
    }
  1. 最后遇到一个小问题,idea打包jar的时候太大,上传服务器速度较慢,将一些依赖去除打包后解决。 解决: 在pom.xml中指定scope为proviede,打包的时候忽略。
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch-hadoop</artifactId>
    <version>5.5.0</version>
    <scope>provided</scope>
</dependency>

总结: es国内资料比较杂乱,很多都还是2.x的。 建议研读官方文档,es这块配置项比较多,而且很多功能也许在配置项中已经可以找到解决方案了,缺乏的是仔细去研读和加深理解。这个我自己也正在学习的路上,哈哈! 另外,es-hadoop导出性能一般,20m/s左右,3个节点。有待提升,有时间可以对比下es-spark和java api效果。