flink⼿手动维护kafka偏移量量

flink对接kafka,官方模式方式是自动维护偏移量

但并没有考虑到flink消费kafka过程中,如果出现进程中断后的事情! 如果此时,进程中段:

 

1:数据可能丢失
从获取了了数据,但是在执⾏行行业务逻辑过程中发⽣生中断,此时会出现丢失数据现象

2:数据可能重复处理理 

flink从kafka拉去数据过程中,如果此时flink进程挂掉,那么重启flink之后,会从当前Topic的 起始偏移量量开始消费 

解决flink消费kafka的弊端 

上述问题,在任何公司的实际⽣生产中,都会遇到,并且⽐比较头痛的事情,主要原因是因为上述的代码 是使⽤用flink⾃自动维护kafka的偏移量量,导致⼀一些实际⽣生产问题出现。~那么为了了解决这些问题,我们就 需要⼿手动维护kafka的偏移量量,并且保证kafka的偏移量量和flink的checkpoint的数据状态保持⼀一致 (最好是⼿手动维护偏移量量的同时,和现有业务做成事务放在⼀一起)~ 

1):offset和checkpoint绑定 

//创建kafka数据流
val properties = new Properties() properties.setProperty("bootstrap.servers", GlobalConfigUtils.getBootstrap) properties.setProperty("zookeeper.connect", GlobalConfigUtils.getZk) properties.setProperty("group.id", GlobalConfigUtils.getConsumerGroup) properties.setProperty("enable.auto.commit" , "true")//TODO properties.setProperty("auto.commit.interval.ms" , "5000") properties.setProperty("auto.offset.reset" , "latest") properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
val kafka09 = new FlinkKafkaConsumer09[String](
  GlobalConfigUtils.getIntputTopic,
  new SimpleStringSchema(),
  properties
)
/** *
如果checkpoint启⽤用,当checkpoint完成之后,Flink Kafka Consumer将会提交offset保存 到checkpoint State中,
这就保证了了kafka broker中的committed offset与 checkpoint stata中的offset相⼀一致。 ⽤用户可以在Consumer中调⽤用setCommitOffsetsOnCheckpoints(boolean) ⽅方法来选择启⽤用 或者禁⽤用offset committing(默认情况下是启⽤用的)
* */
kafka09.setCommitOffsetsOnCheckpoints(true)
kafka09.setStartFromLatest()//start from the latest record
kafka09.setStartFromGroupOffsets()
//添加数据源addSource(kafka09)
val data: DataStream[String] = env.addSource(kafka09)

2):编写flink⼿手动维护kafka偏移量量 

 
/**
* ⼿手动维护kafka的偏移量量 */
object KafkaTools {
  var offsetClient: KafkaConsumer[Array[Byte], Array[Byte]] = null
  var standardProps:Properties = null
  def init():Properties = {
    standardProps = new Properties
    standardProps.setProperty("bootstrap.servers",
GlobalConfigUtils.getBootstrap)
    standardProps.setProperty("zookeeper.connect", GlobalConfigUtils.getZk)
    standardProps.setProperty("group.id",
GlobalConfigUtils.getConsumerGroup)
    standardProps.setProperty("enable.auto.commit" , "true")//TODO
    standardProps.setProperty("auto.commit.interval.ms" , "5000")
    standardProps.setProperty("auto.offset.reset" , "latest")
    standardProps.put("key.deserializer",
"org.apache.kafka.common.serialization.StringDeserializer");
    standardProps.put("value.deserializer",
"org.apache.kafka.common.serialization.StringDeserializer");
    standardProps
}
  def getZkUtils():ZkUtils = {
    val zkClient = new ZkClient("hadoop01:2181")
    ZkUtils.apply(zkClient, false)
}
  def createTestTopic(topic: String, numberOfPartitions: Int,
replicationFactor: Int, topicConfig: Properties) = {
    val zkUtils = getZkUtils()
    try{
      AdminUtils.createTopic(zkUtils, topic, numberOfPartitions,
replicationFactor, topicConfig)
    }finally {
      zkUtils.close()
} }
  def offsetHandler() = {
    val props = new Properties
    props.putAll(standardProps)
    props.setProperty("key.deserializer",
"org.apache.kafka.common.serialization.ByteArrayDeserializer")
 
props.setProperty("value.deserializer",
"org.apache.kafka.common.serialization.ByteArrayDeserializer")
    offsetClient = new KafkaConsumer[Array[Byte], Array[Byte]](props)
  }
  def getCommittedOffset(topicName: String, partition: Int): Long = {
    init()
    offsetHandler()
    val committed = offsetClient.committed(new TopicPartition(topicName,
partition))
    println(topicName , partition , committed.offset())
    if (committed != null){
      committed.offset
    } else{
0L
} }
  def setCommittedOffset(topicName: String, partition: Int, offset: Long) {
    init()
    offsetHandler()
    var partitionAndOffset:util.Map[TopicPartition , OffsetAndMetadata] =
new util.HashMap[TopicPartition , OffsetAndMetadata]()
    partitionAndOffset.put(new TopicPartition(topicName, partition), new
OffsetAndMetadata(offset))
    offsetClient.commitSync(partitionAndOffset)
  }
  def close() {
    offsetClient.close()
}
}

 

 

转载于:https://www.cnblogs.com/niutao/p/10948919.html

Flink 中,可以通过 `KafkaConsumer` 提供的 `FlinkKafkaConsumer` 来消费 Kafka 中的数据。而保存消费 Kafka偏移量可以使用 Flink 提供的 `FlinkKafkaConsumer` 的 `enableCheckpointing` 方法来实现。 具体做法如下: 1. 在创建 `FlinkKafkaConsumer` 对象时,设置 `enableCheckpointing` 方法开启 Flink 的 checkpoint 机制。 ```java FlinkKafkaConsumer<String> kafkaConsumer = new FlinkKafkaConsumer<>(topic, new SimpleStringSchema(), properties); kafkaConsumer.setStartFromEarliest(); kafkaConsumer.setCommitOffsetsOnCheckpoints(true); env.enableCheckpointing(5000); ``` 2. 设置 `setCommitOffsetsOnCheckpoints` 方法为 `true`,表示 Flink 在进行 checkpoint 时会自动提交当前消费 Kafka偏移量。 3. 在 Flink 应用程序中,可以通过实现 `CheckpointedFunction` 接口来手动管理 Kafka 偏移量的保存和恢复。 ```java public class KafkaConsumer implements SourceFunction<String>, CheckpointedFunction { private transient ListState<Long> offsetState; private volatile boolean isRunning = true; private long offset = 0L; @Override public void snapshotState(FunctionSnapshotContext context) throws Exception { offsetState.clear(); offsetState.add(offset); } @Override public void initializeState(FunctionInitializationContext context) throws Exception { ListStateDescriptor<Long> offsetStateDescriptor = new ListStateDescriptor<>("offsets", Long.class); offsetState = context.getOperatorStateStore().getListState(offsetStateDescriptor); if (context.isRestored()) { for (Long offset : offsetState.get()) { this.offset = offset; } } } @Override public void run(SourceContext<String> ctx) throws Exception { while (isRunning) { ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000)); for (ConsumerRecord<String, String> record : records) { offset = record.offset(); ctx.collect(record.value()); } } } } ``` 在 `snapshotState` 方法中,将当前消费 Kafka偏移量保存到 `offsetState` 状态变量中。在 `initializeState` 方法中,可以从状态变量中恢复保存的偏移量。在 `run` 方法中,每次消费 Kafka 中的数据时,都会更新 `offset` 变量为当前消费的偏移量。 这样,当 Flink 应用程序出现故障,重新启动时,就可以从保存的偏移量处继续消费 Kafka 中的数据,避免了数据的重复消费。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值