Hudi-StructuredStreaming流式写入Hudi

场景

实时产生的数据写入到Kafka,由Spark实时读取Kafka的数据,写入hudi

实现

package com.zhen.hudi.streaming

import com.zhen.hudi.didi.SparkUtils
import org.apache.hudi.DataSourceWriteOptions.{PARTITIONPATH_FIELD, PRECOMBINE_FIELD, RECORDKEY_FIELD}
import org.apache.hudi.config.HoodieWriteConfig.TBL_NAME
import org.apache.spark.sql.functions._
import org.apache.spark.sql.streaming.OutputMode
import org.apache.spark.sql.{DataFrame, Dataset, Row, SaveMode, SparkSession}

/**
  * @Author FengZhen
  * @Date 3/3/22 10:16 PM 
  * @Description 基于StructuredStreaming结构化流实时从kafka消费数据,经过ETL转换后,存储至Hudi表
  */
object HudiStructureDemo {

  /**
    * 指定kafka topic名称,实时消费数据
    * @param spark
    * @param topicName
    * @return
    */
  def readFromKafka(spark: SparkSession, topicName: String): DataFrame = {

    spark.readStream
      .format("kafka")
      .option("kafka.bootstrap.servers", "localhost:9092")
      .option("subscribe", topicName)
      //消费位置
      .option("startingOffsets", "latest")
      //每次最多处理十万条
      .option("maxOffsetsPerTrigger", 100000)
      //数据丢失是否失败
      .option("failOnDataLoss", "false")
      .load()

  }

  /**
    * 对kafka获取的数据进行转换操作,获取所有字段的值,转换为String,以便保存到hudi表
    * @param streamDF
    * @return
    */
  def process(streamDF: DataFrame): DataFrame = {

    streamDF
      //选择字段
      .selectExpr(
      "CAST(key AS STRING) AS order_id",
        "CAST(value AS STRING) AS message",
        "topic", "partition", "offset", "timestamp"
      )
      //解析Message数据,提取字段值
      .withColumn("user_id", get_json_object(col("message"), "$.userId"))
      .withColumn("order_time", get_json_object(col("message"), "$.orderTime"))
      .withColumn("ip", get_json_object(col("message"), "$.ip"))
      .withColumn("order_money", get_json_object(col("message"), "$.orderMoney"))
      .withColumn("order_status", get_json_object(col("message"), "$.orderStatus"))
      //删除message字段
      .drop(col("message"))
      //转换订单日期时间格式为Long类型,作为hudi表中合并数据字段
      .withColumn("ts", to_timestamp(col("order_time"), "yyyy-MM-dd HH:mm:ss.SSS"))
      //订单日期时间提取分区日期:yyyy-MM-dd
      .withColumn("day", substring(col("order_time"), 0, 10))
  }

  /**
    * 将流式数据DataFrame保存到Hudi表中
    * @param streamDF
    */
  def saveToHudi(streamDF: DataFrame): Unit = {
    streamDF.writeStream
      .outputMode(OutputMode.Append())
      .queryName("query-hudi-streaming")
      .foreachBatch((batchDF: Dataset[Row], batchId: Long) =>{
        println(s"=============== BatchId: ${batchId} start =============== ")
        import org.apache.hudi.DataSourceWriteOptions._
        import org.apache.hudi.config.HoodieWriteConfig._
        import org.apache.hudi.keygen.constant.KeyGeneratorOptions._

        batchDF.write
          .mode(SaveMode.Append)
          .format("hudi")
          .option("hoodie.insert.shuffle.parallelism", "2")
          .option("hoodie.upsert.shuffle.parallelism", "2")
          //hudi表的属性值的设置
          //主键
          .option(RECORDKEY_FIELD.key(), "order_id")
          //预合并
          .option(PRECOMBINE_FIELD.key(), "ts")
          //分区
          .option(PARTITIONPATH_FIELD.key(), "day")
          //分区值对应目录格式,与hive分区策略一致
          .option(HIVE_STYLE_PARTITIONING_ENABLE.key(), "true")
          //表名
          .option(TBL_NAME.key(), "tbl_hudi_order")
          //读取时合并表类型
          .option(TABLE_TYPE.key(), "MERGE_ON_READ")
          .save("/hudi-warehouse/tbl_hudi_order")
      })
      .option("checkpointLocation", "/datas/hudi-spark/struct-ckpt-1001")
      .start()
  }

  def main(args: Array[String]): Unit = {
    //1.构建SparkSession实例对象
    val spark: SparkSession = SparkUtils.createSparkSession(this.getClass)

    //2.从kafka实时消费数据
    val kafkaStreamDF: DataFrame = readFromKafka(spark, "order-topic")

    //3.提取数据,转换数据类型
    val streamDF: DataFrame = process(kafkaStreamDF)

    //4.保存数据至hudi表中:MOR类型,读取表数据合并文件
    saveToHudi(streamDF)

    //5.流式应用启动以后,等待终止
    spark.streams.active.foreach(query => println(s"Query: ${query.name} is Running"))
    spark.streams.awaitAnyTermination()

  }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值