幂等性
生产者在进行重试的时候有可能会重复写入消息,而使用Kafka的幂等性功能之后就可以避免这种情况。
在生产者客户端配置enable.idempotence设置为true。如果配置了这个参数,那么retries必须大于0,acks=-1,max.in.flight.requests.per.connection不能大于5。
Kafka为此引入了product id(PID)和序列号这两个概念,每个新的生产者实例在初始化的时候都会被分配一个PID,对于每一个PID,消息发送到的每个分区都有对应的序列号,这些序列号从0开始单调递增。生产者每发送一条消息就会将<PID, 分区>对应的序列号的值加1。broker端收到消息后,会将当前消息的序列号和broker维护的序列号进行对比,如果比broker中的序列号大1,那么会接收它;如果小于等于,则丢弃;如果大于1,则说明可能消息丢失了,对应的生产者会抛出异常。
- 引入序列号实现幂等也只是针对每一对<PID, 分区>而言的,也就是说,Kafka的幂等只能保证单个生产者会话中单分区的幂等 。
事务
幂等性不能跨多个分区运作,而事务可以弥补这个缺陷。事务可以保证对多个分区写入操作的原子性。操作的原子性是指多个操作要么全部成功,要么全部失败。
对流式应用而言,一个典型的应用模式“consumer-transform-produce”。在这种模式下消费和生产并存:应用程序从某个主题中消费消息,然后经过一系列转换后写入另一个主题,消费者可能在提交消费位移的过程中出现问题而导致重复消费,也可能生产者重复生产。Kafka中的事务可以使应用程序将消费消息、生产消息、提交消费位移当作原子操作来处理,同时成功或失败,即使该生产或消费会跨多个分区。
为了实现事务,应用程序必须提供唯一的transactionalId,通过客户端transactional.id来显式设置。
事务要求生产者客户端必须开始幂等特性,因此设置transactionalId.id后也要将enable.idempotence设置为true。
transactionalId与PID一一对应,transactionalId是由用户自己指定的,PID是kafka内部分配的,为了保证新的生产者启动后具有相同transactionalId的旧生产者能够立即失效,还会获得一个单调递增的producer epoch。
- 从生产者的角度分析:
具有相同transactionalId的新生产者示例被创建且工作的时候,旧的且拥有相同transactionalId的生产者实例将不再工作。
当某个生产者实例宕机后,新的生产者实例可以保证任何未完成的旧事务要么被提交,要么被终止,如此可以使新的生产者实例从一个正常的状态开始工作。
Kafka提供了5个和事务相关的方法。
void initTransactions(); //初始化事务
void beginTransaction(); //开始事务
void sendOffsetsToTransaction(Map<TopicPartition, OffsetAndMetadata> offsets, String consumerGroupId); //为消费者提供在事务内的位移提交的操作
void commitTransaction(); //提交事务
void abortTransaction(); //回滚事务
producer.initTransactions(<