kafka学习(8)如何保证数据有序,处理数据丢失和数据重复消费

首先了解一下kafka消息生产者和消费者是如何处理消息的

生产者发送消息有两种方式,同步(sync)和异步 (async) 

Kafka 消息发送分同步 (sync)、异步 (async) 两种方式,默认使用同步方式,可通过

producer.type=sync 同步模式 
producer.type=async 异步模式 

//不提供回调
public Future<RecordMetadata> send(ProducerRecord<K, V> record);
//提供回调
public Future<RecordMetadata> send(ProducerRecord<K, V> record, new Callback(
    public void onCompletion(RecordMetadata metadata, Exception exception) {
        // 发送失败
        if (exception != null) {
        // 同步发送,需要设置阻塞时间,不然会一直阻塞
        producer.send(proRecord).get(timeout);
        
        // 发送成功
        }else{
        
        }
}
))
bootstrap.servers=${KAFKA_SERVER_IN}
key.serializer=org.apache.kafka.common.serialization.StringSerializer
value.serializer=com.unionpay.cloudatlas.galaxy.common.protocol.kafka.RecordKryoSerializer
#max time to wait,default to 60s
max.block.ms=10000
batch.size=65536
buffer.memory=134217728
retries=3

通过上述代码和配置可以看出

  • 最大block事件为1000ms,也就是10s
  • buffer配置的较大,为134M
  • 生产者先是异步发送,如果发送失败,则执行一次同步发送

问题定位

  • 在异步发送的的回调里使用了同步的方式再次发送,由于kafka producer的同步发送是阻塞等待,且使用的是不带超时时间的无限期等待(future.get()中未指定超时时间),因此当不被唤醒时会一直wai下去
  • kafka生产者的IO线程(实际执行数据发送的线程)是单线程模型,且回调函数是在IO线程中执行的,因此回调函数的阻塞会直接导致IO线程阻塞,于是生产者缓冲区的数据无法被发送
  • kafka生产者还在不断的被应用调用,因此缓冲区一直累积并增大,当缓冲区满的时候,生产者线程会被阻塞,最大阻塞时间为max.block.time,如果改时间到达之后还是无法将数据塞入缓冲区,则会抛出一个异常,因此日志中看到达到10s之后,打印出异常栈
  • 由于使用了get没有指定超时时间,且该await一直无法被唤醒,因此这种情况会一直持续,在没有人工干预的情况下,永远不会发送成功

生产建议

  • kafka生产者推荐使用异步方式发送,并且提供回调以响应发送成功或者失败
  • 如果需要使用future.get的方式模拟同步发送,则需要在get里加上合适的超时时间,避免因为不可预知的外部因素导致线程无法被唤醒,即使用Future.get(long timeout)的api而不是不带超时参数的Future.get()
  • 不要在异步回调中执行阻塞操作或者耗时比较久的操作,如果有必要可以考虑交给另一个线程(池)去做



作者:sheen口开河
链接:https://www.jianshu.com/p/45258f744425
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

同步

kafka同步生产者:这个生产者写一条消息的时候,它就立马发送到某个分区去。follower还需要从leader拉取消息到本地,follower再向leader发送确认,leader再向客户端发送确认。由于这一套流程之后,客户端才能得到确认,所以很慢。
kafka异步生产者:这个生产者写一条消息的时候,先是写到某个缓冲区,这个缓冲区里的数据还没写到broker集群里的某个分区的时候,它就返回到client去了。虽然效率快,但是不能保证消息一定被发送出去了。

 

通过 request.required.acks 属性进行配置:值可设为 0, 1, -1(all)    -1 和 all 等同

0 代表:不等待 broker 的 ack,这一操作提供了一个最低的延迟,broker 一接收到还没有写入磁盘就已经返回,当 broker 故障时有可能丢失数据;

1 代表:producer 等待 broker 的 ack,partition 的 leader 落盘成功后返回 ack,如果在 follower 同步成功之前 leader 故障,那么将会丢失数据;

-1 代表:producer 等待 broker 的 ack,partition 的 leader 和 follower 全部落盘成功后才返回 ack,数据一般不会丢失,延迟时间长但是可靠性高;但是这样也不能保证数据不丢失,比如当 ISR 中只有 leader 时( ISR 中的成员由于某些情况会增加也会减少,最少就只剩一个 leader),这样就变成了 acks = 1 的情况;

还有第四种,部分保存策略。需要配置。

消费者消息消息的方式,高级api和低级api

另外一个就是使用高级消费者存在数据丢失的隐患: 消费者读取完成,高级消费者 API 的 offset 已经提交,但是还没有处理完成DB操作,消费者就挂掉了,此时 offset 已经更新,无法再消费之前丢失的数据. 解决办法消费者使用低级api,读取到消息后,处理完成,在手动commit给kafka,这样子一旦处理失败,offset不会提交,下次还可以继续消费。

消息丢失场景

1.acks=0,不和Kafka集群进行消息接收确认,则当网络异常、缓冲区满了等情况时,消息可能丢失;
2.acks=1、同步模式下,只有Leader确认接收成功后但挂掉了,副本没有同步,数据可能丢失;

如何防止数据丢失

生产者:同步发送消息,且消息配置为-1或all,leader分区和所有follwer都写到磁盘里。

             异步模式下,为防止缓冲区满,可以在配置文件设置不限制阻塞超时时间,当缓冲区满时让生产者一直处于阻塞状态。但是处于阻塞状态会影响其他正常性能。

生产者:手动提交,即读取到消息后,确认消息消费完毕,才手动提交offset。但是要避免逻辑处理时间过长,导致连接超时,会使消息重复消费。

消息重复的原因

acks = -1 的情况下,数据发送到 leader 后 ,部分 ISR 的副本同步,leader 此时挂掉。比如 follower1 和 follower2 都有可能变成新的 leader, producer 端会得到返回异常,producer 端会重新发送数据,数据可能会重复

另外, 在高阶消费者中,offset 采用自动提交的方式, 自动提交时,假设 1s 提交一次 offset 的更新,设当前 offset = 10,当消费者消费了 0.5s 的数据,offset 移动了 15,由于提交间隔为 1s,因此这一offset 的更新并不会被提交,这时候我们写的消费者挂掉,重启后,消费者会去 ZooKeeper 上获取读取位置,获取到的 offset 仍为10,它就会重复消费. 解决办法使用低级消费者.

消息重复解决方案

针对消息重复:将消息的唯一标识保存到外部介质中,每次消费时判断是否处理过即可。比如redis中

消息可以使用唯一id标识

生产者(ack=all 代表至少成功发送一次)

消费者 (offset手动提交,业务逻辑成功处理后,提交offset)

落表(主键或者唯一索引的方式,避免重复数据)

业务逻辑处理(选择唯一主键存储到Redis或者mongdb中,先查询是否存在,若存在则不处理;若不存在,先插入Redis或Mongdb,再进行业务逻辑处理)


 

转载于:https://my.oschina.net/xiaoyoung/blog/3033977

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值