「Kafka系列」- 如何防止 Kafka 丢失消息

1,083 阅读5分钟

这是我参与8月更文挑战的第N天,活动详情查看:8月更文挑战

前言

所谓的丢失消息,意味着消息没有被消费,但是没有被开发人员感知到,如果是Broker挂掉了,但是消息持久化到磁盘了,重启后消费者任然会接收到消息,应不属于消息丢失

Kafka丢消息的问题,可能发生在Broker,Producer和Consumer三个地方

本文详细描述了Kafka可能丢失消息的环节以及对应的解决方案


Producer丢失消息

消息的生产者调用 send() 方法后,由于send()方法是异步的,只管发送不管结果,当消息因为网络波动没有发送出去的时候,系统是感知不到的

kafkaTemplate.send(topic, data);

解决方案:

对于KafkaTemplate来说,可以通过添加回调函数来处理发送成功和发送失败的情况

 ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, data);
 future.addCallback(result -> log.info("消息发送成功"),
         exception -> log.error("消息发送失败"));

其次,可以为生产者设置一个合理的重试次数,但是重试机制会给消息的顺序带来一定的风险,如果是对消息要求非常高的业务场景,会将重复次数设为0,用其他方式处理


Consumer丢失消息

当一个Consumer订阅了一个Topic时,该Topic每个Partition都会维护一个偏移量(offset),来记录这个Consumer消费到哪里了

消费者消费消息要经过以下阶段:

  1. 拉取所订阅Topic中的消息
  2. 处理消息
  3. 告诉Kafka冲完了,此时才是Kafka修改位移量的时候(提交操作Commited)

Kafka消费消息的模型

问题的出现:

但是提交又分为手动提交和自动提交,当Consumer处于自动提交机制时,第二步和第三步是异步的,于是就存在这么一种情况:消费者刚告诉Kafka我冲完了,此时电脑被强行关机或者消费出现异常,其实消息还没有被正常消费而开发人员无法感知到这条消息,对于这个消费组来说,消息就丢失了


解决方案:

将自动提交设为false,比如使用Spring-Kafka的话可以设置spring.kafka.producer.enable-auto-commit = false

将自动提交设置为手动提交,可以保证至少被消费一次,但是可能会出现重复消费的问题,比如消费者在刚消费完还没提交时,自己挂了,再启动时又消费了一次消息,这个情况不在本文讨论


Kafka自己弄丢了消息

Kafka为了提高消息存储的安全性和容灾能力,引入了多副本机制,副本分为领导者副本(Leader)和追随者副本(Follower),每个分区创建时,都会选举一个副本,称为领导者副本

当领导者副本宕机时,依托于Zookeeper提供的实时感知功能可以察觉到,并立即开启新的一轮领导者选举,从追随者副本中挑选一个作为新的领导者

问题的出现:

试想一下,此时所有的Follower都还没拉取到Leader的最新消息,然后Leader挂了,此时就会造成消息的丢失

解决方案:

对于Producer来说,有个很重要的参数acks,它用来配置生产者发送消息后是否等待来自服务器的响应,可配置的有效值有三个:

  1. acks = 0 :生产者只管发送消息,不会等待来自服务器的响应
  2. acks = 1(默认值) :只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应
  3. acks = all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应

根据以上信息,我们可以把acks设置为all,等所有副本全部同步了消息,才认为消息是发送成功了


replication.factor:设置为Partition创建多少个副本的参数

其次,为了保证Leader有足够的副本可以同步,建议把replication.factor 的数量设置为大于等于三,此参数可以在Broker或Topic层面设置,这样可以保证每隔Partition至少有三个副本,虽然牺牲了磁盘空间以及数据的冗余,但是带来了数据的安全性


min.insync.replicas:配置Partition到底同步了多少个副本算同步完成

另外在设置了 acks = all 后,还需要设置一个参数是 min.insync.replicas > 1 ,这个参数用于配置消息至少要同步到多少个副本才算是发送成功。当且仅当ack = all 时这个配置才会才生效,且这个值不应该大于replication.factor,很好理解如果需要发送到4个副本才算成功,但是只配置了三个副本,那么这条消息是不是永远都无法发送成功呢?


总结

要想防止Kafka在运行的过程中消息防丢,不仅要开发人员在开发阶段就应该考虑到并且加以相应的措施,还需要运维人员保障服务集群的健康,配置的正确、网络的通畅

消息的丢失在不同的业务场景下造成的后果也是不同的

例子一:

假设医院有个病人在门诊的时候医生开了药单,并将药单发送到Kafka,此时由于网络波动,药房无法察觉漏了消息,并且门诊系统是无感知的,也没有对应的记录,就会造成病人去拿药的时候无法开药,耽误了医务工作者和病人大量的时间,这种后果是比较严重的

例子二:

其次是在登录系统系统中,用户注册账号后,使用Kafka来异步发送验证邮件,此时消息若是出现了丢失,假设服务很快的恢复的话,其影响也是微乎其微的,用户只需要没有收到激活码,重新发送一次激活邮件即可


Reference

JavaGuide :Kafka常见问题总结

CSDN:Kafka生产环境的几个重要配置参数