生产者问题
Producer发送消息到队列,分区Leader收到消息后返回ACK给Producer,表示接收成功,此时可以继续发送下一笔消息。
Kafka提供了3种不同级别的ACK机制:
- 0:Leader收到消息后立刻返回给Producer,消息可能还没刷盘,也还没有同步给Follower。此时如果Leader挂掉,消息就丢失了。
- 1:Leader将消息写入磁盘后,立刻返回ACK给Producer,消息可能还没同步Follower。此时如果Leader挂掉,选举新的Follower成为主分区,因为刚才没同步成功,所以消息丢失。
- -1:Leader将消息写入磁盘,并且同步给所有Follower,此时再返回ACK。这个方案也存在一个问题,如果在Leader准备返回ACK的时候挂掉,Producer没收到ACK认定为发送失败。此时又有2种情况
(1)开启重试:会导致队列收到2条重复的消息,此时需要Consumer端保证消费幂等,后面会介绍
(2)不开启重试(不推荐):网络问题、超时等原因导致发送失败,不会重新发送,消息容易丢失
消费者问题
Consumer进行消费时,提交Offset偏移给Kafka记录消费的最新位置。 提交Offset有以下2种方式:
- 自动提交:
enable.auto.commit 设置 true
开启,auto.commit.interval.ms
设置自动提交的频率间隔。Consumer获取到消息就提交Offset,不管消费是否成功 - 手动提交:
enable.auto.commit 设置 false
,消费完成后再调用 consumer.commit(),手动更新Offset。如果消费失败,我们可以将异常记录进行人工干预,然后再提交Offset,避免影响后面消息的消费。
问题总结
Kafka消息丢失有2个方面:
- Producer的ACK等级设置0或者1,可能因为没落盘或者没同步Follower导致丢失
- Consumer设置自动提交,消费失败来不及记录异常就宕机,但是Offset提交了,导致丢失
Kafka重复消费:
- Producer的ACK设置-1,没收到ACK进行重新发送,导致重复了
解决方案
对性能要求不是极高的情况下,建议将ACK设置-1。
此时,我们只需要在 Consumer 避免消息重复消费就行。
为了避免重复,我们需要在发送消息的时候增加一个唯一的标识字段。例如:订单ID、订单号。 Consumer消费成功后,将该标识记录在Redis等媒介。下次消费的时候,先判断当前标识如果已经存在,直接跳过;如果不存在,再进行消费
此外,消息标识ID也可以记录在MySQL。建议开启唯一性索引,让MySQL直接帮我们检查,抛出异常就是重复消费,直接结束即可。
如果对性能要求极高,同时又允许部分数据丢失。可以将ACK等级改成0或者1
例如:发送短信、日志记录等场景。本身Kafka挂掉的几率就不大,就算异常导致个别消息丢失,在这些场合下也无伤大雅。
因此,具体采用哪种方案,大家还是要根据业务进行选择。