Kafka中的恰好一次交付
Kafka支持三种交付语义,至少一次(At least once semantics)、至多一次(At most once semantics)、恰好发送一次(Exactly once semantics),这篇文章讲解了Kafka是如何实现第三种的
任何时候都可能发生Broker节点下线,针对这种情况,Kafka保证了每条消息被持久化并且冗余多份(作者按:建议约定topic的配置replication.factor参数值必须大于1,要求每个 partition必须有至少2个副本)。不过也有可能生产者到Broker节点的RPC失败(The producer-to-broker RPC can fail),如果此时消息已经写入Broker,但是还没来得及给生产者发送ACK确认就崩溃了,生产者无法感知原因,它只能等待一段时间后重试,最后导致消费者重复消费。另外一种情况就是客户端也可能会失败。所以在错综复杂的环境中,确保只交付一次是非常有挑战的工程问题
Kafka 0.11.x解决了这个问题,重点是下面提到的三点
1、幂等性(Idempotence: Exactly once in order semantics per partition)
每批消息将包含一个序列号,通过序列号校验过滤重复消息,即使主节点挂了,重新选举的节点也能感知到是否有重复(this sequence number is persisted to the replicated log, so even if the leader fails, any broker that takes over will also know if a resend is a duplicate),可以通过设置在生产端配置”enable.idempotence=true”开启这个功能。作者按:建议约定服务端配置min.insync.replicas参数必须大于1,要求leader感知至少还有一个follower保持心跳连接
2、事务(Transactions: Atomic writes across multiple partitions)
需要保证跨多个Topic-Partition的数据要么全部写入成功,要么全部失败,不会出现中间状态,才能保证幂等性写入
producer.initTransactions();
try {
producer.beginTransaction();
producer.send(record1);
producer.send(record2);
producer.commitTransaction();
} catch(ProducerFencedException e) {
producer.close();
} catch(KafkaException e) {
producer.abortTransaction();
}
复制代码
需要注意的是,消费者可能读取到的是中间状态,不过可以配置参数isolation.level=read_committed,表示事务提交后可获取,另外一个可选值是read_uncommitted,表示不等待事务提交,正常通过offset order读取
3、流处理(The real deal: Exactly once stream processing in Apache Kafka)
数据处理失败后,还能访问到原始输入数据,然后再执行处理操作,也就是说保证输入数据源的可依赖性才能保证恰好一次正确交付。Kafka是通过将数据透明地fold起来合并成原子性操作以事务的形式写入多个分区,如果失败就回滚关联状态,不需要重新设置offset,整个过程不涉及原始数据获取的幂等性操作
文章来源:www.liangsonghua.me
作者介绍:京东资深工程师-梁松华,长期关注稳定性保障、敏捷开发、JAVA高级、微服务架构