总所周知,Kafka是一个分布式的、可分区的、可复制的消息系统。
也就是说一个topic中的消息是放在多个partition上的,可是当一台机器宕机后不就会导致部分消息不可消费吗?所以Kafka还做了多副本冗余,每个Partition都可以搞一个副本放在别的机器上,这样某台机器宕机,只不过是Partition其中一个副本丢失。
如果某个Partition有多副本的话,Kafka会选举其中一个Parititon副本作为Leader,然后其他的Partition副本是Follower。
只有Leader Partition是对外提供读写操作的,Follower Partition就是从Leader Partition同步数据。
一旦Leader Partition宕机了,就会选举其他的Follower Partition作为新的Leader Partition对外提供读写服务,这就实现了高可用架构。
什么情况会导致Kafka中写入数据会丢失呢?
大家都知道写入数据都是往某个Partition的Leader写入的,然后那个Partition的Follower会从Leader同步数据,但是这个同步过程是异步的。也就是说如果此时1条数据刚写入Leader Partition1,还没来得及同步给Follower,Leader Partiton1所在机器突然就宕机了的话,此时就会选举Partition1的Follower作为新的Leader对外提供服务,然后用户就读不到刚才写入的那条数据了。因为Partition0的Follower上是没有同步到最新的一条数据的,这个时候就会造成数据丢失的问题。
这个机制简单来说,就是会自动给每个Partition维护一个ISR列表,这个列表里一定会有Leader,然后还会包含跟Leader保持同步的Follower。
也就是说,只要Leader的某个Follower一直跟他保持数据同步,那么就会存在于ISR列表里。
但是如果Follower因为自身发生一些问题,导致不能及时的从Leader同步数据过去,那么这个Follower就会被认为是“out-of-sync”,从ISR列表里移除。
如果不满足上述两个条件,那就一直写入失败,让生产系统不停的尝试重试,直到满足上述两个条件,然后才能认为写入成功
这个时候万一leader宕机,就可以切换到那个follower上去,那么Follower上是有刚写入的数据的,此时数据就不会丢失了。
关于第二点就需要去配置相应ack参数,才能保证写入Kafka的数据不会丢失。
acks参数,是在Kafka Producer,也就是生产者里设置的。
这个参数实际上有三种常见的值可以设置,分别是:0、1 和 all。
ack = 0,意思就是我的Kafka Producer在客户端,只要把消息发送出去,不管那条数据有没有在哪怕Partition Leader上落到磁盘,直接就认为这个消息发送成功了。
ack = 1,意思就是说只要Partition Leader接收到消息而且写入本地磁盘了,就认为成功了,不管他其他的Follower有没有同步过去这条消息了。这种设置是kafka默认的设置
ack = all,意思就是说,Partition Leader接收到消息之后,还必须要求ISR列表里跟Leader保持同步的那些Follower都要把消息同步过去,才能认为这条消息是写入成功了。
采用这种设置,如果Partition Leader刚接收到了消息,但是结果Follower没有收到消息,此时Leader宕机了,那么客户端会感知到这个消息没发送成功,他会重试再次发送消息过去。
此时可能Partition 2的Follower变成Leader了,此时ISR列表里只有最新的这个Follower转变成的Leader了,那么只要这个新的Leader接收消息就算成功了。这样就可以大概率保证发送端发送的消息不会丢失。
唯一可能导致消费者弄丢数据的情况:就是说,你已经消费到了这个消息,然后消费者那边自动提交了 offset,让 Kafka 以为你已经消费好了这个消息,但其实你才刚准备处理这个消息,但是还没处理,消费端就挂了,此时这条消息就丢了。
解决方案:
关闭自动提交 offset,在处理完之后自己手动提交 offset,就可以保证数据不会丢。但是此时确实还是可能会有重复消费,比如你刚处理完,还没提交 offset,结果自己挂了,此时肯定会重复消费一次,自己保证幂等性就好了。