• Hive 集成 Hudi 实践(含代码)| 可能是全网最详细的数据湖系列


    公众号后台越来越多人问关于数据湖相关的内容,看来大家对新技术还是很感兴趣的。关于数据湖的资料网络上还是比较少的,特别是实践系列,对于新技术来说,基础的入门文档还是很有必要的,所以这一篇希望能够帮助到想使用Hudi的同学入门。

    本篇的Hudi使用的是孵化版本 0.5.2;其他依赖 Spark-2.4.4,Hive-1.1.0

    Hudi 服务器环境准备

    wget https://github.com/apache/hudi/archive/release-0.5.2-incubating.tar.gz
    tar zxvf release-0.5.2-incubating.tar.gz
    cd release-0.5.2-incubating
    mvn clean package -DskipTests -DskipITs
    cp ./hudi-hadoop-mr/target/hudi-hadoop-mr-0.5.2-incubating.jar $HIVE_HOME/lib/
    

    拷贝依赖包到 Hive 路径是为了 Hive 能够正常读到 Hudi 的数据,至此服务器环境准备完毕。

    用 Spark 写一段数据

    一切准备完毕先写一段数据到 Hudi 里,首先数据源 ods.ods_user_event 的表结构为:

    CREATE TABLE ods.ods_user_event(
        uuid STRING,
        name STRING,
        addr STRING,
        update_time STRING,
        date STRING)
    stored as parquet;
    

    然后是 Maven 的依赖,详细代码关注公众号【老蒙大数据】回复 hudi 后即可获取。

          <dependency>
                <groupId>org.apache.hudi</groupId>
                <artifactId>hudi-spark_2.11</artifactId>
                <version>0.5.2-incubating</version>
            </dependency>
            <dependency>
                <groupId>org.apache.hudi</groupId>
                <artifactId>hudi-common</artifactId>
                <version>0.5.2-incubating</version>
            </dependency>
    

    代码逻辑:

    1. 初始化 SparkSession,配置相关配置项
    2. 构建 DataFrame,大家可以自由发挥,这里的案例是从Hive读数据构建。
    3. DataFrame写入Hudi,这一块说到底就是把数据写入 HDFS 路径下,但是需要一堆配置,这些配置就体现了 Hudi 的特性:
      • DataSourceWriteOptions.RECORDKEY_FIELD_OPT_KEY:指定唯一id的列名
      • DataSourceWriteOptions.PRECOMBINE_FIELD_OPT_KEY:指定更新时间,该字段数值大的数据会覆盖小的
      • DataSourceWriteOptions.PARTITIONPATH_FIELD_OPT_KEY:指定分区列,和Hive的分区概念类似
      • HoodieIndexConfig.BLOOM_INDEX_UPDATE_PARTITION_PATH:设置当分区变更时,当前数据的分区目录是否变更
      • HoodieIndexConfig.INDEX_TYPE_PROP:设置索引类型目前有 HBASE,INMEMORY,BLOOM,GLOBAL_BLOOM 四种索引
        上述例子中,选择了 HoodieGlobalBloomIndex(全局索引),会在所有分区内查找指定的 recordKey。而 HoodieBloomIndex 只在指定的分区内查找。
      def main(args: Array[String]): Unit = {
        val sss = SparkSession.builder.appName("hudi")
          .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
          .config("hive.metastore.uris", "thrift://ip:port")
          .enableHiveSupport().getOrCreate()
    
        val sql = "select * from ods.ods_user_event"
        val df: DataFrame = sss.sql(sql)
    
        df.write.format("org.apache.hudi")
          .option(DataSourceWriteOptions.RECORDKEY_FIELD_OPT_KEY, "recordKey") 
          .option(DataSourceWriteOptions.PRECOMBINE_FIELD_OPT_KEY, "update_time") 
          .option(DataSourceWriteOptions.PARTITIONPATH_FIELD_OPT_KEY, "date") 
          .option(HoodieIndexConfig.BLOOM_INDEX_UPDATE_PARTITION_PATH, "true")
          .option(HoodieIndexConfig.INDEX_TYPE_PROP, HoodieIndex.IndexType.GLOBAL_BLOOM.name())
          .option("hoodie.insert.shuffle.parallelism", "10")
          .option("hoodie.upsert.shuffle.parallelism", "10")
          .option(HoodieWriteConfig.TABLE_NAME, "ods.ods_user_event_hudi")
          .mode(SaveMode.Append)
          .save("/user/hudi/lake/ods.db/ods_user_event_hudi")
      }
    

    执行成功后会有如下结果,因为我们是按照date分区,每一天的数据会生成一个文件夹和Hive类似。

    [hadoop@hadoop31 ~]# hdfs dfs -ls /user/hudi/lake/ods.db/ods_user_event_hudi/
    Found 4 items
    drwxr-xr-x   - hadoop hadoop 0 2020-05-25 18:42 /user/hudi/lake/ods.db/ods_user_event_hudi/20200501
    drwxr-xr-x   - hadoop hadoop 0 2020-05-25 18:42 /user/hudi/lake/ods.db/ods_user_event_hudi/20200502
    drwxr-xr-x   - hadoop hadoop 0 2020-05-25 18:42 /user/hudi/lake/ods.db/ods_user_event_hudi/20200503
    drwxr-xr-x   - hadoop hadoop 0 2020-05-25 18:42 /user/hudi/lake/ods.db/ods_user_event_hudi/20200504
    

    另外,注意 recordKey 必须唯一,不然数据会被覆盖,且值不能为 null,否则会有以下报错。

    Caused by: org.apache.hudi.exception.HoodieKeyException: recordKey value: "null" for field: "user_uid" cannot be null or empty.
    

    Hive 创建外部表读数据

    上一步中 Spark 将数据写到了 hudi,想要通过Hive访问到这块数据,就需要创建一个Hive外部表了,因为 Hudi 配置了分区,所以为了能读到所有的数据,咱们的外部表也得分区,分区字段名可随意配置。

    CREATE TABLE ods.ods_user_event_hudi(
        uuid STRING,
        name STRING,
        addr STRING,
        update_time STRING,
        date STRING)
    PARTITIONED BY ( 
      `dt` string)
    ROW FORMAT SERDE 
      'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe' 
    STORED AS INPUTFORMAT 
      'org.apache.hudi.hadoop.HoodieParquetInputFormat' 
    OUTPUTFORMAT 
      'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
    LOCATION
      '/user/hudi/lake/ods.db/ods_user_event_hudi'
    

    至此,直接读数据肯定是空的,因为我们创建的是个分区表,所以还需要指定分区

    alter table ods.ods_user_event_hudi add if not exists partition(dt='20200504') location '/user/hudi/lake/ods.db/ods_user_event_hudi/20200504'
    

    那么这个时候问题来了,一年有365个分区,要一个一个建立手动创建分区吗?
    抱歉我也没发现更好的办法,只能送你个简单的脚本了。

    #!/bin/bash
    start_date=20190101
    end_date=20200520
    start=`date -d "$start_date" "+%s"`
    end=`date -d "$end_date" "+%s"`
    for((i=start;i<=end;i+=86400)); do
        dt=$(date -d "@$i" "+%Y%m%d")
        hive -e "alter table ods.ods_user_event_hudi add if not exists partition(dt='${dt}') location '/user/hudi/lake/ods.db/ods_user_event_hudi/${dt}';
        "
    done
    

    后记

    最后,执行 select * from ods.ods_user_event_hudi 要是没有数据你来找我。另外值得注意的是,如果此时直接用 Hive 将数据 insert into ods.ods_user_event_hudi,虽然数据会写入到 hudi 的目录下,但是相同的 recordKey 是不会覆盖原有数据的。

    下一篇详细写 Spark 操作 Hudi 的相关内容,敬请期待。本篇详细代码关注公众号【老蒙大数据】回复 hudi 后即可获取。

    推荐阅读
    3000字长文教你大数据该怎么学!
    选方向?大数据的职位你了解多少

  • 相关阅读:
    关于OI的文学作品
    HBOI 2020 游记
    从0开始的字符串生活(选手命要没了)
    近两年HBOI选做
    NOI online #3
    2020年“美团杯”程序设计挑战赛题解(目前只有测试赛)
    退群咕咕墙
    JS 获得当前地址栏url
    你了解getBoundingClientRect()?
    字符串与数字相加
  • 原文地址:https://www.cnblogs.com/uncledata/p/12974087.html
Copyright © 2020-2023  润新知