KAFKA API操作

先启动zookeeper
然后启动kafka
创建topic 在node02 节点
kafka-topics.sh --zookeeper node02:2181/kafka --create --topic xxxx --partitions 2 --replication-factor 2

 

package com.msb.zk;

import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

@SpringBootTest
class ZkApplicationTests {

    @Test
    void contextLoads() {
    }

    /*
    创建topic
    * kafka-topics.sh --zookeeper node02:2181/kafka --create --topic xxxx --partitions 2 --replication-factor 2
    *

    */
    @Test
    public void  producer() throws ExecutionException, InterruptedException {
        String topic="xxxx";
        Properties properties = new Properties();
        properties.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.1.137:9092,192.168.1.138:9092,192.168.1.139:9092" );
        //kafka 持久化数据的MQ 数据->byte[] 不会对数据进行干预 双发要约定编解码
        //kafka 是一个application 使用零拷贝 sendfile 系统调用实现快速数据消费
        properties.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
        //现在producer就是一个提供者 面向的其实是broker 虽然在使用的时候我们期望把数据打入topic
        /**
         * xxxx
         * 2 partitions
         * 2 replication
         * 分区里面的消息是有序的 分区间的消息是无序的
         * 一个生产者可以向多个分区发送数据
         * 但是相同的key一定会达到一个分区中
         */
        /**
         * 三种商品  每种商品有线性的3个ID
         * 相同的商品 最好去到一个分区里
         */
       while (true){
           for (int i = 0; i <3 ; i++) {//3商品
               for (int j = 0; j <3 ; j++) {//3个id
                   ProducerRecord<String, String> record = new ProducerRecord<>(topic, "item"+j,"value" + i);
                   Future<RecordMetadata> send = producer.send(record);
                   RecordMetadata rm = send.get();
                   int partition = rm.partition();
                   long offset = rm.offset();
                   System.out.println("key:"+record.key()+"  val:"+record.value()+"  partitions:"+partition+"  offset:"+offset);

               }
           }

       }
    }
    @Test
    public void consumer(){
        //基础配置
        Properties properties = new Properties();
        properties.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.1.137:9092,192.168.1.138:9092,192.168.1.139:9092");
        properties.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
        /**
         * 消费者细节   消费者从属于哪个组  一个组里有多少个consumer
         * 一个组里的consumer可以消费一个分区   一个分区可以被一个组里的一个consumer消费  但是可以被不同分区的consumer 消费
         */
            properties.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "xxxx03");
            //KAFKA 是MQ 也是一个STORAGE
        //What to do when there is no initial offset in Kafka or if the current offset does not exist any more on the server
        // (e.g. because that data has been deleted):
        // <ul><li>earliest: automatically reset the offset to the earliest offset<li>
        // latest: automatically reset the offset to the latest offset</li><li>
        // none: throw exception to the consumer if no previous offset is found for the consumer's group</li>
        // <li>anything else: throw exception to the consumer.</li></ul>
        //第一次启动没有offset
           // properties.setProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");//会追溯到历史消息最后 等最新的消息
        properties.setProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
        /**开启自动提交 异步提交
         * 上面说如果没有找到offset offset 是一个持久化单进程的   但刚起动是没有offset的
         * 当offset拉过来了还没计算的 就提交了  就会丢失数据
         */
        /*当properties.setProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest")//此时是尽早的获取数据
        properties.setProperty(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"false")//关闭自动提交  会造成数据的重复消费
        当properties.setProperty(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"true")
        自动提交间隔是properties.setProperty(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "15000"); 就算开启了自动提交 也会等到15秒以后自动提交
        但是在这15秒以内 容易造成数据的丢失
        一个运行的consumer,那么自己会维护自己的消费进度
        一旦你自动提交,但是是异步的
        1还没到时间,挂了 没提交 ,那么重启一个consumer 参照offset的时候 会重复消费
        2一个批次的数据还没写数据库成功  但是这个批次的offset 异步提交了, 这个时候挂了,重启一个consumer 参照offset的时候 会丢失消息
        [root@node02 node02]# kafka-consumer-groups.sh --bootstrap-server 192.168.1.137:9092 --describe --group xxxx01
        Consumer group 'xxxx01' has no active members.

        TOPIC           PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG             CONSUMER-ID     HOST            CLIENT-ID
        xxxx            1          44255           51841           7586            -               -               -
        xxxx            0          22127           25928           3801            -               -               -
        这里的当前offset 只消费到44255  但是日志记录到51841 这里 下次会从日志加载 减去LAG  就是下次加载的位置 但是一旦宕机 数据丢失 会造成重复消费
        * */
            properties.setProperty(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"true");//自动提交 异步提交 容易丢数据 重复数据
           // properties.setProperty(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "15000");//默认自动提交间隔5秒
            //properties.setProperty(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, );//kafka是发布订阅的 靠的是poll拉取数据  弹性 按需的  拉取多少

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
        //先去订阅 topic,后面可以接参数    kafka 的consumer会动态负载均衡
        consumer.subscribe(Arrays.asList("xxxx"), new ConsumerRebalanceListener() {
            //消失那些分区
            @Override
            public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
                System.out.println("----------onPartitionsRevoked------------");
                Iterator<TopicPartition> iterator = partitions.iterator();
                while (iterator.hasNext()){
                    System.out.println(iterator.next());
                }
            }

            //得到哪些分区
            @Override
            public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
                System.out.println("----------onPartitionsAssigned------------");
                Iterator<TopicPartition> iterator = partitions.iterator();
                while (iterator.hasNext()){
                    System.out.println(iterator.next());
                }


            }
        });
        while(true){
            /*
            如果想多线程处理多分区
            每poll一次 用一个语义:一个job启动
            一次job用多线程并行处理分区
            且 job应该被控制是串行的  一个job没跑完 后面的job会堆积
            * */
            //拉取 微批
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(0));//超时时间  有了数据就立刻返回 0-n条
            /**kafka-consumer-groups.sh --bootstrap-server 192.168.1.137:9092 --list
             *[root@node02 node02]# kafka-consumer-groups.sh --bootstrap-server 192.168.1.137:9092 --describe --group xxxx01
             *
             * TOPIC           PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG             CONSUMER-ID                                     HOST            CLIENT-ID
             * xxxx            0          12              12              0               consumer-1-08faeb7f-c474-4466-ab64-39c0e3ccf64b /192.168.1.4    consumer-1
             * xxxx            1          24              24              0               consumer-1-08faeb7f-c474-4466-ab64-39c0e3ccf64b /192.168.1.4    consumer-1
             */
            if(!records.isEmpty()) {
                //以下代码的优化很重要
                System.out.println("---------------"+records.count()+"---------------------");
                Set<TopicPartition> partitions = records.partitions();//每次poll的时候是去取多个分区的数据
                //分区内的数据时有序的
                /*如果手动提交offset
                * 1按消息进度同步提交
                * 2按分区粒度同步提交
                * 3按当前poll的批次同步提交
                * */
                for (TopicPartition partition : partitions) {
                    List<ConsumerRecord<String, String>> records1 = records.records(partition);
                    //在一个微批里 按分区取poll回来的数据
                    //线性按分区处理,还可以并行按分区处理 用多线程的方式
                  Iterator<ConsumerRecord<String, String>> iterator = records1.iterator();
                    while (iterator.hasNext()){
                        ConsumerRecord<String, String> next = iterator.next();
                        int partition1 = next.partition();
                        long offset = next.offset();

                        System.out.println("key:"+next.key()+"  value:"+next.value()+"  partition:"+next.partition()+"  offset:"+next.offset());
                        TopicPartition TopicPartition = new TopicPartition("xxxx", partition1);
                        //(Map<TopicPartition, OffsetAndMetadata> offsets, OffsetCommitCallback callback)
                        OffsetAndMetadata OffsetAndMetadata = new OffsetAndMetadata(offset);
                        Map<TopicPartition,OffsetAndMetadata> map =new HashMap();
                        map.put(TopicPartition, OffsetAndMetadata);
                        consumer.commitAsync(map, new OffsetCommitCallback() {
                            @Override
                            public void onComplete(Map<org.apache.kafka.common.TopicPartition, org.apache.kafka.clients.consumer.OffsetAndMetadata> map, Exception e) {

                            }
                        });//这个是最安全的 每条记录级的更新  第一点
                        //单线程 多线程 都可以
                        long offset1 = records1.get(records1.size() - 1).offset();
                        OffsetAndMetadata pom = new OffsetAndMetadata(offset);
                        Map<TopicPartition,OffsetAndMetadata> map1 =new HashMap();
                        map1.put(TopicPartition, pom);

                    }
                    consumer.commitAsync();//按分区粒度提交 第二点
                    /*因为你都拿到分区了
                    拿到分区的数据集
                    期望的是对数据 整体加工
                    问 你怎么知道最后一条offset
                    * */
                }
                consumer.commitAsync();//这个是poll的批次提交offset  第三点
               /* Iterator<ConsumerRecord<String, String>> iterator = records.iterator();
                while (iterator.hasNext()){
                    //因为一个consumer 可以消费多个分区  但是一个分区只能给一个组里的一个consumer消费  所以在遍历的时候可以发现他们是来自不同分区的
                    ConsumerRecord<String, String> record = iterator.next();
                    int partition = record.partition();
                    long offset = record.offset();

                    System.out.println("key:"+record.key()+"  value:"+record.value()+"  partition:"+record.partition()+"  offset:"+record.offset());

                }
*/
            }

        }


    }
}

 

posted @ 2022-04-21 10:19  花心大萝卜li  阅读(63)  评论(0)    收藏  举报