1.Kafka 能否保证消息的不丢失?
Kafka 只对“已提交”的消息(Committed message)做“有限的持久化“保证,“已提交”的消息是指 Kafka 的若干个 Broker 成功接收到一条消息后写入到日志文件后,告诉生产者这条信息已成功提交。为什么是若干个 Broker 呢,是指写入的副本个数。第二个要点,“有限的持久化”假设在 N 个 Broker 上进行保存,那么前提就是至少有一个 Broker 存活,那么 Kafka 就能保证这条信息不会丢失。
2.Kafka 在什么情况会出现消息丢失?
(1)生产者出现消息丢失
Kafka 在生产者(Producer)提交时属于异步提交,也就是 producer.send(message) 会立刻返回,此时 Producer 并不知道消息是否发送成功没,从而导致的消息丢失。这种情况在现实场景中也十分常见:比如说网络抖动、导致消息没有发送到 Broker 端;或者说 Broker 拒绝接受(消息太大了,超过设置的大小);Broker 端宕机了,还没来及的写入磁盘就发生了宕机,数据就会丢失。 解决办法:
- 设置
acks参数:acks参数用于设置生产者需要等待多少个副本确认接收到消息后才认为消息发送成功。如果设置为all或-1,则需要所有的副本都确认接收到消息后才认为消息发送成功。 - 使用
send()方法的返回值:send()方法返回一个Future对象,我们可以通过调用Future的get()方法来等待消息发送完成,并获取发送结果。如果消息发送失败,get()方法会抛出异常。 - 设置
retries参数:retries参数用于设置生产者在消息发送失败后的重试次数。如果设置为一个较大的值,那么生产者在遇到短暂的错误时可以自动重试,从而提高消息发送的成功率。 - 使用回调函数:
send()方法还可以接受一个回调函数,当消息发送完成后,这个回调函数会被调用。我们可以在回调函数中检查消息是否发送成功,如果发送失败,可以进行相应的处理,如重试或者记录错误
(2)消费者出现消息丢失
Kafka 在消费者(Consumer)中会出现消息丢失的场景:位移提交问题,位移(offset)是用来标记消费者(Consumer)已经读取到哪里的一个标记,Kafka 的消费者通过提交位移(offset)来记录消费到哪里。如果消费者在消费完消息后未能正确的提交 offset(比如说多线程,其中一个线程消费失败了),那么下次消费可能会跳过一些信息,导致消息丢失。多线程异步消费消息时,Consumer 不要开启手动提交了,而是要应用程序开启手动提交。但是值得注意的是,使用多线程异步处理消费消息,那么就需要手动管理这个位移。这是因为在多线程环境下,不同的线程可能会同时读取和处理消息,这就可能导致位移的更新出现冲突,从而导致消息被重复消费。
假设我们有一个消费者,它有两个线程 A 和 B 同时读取和处理消息。
- 线程 A 读取了 offset 为 10 的消息,开始处理。
- 同时,线程B读取了 offset 为 20 的消息,开始处理。由于线程 B 处理的消息比较简单,所以很快就处理完了,然后提交了 offset 为 20。
- 此时,线程 A 处理完了 offset为 10 的消息,然后提交了 offset 为 10。
在这个例子中,由于线程 A 和 B 线程的处理速度不同,导致了线程 A 后于线程 B 提交 offset。而 Kafka 消费者会从最后提交的 offset 开始读取消息,所以当消费者下次开始读取消息时,它会从 offset 为 10 的消息开始读取,这就导致了 offset 为 11 到 20 的消息被重复消费。
为了避免这种情况,可以采取以下策略:
- 单线程提交 offset:即使在消费者中使用多线程处理消息,也只使用一个线程来提交 offset。这样可以保证offset 的提交是有序的。
- 同步处理和提交:在处理完消息并准备提交 offset 之前,先获取一个锁,提交完后再释放锁。这样可以保证同一时间只有一个线程在提交 offset。
- 分区粒度的并发:每个线程处理特定的分区,并且只提交自己处理的分区的 offset。这样可以避免不同线程之间的 offset 冲突。