首先了解一下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,再进行业务逻辑处理)