文章目录
一 数据漏消费和重复消费
无论是同步提交还是异步提交offset,都有可能会造成数据的漏消费或者重复消费。先提交offset后消费,有可能造成数据的漏消费;而先消费后提交offset,有可能会造成数据的重复消费。
重复消费解决方案:
(1)下游去重:但是缺点是kafka采取了幂等性和事务,保证了kafka服务端消息没有重复的情况下,下游去重浪费了kafka的性能
(2)确保数据的消费和提交两个操作是原子性的,要么同时成功,要么同时失败。原子化绑定的前提是提交动作不能往kafka服务端提交,因为往kafka服务端提交与消费动作无法实现原子绑定。因此想实现原子化绑定,需要自己保存offset,而不是让kafka帮我们保存offset。
手动保存offset
package com.hike.consumer;
import org.apache.kafka.clients.consumer.ConsumerRebalanceListener;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;
import java.io.*;
import java.util.*;
/**
* 自定义保存offset
*/
public class ConsumerManual {
//记录每个消费者消费的offset,缓存
private static Map<TopicPartition,Long> offset = new HashMap<TopicPartition, Long>();
//用来保存Hash值
private static String file = "d:/offset";
public static void main(String[] args) throws IOException, InterruptedException {
//1 新建一个consumer对象
Properties properties = new Properties();
properties.load(Consumer.class.getClassLoader().getResourceAsStream("consumer1.properties"));
final KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(properties);
//2 订阅话题,拉取消息
consumer.subscribe(Collections.singleton("hello"),
//每当有一个新的消费者加入到consumerGroup都会重新进行分区分配
new ConsumerRebalanceListener() {
//新加入进来的consumer应该从之前组中的consumer消费过的地方开始消费,而不是从0开始
//之前由kafka服务器负责告知,现在采用自定义保存offset的方式,服务器不知道消费的位置
//需要手动的告诉新加入的消费者,需要分别执行以下两个函数,完成此项功能
//分区分配之前做的事情
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
//各个消费者应该将旧的offset提交
commit();
}
//分区分配之后做的事情
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
//各个消费者应该将offset遍历,获取新的offset
readOffset(partitions);
//遍历所有分区,将offset读取出来,告诉消费者从哪里开始消费
for (TopicPartition partition : partitions) {
Long os = offset.get(partition);
if (os == null) {
consumer.seek(partition, 0);
} else {
consumer.seek(partition, os);
}
}
}
});
//消费消息
while(true){
ConsumerRecords<String, String> records = consumer.poll(2000);
//将此部分的操作原子绑定
{
for (ConsumerRecord<String, String> record : records) {
System.out.println(record);
//将获取到的数据存放到高速缓存中
offset.put(
new TopicPartition(record.topic(), record<