spark消费kafka多主题并注册为表进行sql处理

代码示例:

case class Table1(@BeanProperty var goods1: String, @BeanProperty var price1: Int) extends Serializable
case class Table2(@BeanProperty var goods2: String, @BeanProperty var price2: Int) extends Serializable
case class Table3(@BeanProperty var goods3: String, @BeanProperty var price3: Int) extends Serializable
object Stream extends Serializable {

  def main(args: Array[String]): Unit = {

    val masterUri = sys.props.getOrElse("spark.master", "local[4]")
    // 获取 spark 环境
    val conf = new SparkConf()
    val spark: SparkSession = SparkSession
      .builder()
      .config(conf)
      .master(masterUri)
      .getOrCreate()
    val sparkContext = spark.sparkContext
    val ssc: StreamingContext = new StreamingContext(sparkContext, Seconds(120))
    val sqlContext = spark.sqlContext

    // ------------------------------------------------------------------------------------------------------------------------------------------------------------
    val kafkaParams: mutable.Map[String, Object] = mutable.Map[String, Object](
      ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "kafka01:9092",
      ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer].getCanonicalName,
      ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer].getCanonicalName,
      ConsumerConfig.GROUP_ID_CONFIG -> "test-1"
    )

    // 保存 offset,最后手动提交
    val offsetRangesList = mutable.ListBuffer[Array[OffsetRange]]()

    val topic1 = Array("topic1")
    val tableName1 = "table1"
    val inputDS1: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
      ssc,
      LocationStrategies.PreferConsistent,
      ConsumerStrategies.Subscribe[String, String](topic1, kafkaParams)
    )
    inputDS1.foreachRDD(rdd => {
      offsetRangesList += rdd.asInstanceOf[HasOffsetRanges].offsetRanges
    })
    inputDS1
      .map(_.value())
      .map(x => JSONUtil.toBean(x, classOf[Table1]))
      .foreachRDD((rdd: RDD[Table1]) => {
        spark.createDataFrame(rdd).createOrReplaceTempView(tableName1)
      })

    val topic2 = Array("topic2")
    val tableName2 = "table2"
    val inputDS2: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
      ssc,
      LocationStrategies.PreferConsistent,
      ConsumerStrategies.Subscribe[String, String](topic2, kafkaParams)
    )
    inputDS2.foreachRDD(rdd => {
      offsetRangesList += rdd.asInstanceOf[HasOffsetRanges].offsetRanges
    })
    inputDS2
      .map(_.value())
      .map(x => JSONUtil.toBean(x, classOf[Table2]))
      .foreachRDD((rdd: RDD[Table2]) => {
        spark.createDataFrame(rdd).createOrReplaceTempView(tableName2)
      })

    val topic3 = Array("topic3")
    val tableName3 = "table3"
    val inputDS3: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
      ssc,
      LocationStrategies.PreferConsistent,
      ConsumerStrategies.Subscribe[String, String](topic3, kafkaParams)
    )
    inputDS3.foreachRDD(rdd => {
      offsetRangesList += rdd.asInstanceOf[HasOffsetRanges].offsetRanges
    })

    // 所有计算和结果写出都在下面维护
    inputDS3
      .map(_.value())
      .map(x => JSONUtil.toBean(x, classOf[Table3]))
      .foreachRDD(foreachFunc = (rdd: RDD[Table3]) => {
        spark.createDataFrame(rdd).createOrReplaceTempView(tableName3)

        // 从 hdfs 读取上一批次的计算结果
        val lastDataDF: DataFrame = sqlContext.read.format("csv").option("header", "true").load("hdfs:///spark/latest-data")
        lastDataDF.createOrReplaceTempView("last_result")

        // 计算最新的结果
        val resultDF: DataFrame = spark.sql("真正要执行的 sql 语句")
        // 将结算结果进行输出,这里简单调用 show ,只是为了演示
        resultDF.show(10)
        // 将本批次结果写入 hdfs,供下次计算前初始化使用
        resultDF.write.option("header", "true").mode(SaveMode.Overwrite).csv("hdfs:///spark/latest-data")
        // 手动提交 offset
        for (offsetRanges <- offsetRangesList) {
          inputDS3.asInstanceOf[CanCommitOffsets].commitAsync(offsetRanges)
        }
        offsetRangesList.clear()
        // 清理掉内存中的结果数据
        resultDF.unpersist()
      })

    ssc.start()
    ssc.awaitTermination()
  }

}

步骤:

  1. 分别通过 spark-kafka 提供的 createDirectStream 函数创建连接 kafka 的 InputDStream 输入流。
  2. 先不对输入流做任何处理,而是将输入流的 offsetRanges 放到一个列表中。
  3. 将输入流进行 map 处理,获取到 kafka 数据对应的 value 值,并且将其转化为对应的 case class 对象。我的示例中,kafka value 值类型为 JSON。
  4. 在每个流的 foreachRdd 函数中,将 rdd 转化为 DataFrame,然后注册为表。
  5. 在最后一个的 foreachRdd 函数中,执行具体的 sql 语句,然后获取到的结果进行输出。
  6. 最后手动提交 offset 值,可以使用 spark 提供的将 offset 值提交到 kafka 的方式,也可以采取其他方式,比如放到 mysql、redis等。如果任务场景对数据的一致性要求不是特别严格,可以采用最简单的方式,直接将其提交给 kafka,不过这个提交时机,并不是在调用了 commitAsync 函数后就会直接提交,而是 spark 任务在拉取下批次代码时才会提交,这个需要注册,这是有可能造成数据的重复消费的。
  7. 清理内存中的结果数据(貌似不清理也可以)。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

第一片心意

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值