Kafka——消息交付(如何确保消息不丢失)

594 阅读4分钟

消费

消息由生产者生产,消费者消费。在消息的传递过程中,Kafka使用了传统的模型:

消息由生产者Push到Broker(Kafka实例),并由消费者从Broker Pull到本地

使用消费者从Broker Pull的方式消费消息而不是Broker Push的方式是因为,Broker Push的方式将推送消息的速率的问题交给了Broker来处理,而Broker可能会需要向多个消费者推送消息,这个过程可能会导致消息生产的速度超过消息消费的速度,从而导致消费者无法消费。此外,使用Push推送消息的方式还会让Broker决定推送消息的时机,如果是接收到生产者的消息就立刻推送,可能每次只会推送极少的消息,从而导致效率低下,如果是在接受到消息之后缓存一段时间再批量推送,可能会导致消息延迟。因此Kafka使用消费者从Broker Pull消息的方式,将消费消息的速度交给消费者。当然,这可能会导致消费者Pull消息的时候并不存在消息,该情况下,消费者会阻塞直到消息产生,可以消费

消息交付

Kafka默认采用at least once的交付方式进行消息交付,当然可以通过设置取消producer重试以及消费者在接收到消息之后首先更新offset再进行消费消息的方式达到at most once的方式。然而,如果想要实现exactly once的方式需要使用事务型的producer/consumer。此外,当需要与外部系统(数据库等)进行交互时,通过将数据和offset一并写入的方式也可以达到exactly once的效果;数据和offset要么同时写入成功,要么都未写入成功

Kafka如何保证消息不会丢失

消息丢失可能发生在生产者Push到Broker阶段,也可能发生在Kafka本身,还有可能发生在消费者阶段,以下分别概述在这三个阶段如何避免消息丢失

  1. 生产者Push消息到Broker(server)阶段

生产者Push消息到Broker时,可能因为网络原因导致生产者没有接收到是否发送成功的响应,不确定消息是否发送到Broker,此时可以采用重新发送的方式确保消息成功Push到Broker

  1. Kafka本身

当消息只存在一个Broker时,该Broker故障即会导致消息丢失,因此通过设置replication.factor的数量可以设置副本的数量,从而实现followers定时从leader备份消息

我们都知道kafka的每个topic是分为多个Partition(分区)的,每个分区都会备份到配置的多个Broker中,在这些Broker中有一个作为该Partition的leader,其余的作为fllowers,只有leader接受生产者Push消息和消费者Pull消息。因此在任意阶段,都会存在leader末尾的一些消息未被fllowers同步,此时如果leader挂掉,则会导致未被同步的消息丢失,为了解决这个问题,我们可以设置acks=all,默认情况下acks=1,即消息只要被leader成功写入即当作成功,通过设置acks=all,只有消息被所有ISR成功备份才算成功写入,不过因为存在备份的成本,因此该模式会影响性能

通过设置min.insync.replicas参数,我们可以设置消息至少被N个副本成功备份之后才算作成功提交,避免消息只被leader写入即当作成功的情况,当acks设置为-1(all)时,该参数生效。需要注意的是当此参数大于ISR副本的数量时,Kafka则无法成功写入。

禁用Unclean leader选举,Kafka保证消息不丢失依赖于备份节点没有全部失效,如果所有备份节点都挂了,此时Kafka需要选举出新的leader,一是等待ISR节点恢复,该节点有极大的概率拥有所有的数据,但是一旦所有ISR节点都无法恢复,则Kafka服务不可用;二是允许非ISR节点成为新的leader,但是此节点可能有部分数据未及时同步导致数据丢失,Kafka默认采用第二种策略,可以通过配置可以配置属性 unclean.leader.election.enable 禁用此策略

  1. 消费者从Broker Pull消息阶段

默认情况下,消费者Pull消息之后即会自动更新offset,然后开始消费消息,如在更新offset之后,消费消息之前消费者发生故障,那么新的消费者实例/该消费者恢复正常时会从更新后的offset继续消费消息从而导致之前Pull到的消息丢失未被消费,因此,这个地方需要更改为手动更新offset,只有在成功消费该消息之后再更新offset,保证消息不被丢失