spark streaming读取kafka 零丢失(四)

在移动互联网时代,处处都存在着实时处理或者流处理,目前比较常用的框架包括spark-streaming + kafka 等;由于spark-streaming读取kafka维护元数据的方式有
1、通过checkpoint保存
2、Direct DStream API 可以通过设置commit.offset.auto=true 设置自动提交
3、自己手动维护,自己实现方法将消费到的DStream中的偏移量信息同步到zookeeper,Redis,mysql等等
其实方法有很多,但是方法有很多的弊端:
1、checkpoint: 程序升级可序列化问题,不能保证不重复消费
2、Direct DStream方式 当driver端失败的时候,会存在数据丢失的情况
3、自己手动维护,随便比较好,但是不能解决绝对的数据不丢失,举个例子:
假如:

kafkaStream.foreachRDD { rdd =>
      val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
      // 对外输出
      rdd.map(_.value()).output...
      // 提交消费的offset到zookeeper
      commitOffset(offsetRanges)
    }

假如rdd输出到外部系统如DB了,但是此时在提交offset的时候出现了问题,此时会导致未提交的offset对应的数据在程序后续的处理中会出现重复消费的问题。
那么该怎么解决这个重复消费的问题呢?

方法一、对数据设置唯一Id,每次写入外部DB的时候,采用upsert的方式处理,存在则更新不存在则插入,此时只需设置direct DStream消费的offset方式 commit.offset.auto=true

 kafkaStream.foreachRDD { rdd =>
      val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
      // 对外输出
      rdd.foreachPartition(partition =>
        partition.foreach(document => db.coll.update(document.id, upsert=true))
      )
    }

这样子的方式的确不会存在重复消费的情况,但是此方法可以应用的场景比较少;
假如我的数据存在复杂的聚合或者window函数会导致该生成唯一主键Id成为瓶颈。

方法二:
在将数据输出落地到DB的时候,在每条数据中添加分区的偏移量,即在每条数据中都添加
该topic下的分区的最新偏移量,当程序再次启动的时候,只需要从该DB中读取最新的partition的偏移量信息即可。

kafkaStream.foreachRDD { rdd =>
      // 该batch对应topic中分区的偏移量
      val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
      // 获取每个分区的最大的偏移量信息
      val perPartitionMaxOffset = offsetRanges.map(tpo => (tpo.partition, tpo.untilOffset)).
        groupBy(_._1).mapValues(_.max)

      val transform = rdd.map(line => (line.value(), 1)).reduceByKey(_ + _)...

      transform.map(line => combine(line, perPartitionMaxOffset)).output
      
    }

该方法在对外输出的时候保证了数据和offset操作的原子性,要么写入成功要么写入失败下次重新消费。

如果有更加好的方法,欢迎在评论区留言,交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章