Kafka-Kafka 如何避免重复消费?

1. 使用消费者组(消费者端)

确保每个分区的消息只被一个消费者实例消费。

2. 使用幂等生产者(生产者端)

启用幂等生产者只需要在生产者配置中设置enable.idempotence=true。幂等生产者确保消息在网络或其他错误导致重试时不会被重复写入 Kafka,通过为每个消息分配唯一的序列号来实现幂等性。

props.put("enable.idempotence", "true");

3. 使用事务性生产者和消费者(性能差)

生产者可以将一组消息作为一个事务写入Kafka,消费者也可以在一个事务中读取和处理消息。要使用事务性生产者,需要配置transactional.id

Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("transactional.id", "my-transactional-id");
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
producer.initTransactions();

4. 手动提交偏移量(精细控制偏移量)

通过commitSync()或commitAsync()方法来实现。Kafka存在再均衡策略;当有新的消费者进入或者消费者移除时,需要暂替当前消费者组中的所有消费者,并重新规划offset,这就称为再均衡。

自动提交偏移量:
默认时间为5s,假设在最后依次提交偏移量之后的三秒发生了再均衡,此时各个消费者需要重新读取各个分区最后一次提交的偏移量,此时读取到的就是三秒前的,这就会导致之前的三秒消费的数据被重复消费。

手动提交偏移量:

commitSync():手动提交,可以是每处理一条消息提交一次或者是处理完一批消息提交依次;

commitAsync():异步手动提交,消费者只管发送提交请求,不需要等待Broker的立即回应;但是他与commitSync()不同的是,当他失败后会一直重试,假如发起了一个异步提交commitA,此时提交的偏移量为1000,随后又发起了一个异步提交commitB且偏移量为2000,如果commitA提交失败,但commit B提交成功,那么此时对commitA进行重试并成功的话,则会把实际上已经提交的偏移量从2000回滚到1000,导致重复消费。

解决方法:

1.使用单调递增的提交机制

// 维护一个原子变量记录已提交的最大偏移量
private AtomicLong lastCommittedOffset = new AtomicLong(-1);

// 在提交时检查
long currentOffset = getCurrentOffset();
if (currentOffset > lastCommittedOffset.get()) {
    consumer.commitAsync(offsets, new OffsetCommitCallback() {
        @Override
        public void onComplete(Map<TopicPartition, OffsetAndMetadata> offsets, Exception e) {
            if (e == null) {
                // 只有成功时才更新lastCommittedOffset
                lastCommittedOffset.updateAndGet(prev -> Math.max(prev, currentOffset));
            }
        }
    });
}

其他方法:

按分区管理偏移量:为每个分区单独维护已提交的偏移量

实现幂等消费:使消息处理逻辑具备幂等性

限制重试次数:为异步提交设置合理的重试次数上限

监控提交延迟:监控提交延迟情况,及时发现潜在问题

5. 使用外部存储来管理偏移量

也就是上面提到的原子偏移量。

6. 去重逻辑

在消息处理逻辑中引入去重机制。例如,可以使用消息的唯一标识符(如消息ID)在处理前检查是否已经处理过该消息,从而避免重复处理。

7. 幂等的消息处理逻辑

设计消息处理逻辑时,尽量使其成为幂等操作,即相同的消息即使被处理多次也不会产生副作用。

例如,在数据库操作时,可以使用UPSERT操作(更新插入)来确保数据的一致性。

数据库UPSERT示例

假设我们有一个用户积分系统,当用户完成订单时,系统会给用户增加积分。使用UPSERT可以确保:

-- 非幂等操作(有问题)
UPDATE user_points SET points = points + 100 WHERE user_id = '123';

-- 幂等操作(使用UPSERT)
INSERT INTO user_points (user_id, points) 
VALUES ('123', 100)
ON DUPLICATE KEY UPDATE points = VALUES(points);

问题在于,如果第一条SQL被执行多次,用户的积分会不断增加。而第二条SQL无论执行多少次,用户的积分都会被设置为100(而不是累加)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值