消息丢失

避免消息丢失包含两个方面:

还没有被消费过的消息不会被清除。

在没有外部系统清除kafka消息的情况下,协调设置broker的最大保留大小 log.retention.bytes 和 最大保留时间log.retention.hours 等,来配合消费者端的读取消息。可以通过读取和监控消费者消费的offsets,来保证消息不会被意外清除。

  • 消费者端消费消息没有遗漏。

当消费者意外中断时,重新启动消费时能够从上一次中断的消息偏移量开始消费。

通过配置来保证消息不丢失

生产者数据的不丢失
kafka的ack机制:在kafka发送数据的时候,每次发送消息都会有一个确认反馈机制,确保消息正常的能够被收到。

  • 如果是同步模式:ack机制能够保证数据的不丢失,如果ack设置为0,风险很大,一般不建议设置为0
producer.type=sync 
request.required.acks=1

 

  • 如果是异步模式:通过buffer来进行控制数据的发送,有两个值来进行控制,时间阈值与消息的数量阈值,如果buffer满了数据还没有发送出去,如果设置的是立即清理模式,风险很大,一定要设置为阻塞模式
producer.type=async 
request.required.acks=1 
queue.buffering.max.ms=5000 
queue.buffering.max.messages=10000 
queue.enqueue.timeout.ms = -1 
batch.num.messages=200

结论:producer有丢数据的可能,但是可以通过配置保证消息的不丢失

  2.通过offset commit来保证消息不丢失

   每次都提交offset,下次Kafka消费时从这个offset开始读

重复消费

 根本原因:kafka读取完数据后,offset没有提交

原因1:强行kill线程,导致消费后的数据,offset没有提交。
原因2:设置offset为自动提交,关闭kafka时,如果在close之前,调用 consumer.unsubscribe() 则有可能部分offset没提交,下次重启会重复消费。例如:

try {
    consumer.unsubscribe();
} catch (Exception e) {
}

try {
    consumer.close();
} catch (Exception e) {
}

上面代码会导致部分offset没提交,下次启动时会重复消费。
原因3(重复消费最常见的原因):消费后的数据,当offset还没有提交时,partition就断开连接。比如,通常会遇到消费的数据,处理很耗时,导致超过了Kafka的session timeout时间(0.10.x版本默认是30秒),那么就会re-blance重平衡,此时有一定几率offset没提交,会导致重平衡后重复消费。
原因4:当消费者重新分配partition的时候,可能出现从头开始消费的情况,导致重发问题。
原因5:当消费者消费的速度很慢的时候,可能在一个session周期内还未完成,导致心跳机制检测报告出问题

常用解决方案:

1.设置offset为自动定时提交,当offset被自动定时提交时,数据还在内存中未处理,此时刚好把线程kill掉,那么offset已经提交,但是数据未处理,导致这部分内存中的数据丢失。
消息推动服务,每天早上,手机上各终端都会给用户推送消息,这时候流量剧增,可能会出现kafka发送数据过快,导致服务器网卡爆满,或者磁盘处于繁忙状态,可能会出现丢包现象。
总结起来就是来得快,去得慢,然后offset又提交了。
此时,对kafka进行限速,再启用重试机制,kafka设置acks=all

props.put("compression.type", "gzip");
props.put("linger.ms", "50");
props.put("acks", "all");
props.put("retries ", 30);
props.put("reconnect.backoff.ms ", 20000);
props.put("retry.backoff.ms", 20000);

2.维护offset

每次消费时更新每个topic+partition位置的offset在内存中,当调用关闭consumer线程时,把位置数据记录到 文件中*(分布式集群可能要记录到redis中)。
下一次启动consumer,需要读取上一次的offset信息,方法是 以当前的topic+partition为key,从上次的Map中去寻找offset。
然后使用consumer.seek()方法指定到上次的offset位置。