Kafka实现精确一次(exactly once)发送消息的原理

语义介绍

At-least-once(最少一次):如果生产者从Kafka broker接收到一个确认(ack)并且ack = all,这意味着消息已经被写入Kafka topic一次。然而,如果生产者ack超时或收到一个错误,它可能会重试发送消息,假设消息没有写入Kafka topic,如果broker在它发送ack之前失败,但在消息被成功写入Kafka topic后,此重试将导致消息被写入两次,因此将不止一次地传递给最终使用者。这种方法可能会导致消息重复和错误的结果。

At-most-once(最多一次):如果在ack超时或返回错误时生产者没有重试,那么消息可能最终不会被写入Kafka topic,因此不会传递给消费者。这种方法避免了消息重复的可能性,但可能会导致消息。

Exactly-once(精确一次):即使生产者重复发送消息,消息也只被传递一次给最终消费者。需要消息传递系统本身与产生者和消费者之间的合作。如果在成功消费了一条消息后,你将你的Kafka消费者回滚到之前的偏移量,你将再次收到从该偏移量到最新偏移量的所有消息。Exactly-once提交也必须考虑到客户端失败。broker应该丢弃由僵尸机发送的消息。对于消费者来说也是如此;一旦启动了新的客户端实例,它必须能够从失败实例遗留的任何状态恢复,并从安全点开始处理。这意味着消耗的偏移量必须始终与产生的输出保持同步。

Kafka中的持久性依赖于生产者从代理接收到的ack。当关闭ack机制,生产者发送消息后就不管了,也就是只发送一次消息,可能会因为网络等原因造成数据丢失。
 

如何实现精确一次处理语义

幂等的producer
在创建producer客户端的时候,添加配置props.put("enable.idempotence", ture),producer就变成幂等的了。底层实现就是对每条消息生成一个id值,broker会根据这个id值进行去重,从而实现幂等,这样一来就能够实现精确一次的语义了。
幂等的producer有两个主要是缺陷:
幂等性的producer仅做到单分区上的幂等性,即单分区消息不重复,多分区无法保证幂等性。
只能保持单会话的幂等性,无法实现跨会话的幂等性,如果producer挂掉再重启,无法保证两个会话间的幂等(新会话可能会重发)。因为broker端无法获取之前的状态信息,所以无法实现跨会话的幂等。

事务的producer
当遇到上述幂等性的缺陷无法解决的时候,可以考虑使用事务了。事务可以支持多分区的数据完整性,原子性。并且支持跨会话的exactly once处理语义,即使producer宕机重启,依旧能保证数据只处理一次。

开启事务首先需要开启幂等性,即设置enable.idempotence为true。然后对producer消息发送做事务控制。

//初始化事务
producer.initTransactions();
try {
    //开启一个事务
    producer.beginTransaction();
    producer.send(record1);
    producer.send(record2);
    //提交
    producer.commitTransaction();
} catch (KafkaException e) {
    //出现异常的时候,终止事务
    producer.abortTransaction();
}


无论开启幂等还是事务的特性,都会对性能有一定影响。

所以kafka默认也并没有开启这两个特性,而是交由开发者根据自身业务特点进行处理。
 

上一篇:网络层协议(三次握手和四次挥手详解)


下一篇:浅谈tcp十大特性