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操作的原子性,要麼寫入成功要麼寫入失敗下次重新消費。

如果有更加好的方法,歡迎在評論區留言,交流。

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