Kafka--JavaAPI-★★★★★

环境准备

  • pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>heima43</artifactId>
        <groupId>cn.hanjiaxiaozhi</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>KafkaStudy</artifactId>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <encoding>UTF-8</encoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-streams</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- java编译插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

生产者

版本说明

  • Kafka 0.10.0.0 及以后的版本,对生产者代码的底层实现进行了重构。
  • katka.producer.Producer类被org.apache.kafka.clients.producer.KafkaProducer类替换。
  • Kafka 系统支持两种不同的发送方式–同步模式(Sync)和异步模式(ASync)

同步和异步

  • 同步模式如下:一般不使用
    在这里插入图片描述
  • 异步模式如下:大数据场景下,效率更高
    在这里插入图片描述

代码实现

在这里插入图片描述

package cn.hanjiaxiaozhi.producer;

import org.apache.kafka.clients.producer.*;

import java.util.Properties;

/**
 * Author hanjiaxiaozhi
 * Date 2020/7/10 15:50
 * Desc 演示Kafak-JavaAPI实现Kafak生产者-同步和异步发送消息
 */
public class MyKafkaProducer {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //注意TODO原本表示该任务没有完成,后续需要继续编写,这里使用仅仅为了步骤清晰
        //TODO 1.准备连接参数
        Properties props = new Properties();
        //kafka集群地址
        props.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"node01:9092");
        //消息确认机制--表示是否发送成功
        //0:表示只要发出去就认为发送成功,可能会有数据丢失,一般不用,除非对性能要求特别高,且不在乎数据丢失
        //1:表示只要leaader收到就认为发送成功,开发中可以使用,但是如果对于数据安全要求还是较高,该配置不适合
        //-1/all:表示所有的ISR副本(Leader+Follower)都收到才认为发送成功,也就是必须等到Follower把数据从Leader上同步过来得了
        props.setProperty("acks","all");
        //重试次数
        props.setProperty("retries","2");
        //每次重试间隔多久
        props.setProperty("retries.backoff.ms","20");
        //buffer缓冲区大小,默认32M,单位是byte
        props.setProperty("buffer.memory","10240000");
        //batch批大小,默认16K
        props.setProperty("batch.size","10240");
        //发送batch批的间隔
        props.setProperty("linger.ms","25");
        //发送的request对象的大小
        props.setProperty("max.request.size","102400");
        //k-v序列化--因为跨网络传输
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        //TODO 2.根据参数创建生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(props);

        //TODO 3.同步发送
        /*for (int i = 10; i < 20; i++){
            ProducerRecord<String, String> record = new ProducerRecord<>("test_topic", "key_" + i, "value_" + i);
            RecordMetadata metadata = producer.send(record).get();
            System.out.println("同步发送后获得分区和offset :"+metadata.partition() + "---" + metadata.offset());
        }*/


        //TODO 4.异步发送
        for (int i = 0; i < 10; i++) {
    
    
            //发送的是一条条的消息/记录
            ProducerRecord<String, String> record = new ProducerRecord<>("test_topic", "key_" + i, "value_" + i);
            //使用生产者发送记录
            //RecordMetadata metadata = producer.send(record).get();//同步
            producer.send(record, new Callback() {
    
    
                //异步返回的结果
                @Override
                public void onCompletion(RecordMetadata metadata, Exception exception) {
    
    
                    //这里就可以获取发送成功之后的信息
                    if (metadata!=null){
    
    
                        System.out.println("异步发送后获得分区和offset :"+metadata.partition() + "---" + metadata.offset());
                    }
                }
            });
        }
        //TODO 5.关闭资源
        producer.close();
    }
}

分区策略

  • 默认的分区策略
  • 注意:
    • 我们的生产者往Kafka发送/推送消息的时候,应该要将消息均匀的发送到Kafka的各个分区中去,为了提供并发读写
    • 那么消息到底被发送到哪一个分区去了呢?由谁决定呢?
    • 有一个Partitioner类型的对象决定
    • 默认有个DefaultPartitioner
    • 连敲2次shift,输入
      在这里插入图片描述
      在这里插入图片描述
  • 默认的分区策略:
    • 1.如果在record记录对象中指定了分区,那么就会使用该分区
    • 2.如果没有指定分区,但存在key,则根据key的 哈希值 % 分区数 得到分区编号;如果key一样都到一个分区.所以如果使用key作为分区依据,那么key应该要不一样
    • 3.如果没有分区或key,则以循环/轮询方式选择分区
//TODO 3.同步发送
for (int i = 10; i < 20; i++){
    
    
    //默认的分区策略:
    //1.如果在record记录对象中指定了分区,那么就会使用该分区
    //ProducerRecord<String, String> record = new ProducerRecord<>("test_topic",1, "key_" + i, "value_" + i);
    //2.如果没有指定分区,但存在key,则根据key的 哈希值 % 分区数 得到分区编号;如果key一样都到一个分区.所以如果使用key作为分区依据,那么key应该要不一样
    //ProducerRecord<String, String> record = new ProducerRecord<>("test_topic", "key_", "value_" + i);
    //3.如果没有分区或key,则以循环/轮询方式选择分区
    ProducerRecord<String, String> record = new ProducerRecord<>("test_topic","value_" + i);
    RecordMetadata metadata = producer.send(record).get();
    System.out.println("同步发送后获得分区编号和offset :"+metadata.partition() + "---" + metadata.offset());
}

自定义分区器

  • 自己实现分区的逻辑 implements Partitioner接口
package cn.hanjiaxiaozhi.producer;

import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;

import java.util.Map;

/**
 * Author hanjiaxiaozhi
 * Date 2020/7/10 16:46
 * Desc 自定义分区器--自己实现分区的方式/方法
 */
public class MyPartitioner implements Partitioner {
    
    

    //分区方法,里面实现我们自己的分区策略即可
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
    
    
        //这里可以根据业务规则,将数据发送到不同的分区
        //这里可以简单模拟下,根据手机号前三位,将数据发送到不同的分区
        //获取手机号前3位
        String k = (String)key;
        String num3 = k.substring(0, 3);
        //获取分区数
        Integer partitionCount = cluster.partitionCountForTopic(topic);
        //根据手机号前3位的hash值%分区数,决定数据到哪个分区
        int partitionNum = Math.abs(num3.hashCode()) % partitionCount;
        System.out.println("获得的分区号是:"+partitionNum);
        return  partitionNum;
        //如果直接返回0,那么全消息进入到0分区
        //return 0;
    }

    //关闭
    @Override
    public void close() {
    
    

    }

    //配置
    @Override
    public void configure(Map<String, ?> configs) {
    
    

    }
}
  • 使用
package cn.hanjiaxiaozhi.producer;

import org.apache.kafka.clients.producer.*;

import java.util.Properties;

/**
 * Author hanjiaxiaozhi
 * Date 2020/7/10 15:50
 * Desc 演示Kafak-JavaAPI实现Kafak生产者-同步和异步发送消息
 */
public class MyKafkaProducer {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //注意TODO原本表示该任务没有完成,后续需要继续编写,这里使用仅仅为了步骤清晰
        //TODO 1.准备连接参数
        Properties props = new Properties();
        //kafka集群地址
        props.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"node01:9092");
        //消息确认机制--表示是否发送成功
        //0:表示只要发出去就认为发送成功,可能会有数据丢失,一般不用,除非对性能要求特别高,且不在乎数据丢失
        //1:表示只要leaader收到就认为发送成功,开发中可以使用,但是如果对于数据安全要求还是较高,该配置不适合
        //-1/all:表示所有的ISR副本(Leader+Follower)都收到才认为发送成功,也就是必须等到Follower把数据从Leader上同步过来得了
        props.setProperty("acks","all");
        //重试次数
        props.setProperty("retries","2");
        //每次重试间隔多久
        props.setProperty("retries.backoff.ms","20");
        //buffer缓冲区大小,默认32M,单位是byte
        props.setProperty("buffer.memory","10240000");
        //batch批大小,默认16K
        props.setProperty("batch.size","10240");
        //发送batch批的间隔
        props.setProperty("linger.ms","25");
        props.setProperty("max.request.size","102400");
        //k-v序列化--因为跨网络传输
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        //指定使用我们自己定义的分区器
        props.put("partitioner.class", "cn.hanjiaxiaozhi.producer.MyPartitioner");

        //TODO 2.根据参数创建生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(props);

        //TODO 3.同步发送---使用同步测试,方便观察,但是开发不用同步
        for (int i = 0; i < 10; i++){
    
    
            ProducerRecord<String, String> record = new ProducerRecord<>("test_topic", "13"+i+"88888888", "value_" + i);
            RecordMetadata metadata = producer.send(record).get();
            System.out.println("同步发送后获得分区编号和offset :"+metadata.partition() + "---" + metadata.offset());
        }


        //TODO 5.关闭资源
        producer.close();
    }
}

消费者

版本说明

消费者从哪个offset偏移量开始消费

在这里插入图片描述

  • kafka-0.10.1.X版本之前: (offest保存在zk中);
    • auto.offset.reset 的值为smallest(最小)和largest(最大)
  • kafka-0.10.1.X版本之后: (offest保存在kafka的名为__consumer_offsets的topic里面);
    auto.offset.reset 的值更改为:earliest,latest,和none
    • 1.earliest :当各分区下有已提交的 Offset 时,从提交的 Offset开始消费;无提交的Offset 时,从头开始消费;
    • 2.latest : 当各分区下有已提交的 Offset 时,从提交的 Offset 开始消费;无提交的 Offset时,消费新产生的该分区下的数据
    • 3.none : Topic 各分区都存在已提交的 Offset 时,从 Offset 后开始消费;只要有一个分区不存在已提交的 Offset,则抛出异常。

如何提交偏移量

  • 自动提交–大多数情况下用自动提交即可
    • enable.auto.commit设置为true ,offset偏移量会被自动提交到默认主题中(__consumer_offsets) ,
    • 注意:
    • 老版本提交到ZK,新版本提交到默认主题,开发中都是使用新版本API
  • 手动提交
    • enable.auto.commit设置为false
    • 然后调用API提交
    • consumer.commitAsync();//异步提交–用这个
    • consumer.commitSync();//同步提交

代码实现-自动提交

package cn.hanjiaxiaozhi.consumer;

import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

import java.util.Arrays;
import java.util.Properties;

/**
 * Author hanjiaxiaozhi
 * Date 2020/7/11 10:17
 * Desc 演示Kafka-JavaAPI-消费者-自动提交偏移量
 */
public class MyKafkaConsumerAutoCommit {
    
    
    public static void main(String[] args) {
    
    
        //TODO 1.准备连接参数
        Properties props = new Properties();
        ///集群地址
        props.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG,"node01:9092");
        //k-v反序列化
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        //是否自动提交偏移量
        props.put("enable.auto.commit","true");//true表示自动提交
        //自动提交的时间间隔
        props.put("auto.commit.interval.ms","1000");
        //指定从哪个位置开始消费
        //1.earliest :当各分区下有已提交的 Offset 时,从提交的 Offset开始消费;无提交的Offset 时,从头开始消费;
        //2.latest : 当各分区下有已提交的 Offset 时,从提交的 Offset 开始消费;无提交的 Offset时,消费新产生的该分区下的数据
        //3.none : Topic 各分区都存在已提交的 Offset 时,从 Offset 后开始消费;只要有一个分区不存在已提交的 Offset,则抛出异常。
        props.put("auto.offset.reset","latest ");
        //设置一个消费者组名称--消费者组是用来管理消费者的,一个组内可以有1~n个消费者
        props.put("group.id","myconsumer");//组名称如果不给的话,会自动生成一个,不方便查看管理

        //TODO 2.根据参数创建消费者
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);

        //TODO 3.订阅主题
        consumer.subscribe(Arrays.asList("test_topic"));

        //TODO 4.开始消费
        //注意:一直消费订阅的主题
        while (true){
    
    
            //从Kafka集群拉取消息
            ConsumerRecords<String, String> records = consumer.poll(100);
            for (ConsumerRecord<String, String> record : records) {
    
    
                System.out.println("分区:"+record.partition()+" 偏移量:"+record.offset()+" key:"+record.key()+" value:"+record.value());
            }
        }
    }
}

代码实现-手动提交

package cn.hanjiaxiaozhi.consumer;

import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Properties;

/**
 * Author hanjiaxiaozhi
 * Date 2020/7/11 10:17
 * Desc 演示Kafka-JavaAPI-消费者-手动提交偏移量
 */
public class MyKafkaConsumerAutoCommitFalse {
    
    
    public static void main(String[] args) {
    
    
        //TODO 1.准备连接参数
        Properties props = new Properties();
        ///集群地址
        props.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG,"node01:9092");
        //k-v反序列化
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        //是否自动提交偏移量
        props.put("enable.auto.commit","false");//false表示不自动提交,那么就是需要手动提交
        //指定从哪个位置开始消费
        //1.earliest :当各分区下有已提交的 Offset 时,从提交的 Offset开始消费;无提交的Offset 时,从头开始消费;
        //2.latest : 当各分区下有已提交的 Offset 时,从提交的 Offset 开始消费;无提交的 Offset时,消费新产生的该分区下的数据
        //3.none : Topic 各分区都存在已提交的 Offset 时,从 Offset 后开始消费;只要有一个分区不存在已提交的 Offset,则抛出异常。
        props.put("auto.offset.reset","latest ");
        //设置一个消费者组名称--消费者组是用来管理消费者的,一个组内可以有1~n个消费者
        props.put("group.id","myconsumer");//组名称如果不给的话,会自动生成一个,不方便查看管理

        //TODO 2.根据参数创建消费者
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);

        //TODO 3.订阅主题
        consumer.subscribe(Arrays.asList("test_topic"));

        //TODO 4.开始消费
        //准备一个集合,存放消费到的数据,集合的size大于一定数量的时候,也就是积攒到一批的时候,才提交偏移量
        ArrayList<ConsumerRecord<String, String>> list = new ArrayList();
        while (true){
    
    
            ConsumerRecords<String, String> records = consumer.poll(100);
            //如果这一次拉取消费有数据,那么消费完应该要手动提交偏移量,如果没有拉取到消息,那么没必要提交
            //如果这一次只消费到了一条数据,也提交吗?有点性能浪费,我们可以积攒到一批再提交
            //当然可以根据实际业务需求来
            //那么我们这里演示有数据,且积攒了一批数据再提交
            for (ConsumerRecord<String, String> record : records) {
    
    
                System.out.println("分区:"+record.partition()+" 偏移量:"+record.offset()+" key:"+record.key()+" value:"+record.value());
                list.add(record);
            }
            //TODO 5.手动提交偏移量
            //经过上面的循环,list中就有了一批数据,如果list.size>5,就提交偏移量
            if (list.size() > 5){
    
    
                System.out.println("list.size>5偏移量已经提交");
                consumer.commitAsync();//异步提交
                //consumer.commitSync();//同步提交
                //提交完这一批,情况list,重新累计
                list.clear();
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_46893497/article/details/114179037