Kafka使用

pom

xml 复制代码
		<dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>3.3.1</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.19</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>

kafka客户端管理器代码

java 复制代码
/**
 * kafka管理client
 * @author
 * @create 2023-05-31 22:06
 */
public class KafkaTopicDML {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Properties properties = new Properties();
        properties.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,"127.0.0.1:9092");
//        properties.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,"127.0.0.1:9092,127.0.0.1:9093,127.0.0.1:9094");
        KafkaAdminClient client = (KafkaAdminClient) KafkaAdminClient.create(properties);

        //创建topic信息
        /*
         *主题,分区,副本因子
         */
        CreateTopicsResult createTopicsResult = client.createTopics(Arrays.asList(new NewTopic("topic02", 3, (short) 3)));
        createTopicsResult.all().get();//异步创建改成同步创建

        /**
         * 打印kafka信息
         */
        //主题列表
        ListTopicsResult topicsResult = client.listTopics();
        Set<String> names = topicsResult.names().get();
        for (String name : names) {
            System.out.println(name);
        }

        //删除topic
        DeleteTopicsResult deleteTopics = client.deleteTopics(Arrays.asList("topic02"));
        deleteTopics.all().get();//异步变同步

        //查看topic详细信息
//        DescribeTopicsResult describeTopicsResult = client.describeTopics(Arrays.asList("topic02"));
//        Map<String, TopicDescription> topicDescriptionMap = describeTopicsResult.all().get();//异步变同步
//        Set<Map.Entry<String, TopicDescription>> entries = topicDescriptionMap.entrySet();
//        for (Map.Entry<String, TopicDescription> entry : entries) {
//            System.out.println(entry.getKey());
//            System.out.println(entry.getValue());
//            System.out.println("=============");
//        }

        //关闭KafkaAdminClient
        client.close();
    }
}

生产者

java 复制代码
public static void main(String[] args) {
        Properties prop = new Properties();
        prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"localhost:9092");
        prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class);

        KafkaProducer<String, String> producer = new KafkaProducer<>(prop);

       try {
            //构建消息
            ProducerRecord<String, String> record = new ProducerRecord<>("my-topic","my-key","yuyang");
//             ProducerRecord<String, String> record = new ProducerRecord<String, String>("my-topic",1,"my-key","yuyang");//指定分区
           
            //发送消息
            producer.send(record);

            log.info("message is sent");
//            System.out.println("message is sent");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            producer.close();
        }
    }

消费者

java 复制代码
public static void main(String[] args) {
        Properties prop = new Properties();
        prop.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"localhost:9092");
        prop.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        prop.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
        prop.put(ConsumerConfig.GROUP_ID_CONFIG,"group01");//消费者组

        //2.创建Topic消费者
        KafkaConsumer<String,String> consumer=new KafkaConsumer<String, String>(prop);

        //3.调用消费者拉取消息   订阅主题
        consumer.subscribe(Collections.singletonList("my-topic"));
        //3.订阅topic开头的消息队列
//        consumer.subscribe(Pattern.compile("^topic.*$"));

//        List<TopicPartition> partitions = Arrays.asList(new TopicPartition("topic", 0));
//        consumer.assign(partitions);
//        //指定消费分区
//        consumer.seekToBeginning(partitions);//从头开始消费
        consumer.seek(new TopicPartition("my-topic",0),1);//从头开始消费

        while (true) {
        	//每隔1秒拉取一次消息
            ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofSeconds(1));
            if (!consumerRecords.isEmpty()) {
                Iterator<ConsumerRecord<String, String>> iterator = consumerRecords.iterator();
                while (iterator.hasNext()) {
                    //获取一个消息
                    ConsumerRecord<String, String> record = iterator.next();
                    String topic = record.topic();
                    System.out.println("topic = " + topic);
                    int partition = record.partition();
                    System.out.println("partition = " + partition);
                    long offset = record.offset();
                    System.out.println("offset = " + offset);

                    String key = record.key();
                    System.out.println("key = " + key);
                    String value = record.value();
                    System.out.println("value = " + value);
                    long timestamp = record.timestamp();
                    System.out.println("timestamp = " + timestamp);
                }
            }
        }
    }

消费者自定义分区

java 复制代码
        KafkaConsumer<String,String> consumer=new KafkaConsumer<String, String>(prop);

        List<TopicPartition> partitions = Arrays.asList(new TopicPartition("topic", 0));
        consumer.assign(partitions);
        //指定消费分区的位置
        consumer.seekToBeginning(partitions);//从头开始消费
        
        //设置消费分区指定位置
//        consumer.seek(new TopicPartition("topic01",0),1);//从偏移量  开始消费

生产者分区策略

java 复制代码
        //自定义分区策略
        prop.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, UserDefinePartitioner.class);

自定义分区类

java 复制代码
public class UserDefinePartitioner implements Partitioner {
    private AtomicInteger counter = new AtomicInteger(0);
    /**
     * 返回分区号
     * @param topic
     * @param key
     * @param keybytes
     * @param value
     * @param valuebytes
     * @param cluster
     * @return
     */
    @Override
    public int partition(String topic, Object key, byte[] keybytes,
                         Object value, byte[] valuebytes, Cluster cluster) {
        //获取所有的分区
        List<PartitionInfo> partitionInfos = cluster.partitionsForTopic(topic);
        int numPartitions = partitionInfos.size();
        if (keybytes == null) {
            int addIncrement = counter.getAndIncrement();
            return (addIncrement&Integer.MAX_VALUE)%numPartitions;
        }else {
            return Utils.toPositive(Utils.murmur2(keybytes))%numPartitions;
        }
    }

    @Override
    public void close() {
        System.out.println("close");
    }

    @Override
    public void configure(Map<String, ?> map) {
        System.out.println("configure");
    }
}

生产者自定义拦截器

java 复制代码
        prop.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, UserDefineInterceptor.class.getName());

自定义的拦截器类

java 复制代码
public class UserDefineInterceptor implements ProducerInterceptor {

    @Override
    public ProducerRecord onSend(ProducerRecord producerRecord) {
        return new ProducerRecord(producerRecord.topic(),producerRecord.key(),producerRecord.value()+":===");
    }

    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
        System.out.println("metadata = " + metadata);
        System.out.println("exception = " + exception);
    }

    @Override
    public void close() {
        System.out.println("close");
    }

    @Override
    public void configure(Map<String, ?> map) {
        System.out.println("configure");
    }
}

拦截器作用:

生产者发送消息时,对数据做一些修饰,装饰;确定消息的生命周期;获取消息的元信息

高级API

offset自动控制

java 复制代码
prop.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");//(最早)自动将偏移量重置为最早偏移量
        
prop.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"laster");//(最近)自动将偏移量重置为最新的偏移量
        
prop.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"none");//如果未找到消费者组的先前偏移量,则向消费者抛出异常

Kafka消费者在消费数据时默认会定期提交消费的偏移量,保证所有的消息至少可以被消费者消费一次,看通过以下两个参数配置

enable.auto.commit=true 默认 //配置offset自动提交

auto.commit.interval.ms=5000 默认

如果用户需要自己管理offset的自动提交,可以关闭offset的自动提交,手动管理offset提交的偏移量,手动提交的offset偏移量永远都要比本次消费的偏移量+1,因为提交的offset是kafka消费者下一次抓取数据的位置

java 复制代码
        //配置offset自动提交时间   5s一次   自动提交offset
        prop.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,5000);
        prop.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,true);

手动提交

java 复制代码
 Properties prop = new Properties();
        prop.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"localhost:9092");
        prop.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        prop.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
        prop.put(ConsumerConfig.GROUP_ID_CONFIG,"group01");

        //./bin/kafka-topics.sh --bootstrap-server localhost:9092 --create --topic topic01 --partitions 3l--replication-factor 3
        prop.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");//从最早的位置

        //关闭 offset偏移量自动提交
        prop.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,false);

        //2.创建Topic消费者
        KafkaConsumer<String,String> consumer=new KafkaConsumer<String, String>(prop);
        //3.订阅topic开头的消息队列
        consumer.subscribe(Arrays.asList("my-topic"));

        while (true) {
            ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofSeconds(1));
            if (!consumerRecords.isEmpty()) {
                Iterator<ConsumerRecord<String, String>> iterator = consumerRecords.iterator();

                //记录分区的消费元数据信息
                Map<TopicPartition, OffsetAndMetadata> offsetMap = new HashMap<>();

                while (iterator.hasNext()) {
                    //获取一个消息
                    ConsumerRecord<String, String> record = iterator.next();
                    String topic = record.topic();
                    System.out.println("topic = " + topic);
                    int partition = record.partition();
                    System.out.println("partition = " + partition);
                    long offset = record.offset();
                    System.out.println("offset = " + offset);

                    String key = record.key();
                    System.out.println("key = " + key);
                    String value = record.value();
                    System.out.println("value = " + value);
                    long timestamp = record.timestamp();
                    System.out.println("timestamp = " + timestamp);

                    //记录消费分区的偏移量元数据
                    offsetMap.put(new TopicPartition(topic,partition),new OffsetAndMetadata(offset));
                    //consumer同步提交  提交消费者偏移量
//                    consumer.commitSync(offsetMap);
                    //consumer异步提交  提交消费者偏移量
                    consumer.commitAsync(offsetMap, new OffsetCommitCallback() {
                        @Override
                        public void onComplete(Map<TopicPartition, OffsetAndMetadata> offsetMap, Exception e) {
                            System.out.println("offsetMap = " + offsetMap + " ,Exception" + e);
                        }
                    });
                }
            }
        }

ACKS & Retries

Kafka生产者在发送完一个消息后,要求Broker在规定的世界内ACK应答,如果没在规定时间内应答,Kafka生产者会尝试n此重发消息。

ACKS 默认1

acks=1 Leader会将record(消息记录)写到本地日志中,但不会等待所有的Follower完全确认的情况下做出响应。这种情况下,如果Leader在确认记录后立即失效,但在Follower复制记录之前失败,则记录将丢失。

acks=0 生产者根本不会等待服务器的确认,该记录立即添加到套接字缓冲区中并视为已发送。这种情况下,不保证服务器已收到该记录(应用于日志)
acks=-1 Leader将订单全部副本同步确认记录,这保证了只要有有一个同步副本处于活动状态,该记录就不会丢失。这个是最有力的保证。这等效于acks=all

如果生产者在规定时间内,没有得到Leader的ack应答,Kafka可以开启reties机制。

request.timeout.ms = 30000 默认

retries = 2147483647 默认

java 复制代码
        prop.put(ProducerConfig.ACKS_CONFIG,-1);
        //重试次数, 不包含第一次发送
        prop.put(ProducerConfig.RETRIES_CONFIG,3);
        //将检测超时时间设置为1ms
        prop.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG,1);

幂等写

幂等特性:不多不少,不丢不重

什么是幂等:一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外)。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。幂等又称为exactly once

Kafka在0.11.0.0版本支持增加了对幂等的支持。幂等是针对生产者角度的特性。幂等可以保证上生产者发送的消息,不会丢失,而且不会重复。实现幂等的关键点就是服务端可以区分请求是否重复,过滤掉重复的请求。要区分请求是否重复的有两点:

唯一标识:要想区分请求是否重复,请求中就得有唯一标识。例如支付请求中,订单号就是唯一标识

记录下已处理过的请求标识:光有唯一标识还不够,还需要记录下那些请求是已经处理过的,这样当收到新的请求时,用新请求中的标识和处理记录进行比较,如果处理记录中有相同的标识,说明是重复记录,拒绝掉。

要停止多次处理消息,必须仅将其持久化到Kafka Topic中仅仅一次。在初始化期间,kafka会给生产者生成一个唯一的ID称为Producer ID或PID。

PID和序列号与消息捆绑在一起,然后发送给Broker。由于序列号从零开始并且单调递增,因此,仅当消息的序列号比该PID / TopicPartition对中最后提交的消息正好大1时,Broker才会接受该消息。如果不是这种情况,则Broker认定是生产者重新发送该消息。

enable.idempotence= false 默认

注意:在使用幂等性的时候,必须开启retries=true和acks=all

java 复制代码
		//设置kafka  acks和retries
        prop.put(ProducerConfig.ACKS_CONFIG,-1);
        //不包含第一次发送,如果尝试发送3次;失败,则系统放弃发送
        prop.put(ProducerConfig.RETRIES_CONFIG,3);
        //检测超时的时间设置为1ms
        prop.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG,1);

        //开启kafka的幂等性
        prop.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,true);
        //让记录严格有序
        //有多于n个记录未被应答,客户端会被阻塞
        prop.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION,1);

一个是唯一标识,一个是开启kafka幂等的特性

kafka事务

kafka的幂等性,只能保证一条记录在分区发送的原子性,但要保证多条记录(多分区)之间的完整性,就需要开启kafka的事务操作了。

在Kafka0.11.0.0除了引入的幂等性的概念,同时也引入了事务的概念。通常kafka的事务分为生产者事务Only、消费者&生产者事务。默认消费者消费的消息级别是read_uncommited数据,这有可能读取到事务失败的数据,所有再开启生产者事务之后,需要用户设置消费者的事务隔离级别

isolation.level = read_uncommitted 默认

该选项有两个值read_committed|read_uncommitted,如果开始事务控制,消费端必须将事务的隔离级别设置为read_committed

开启的生产者事务时,只需要指定transactional.id属性即可,一旦开启了事务,默认生产者就已经开启了幂等。但要求transactional.id的取值必须是唯一的,同一时刻只能有一个transactional.id存储在,其他的将会被关闭。

生产者

java 复制代码
//1.创建链接参数
        Properties props=new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());

        //必须配置事务ID,必须是唯一的
        props.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"transaction-id"+ UUID.randomUUID().toString());
        //配置kafka批处理大小
        props.put(ProducerConfig.BATCH_SIZE_CONFIG,1024);
        //等待5ms如果batch中数量不足1024大小,也会发送
        props.put(ProducerConfig.LINGER_MS_CONFIG,5);
        //开启kafka的重试机制和幂等性
        props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,true);
        props.put(ProducerConfig.ACKS_CONFIG,-1);
        props.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG,10000);//请求超时

        //2.创建生产者
        KafkaProducer<String,String> producer=new KafkaProducer<String, String>(props);
        producer.initTransactions();//初始化事务

        try{
            producer.beginTransaction();//开启事务
            //3.封账消息队列
            for(Integer i=0;i< 10;i++){
                Thread.sleep(10000);
                ProducerRecord<String, String> record = new ProducerRecord<>("topic01", "key" + i, "value" + i);
                producer.send(record);
                producer.flush();
            }
            producer.commitTransaction();//提交事务
        }catch (Exception e){
            producer.abortTransaction();//终止事务
        }
        producer.close();

消费者

java 复制代码
		
		
相关推荐
DC_BLOG1 小时前
Linux-GlusterFS进阶分布式卷
linux·运维·服务器·分布式
gu201 小时前
c#编程:学习Linq,重几个简单示例开始
开发语言·学习·c#·linq
点点滴滴的记录2 小时前
分布式之Raft算法
分布式
桃林春风一杯酒4 小时前
HADOOP_HOME and hadoop.home.dir are unset.
大数据·hadoop·分布式
逻各斯8 小时前
Redisson分布式锁java语法, 可重入性实现原理 ,(还有可重试性,超时不释放,主从一致性)
分布式
WeiLai11129 小时前
面试基础--微服务架构:如何拆分微服务、数据一致性、服务调用
java·分布式·后端·微服务·中间件·面试·架构
奔跑吧邓邓子10 小时前
【Python爬虫(44)】分布式爬虫:筑牢安全防线,守护数据之旅
开发语言·分布式·爬虫·python·安全
茶本无香10 小时前
kafka+spring cloud stream 发送接收消息
spring cloud·kafka·java-zookeeper
转身後 默落11 小时前
11.Docker 之分布式仓库 Harbor
分布式·docker·容器
奔跑吧邓邓子11 小时前
【Python爬虫(45)】Python爬虫新境界:分布式与大数据框架的融合之旅
开发语言·分布式·爬虫·python·大数据框架