kafak基础API实现

1. producer

流程图
在这里插入图片描述
1. 异步:在没有收到ack报文就继续发送下一条信息了
2. 同步:在发送完消息后,线程会进入阻塞状态,直到producer收到ack报文为止
3. 回调函数: 就是在producer收到ack报文后,执行的函数

1.异步不带回调

package review.producer;

import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

/**
 * @ClassName: Async
 * @Description: 异步不带回调生产信息
 *                要用到的类
 *                KafkaProducer:需要创建一个生产者对象,用来发送数据
 *                ProducerConfig:获取所需的一系列配置参数
 *                ProducerRecord:每条数据都要封装成一个ProducerRecord对象
 *
 *                1. 配置的key值 可以使用 ProducerConfig 类的常量
 *                2. 配置的value值  可以点进ProducerConfig 类 每一个key都会有说明  该填的类型及可选项
 * @Author: jjj
 * @Date: 2021/12/25 19:00
 **/
public class Async {
    // 1.生产配置对象




    public static void main(String[] args) {

        // 1.写配置信息 properties

        Properties properties = getProperties();

        // 2.创建 kafkaProducer对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);

        // 3.发送信息 注 : 信息需要用ProduceRecord封装
        for (int i = 0; i < 100; i++) {
            /**
             * 这里有6种重载方法
             * 1. topic,value 必填
             * 2. partition(分区号)
             * 3. key 值
             * 4. timestamp 时间戳
             * 5. iterable  一个header的迭代对象???不了解
             * 这里的泛型时 key,value 的数据类型
             *
             */
            ProducerRecord<String, String> record = new ProducerRecord<>("first", i + "#");
            producer.send(record);
        }
        // 关闭连接
        producer.close();
    }

    public static Properties getProperties(){
        Properties properties = new Properties();
        // 1.引导服务
        properties.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092");

        // 2.key的序列化类型
        // org.apache.kafka.common.serialization.StringSerializer -> 功能是将 string型的值转换为字节数组
        // 还有 。。。IntegerSerializer 等一系列序列化类型
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");

        // 2.value的序列化类型
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        /**
         * 以上是必填配置信息
         * 接下来是可视情况而定
         */
        // 3.ack 级别  -> 确保数据的可靠性程度  [0,1,all] 三种选择
        properties.put(ProducerConfig.ACKS_CONFIG, "0");

        // 4.retries 重复次数
        properties.put(ProducerConfig.RETRIES_CONFIG, "1");

        // 5.batchSize 批次大小  ->  当达到batchSize时,就会把数据放到recorderAccumulate
        properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);

        // 6.linger.ms 等待时间
        properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);

        return properties;
    }
}

2.异步带回调

package review.producer;

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

import java.util.Properties;

/**
 * @ClassName: AsyncAndCallBack
 * @Description: 异步带回调
 * @Author: jjj
 * @Date: 2021/12/25 19:00
 **/
public class AsyncAndCallBack {
    public static void main(String[] args) {
        // 1.写配置信息
        Properties properties = Async.getProperties();
        // 2.创建 kafkaProducer对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);

        for (int i = 0; i < 100; i++) {
            ProducerRecord<String, String> record = new ProducerRecord<>("first", i + "%");
            producer.send(record, new Callback() {
                /**
                 *
                 * @param metadata  消息的元数据
                 * @param exception 异常
                 *                  如果异常为空 说明发送成功
                 *                  否则发送失败
                 */
                @Override
                public void onCompletion(RecordMetadata metadata, Exception exception) {
                    if (exception == null)
                        System.out.println("success" + record.value());
                    else
                        exception.printStackTrace();
                }
            });
        }
        // 关闭资源
        producer.close();
    }
}

3.同步不带回调

package review.producer;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * @ClassName: Sync
 * @Description: 同步发送信息  -> producer在收到ack报文之前会阻塞进程
 * 由于send方法返回的是一个Future对象,根据Futrue对象的特点,我们也可以实现同步发送的效果,
 * 只需在调用Future对象的get方发即可。
 * @Author: jjj
 * @Date: 2021/12/25 20:28
 **/
public class Sync {
    public static void main(String[] args) {
        // 1.配置信息
        Properties properties = Async.getProperties();
        // 2.生产对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
        // 3.发送信息
        for (int i = 0; i < 100; i++) {
            ProducerRecord<String, String> record = new ProducerRecord<>("hello", i + "^^");

            Future<RecordMetadata> future = producer.send(record);

            try {
                future.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        // 4.关闭资源
        producer.close();
    }
}

4.同步带回调

package review.producer;

import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * @ClassName: Sync
 * @Description: 同步发送信息  -> producer在收到ack报文之前会阻塞进程
 * 由于send方法返回的是一个Future对象,根据Futrue对象的特点,我们也可以实现同步发送的效果,
 * 只需在调用Future对象的get方发即可。
 * @Author: jjj
 * @Date: 2021/12/25 20:28
 **/
public class SyncAndCallBack {
    public static void main(String[] args) {
        // 1.配置信息
        Properties properties = Async.getProperties();
        // 2.生产对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
        // 3.发送信息
        for (int i = 0; i < 100; i++) {
            ProducerRecord<String, String> record = new ProducerRecord<>("first", i + "^^");

            Future<RecordMetadata> future = producer.send(record, new Callback() {
                @Override
                public void onCompletion(RecordMetadata metadata, Exception exception) {
                    if (exception == null){
                        System.out.println("success"+ record.value());
                    }else
                        exception.printStackTrace();
                }
            });

            try {
                future.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        // 4.关闭资源
        producer.close();
    }
}

2.consumer

1. 自动提交 offset

1.自动提交 offset: 系统默认 没啥好说
2.手动提交 offset

无论异步同步提交都有可能尝产生数据重新消费或漏消费的情况

    1. 异步提交:消费信息 与 提交 offset 这两个动作互不干涉
    2. 同步提交:消费一条信息就要把该信息的offset提交上去

同步提交在提交失败的时候,会不断重新尝试提交,知道提交成功,但异步提交自会提交一次

package review.consumer;

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

import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;

/**
 * @ClassName: Auto
 * @Description:    消费者组 自动提交 offset
 *               用到的类:
 *                  KafkaConsumer:需要创建一个消费者对象,用来消费数据
 *                  ConsumerConfig:获取所需的一系列配置参数
 *                  ConsumerRecord:每条数据都要封装成一个ConsumerRecord对象
 * @Author: jjj
 * @Date: 2021/12/25 20:45
 **/
public class AutoSubmit {
    public static void main(String[] args) {
        // 1.获取配置
        Properties properties = getProperties();

        // 2.创建消费对象 与建立连接 泛型 key value 的数据类型
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);

        // 3.订阅主题  -> 可以订阅多个topic
        ArrayList<String> list = new ArrayList<>();
        list.add("first");
        list.add("hello");
        consumer.subscribe(list);

        // 4.消费主题
        while (true){
            // 要用这个方法  注释说,在没有订阅任何主题或者分区就获取数据是错误的
//            ConsumerRecords<String, String> poll = consumer.poll(100);
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(2));

            for (ConsumerRecord<String, String> record : records) {
                System.out.println("消费到:" +record.topic()
                        +":"+record.partition()
                        +":"+record.offset()
                        +":"+record.key()
                        +":"+record.value()
                );
            }
        }
        // 关闭资源 但前面时死循环所一用不到咯
    }

    public static Properties getProperties(){
        Properties properties = new Properties();
        // 1.bootstrap-server
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");
        // 2.序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        // 3.消费者所属组 没有会自己创建
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "1002");
        // 4.自动提交是否关闭 默认开启  重点
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
        // 5.自动提交间隔    重点
        properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");

        // 6.重置offset  [earliest,latest,none]
        /**
         * earliest : 将offset移动到开头
         * latest : 将offset 移动到最后
         * none : 当 此消费者组找不到offset时抛出异常
         */
        properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");

        return properties;
    }
}

2. 异步提交offset

package review.consumer;

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

import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;

/**
 * @ClassName: AsyncSubmit
 * @Description:
 * @Author: jjj
 * @Date: 2021/12/25 21:11
 **/
public class AsyncSubmit {
    public static void main(String[] args) {
        // 1.获取配置
        Properties properties = SyncSubmit.getProperties();
        // 2.生成消费者对象
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
        // 3.订阅主题
        ArrayList<String> topics = new ArrayList<>();
        topics.add("hello");
        topics.add("first");
        consumer.subscribe(topics);
        // 4.消费
        while (true){
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));

            for (ConsumerRecord<String, String> record : records) {
                System.out.println("消费到:" +record.topic()
                        +":"+record.partition()
                        +":"+record.offset()
                        +":"+record.key()
                        +":"+record.value()
                );
            }
            // 异步提交
            consumer.commitAsync();
        }
    }
}

3.同步提交offset

package review.consumer;

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

import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;

/**
 * @ClassName: SyncSubmit 相较于异步提交 会可靠一点,但这意味这会阻塞进程对吞吐量造成影响
 *              无论是同步提交还是异步提交offset,都有可能会造成数据的漏消费或者重复消费。
 *              先提交offset后消费,有可能造成数据的漏消费;
 *              而先消费后提交offset,有可能会造成数据的重复消费。
 *
 *              所以一般 选择  异步提交
 * @Description:
 * @Author: jjj
 * @Date: 2021/12/25 21:10
 **/
public class SyncSubmit {
    public static void main(String[] args) {
        // 1.获取配置
        Properties properties = getProperties();
        // 2.生成消费者对象
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
        // 3.订阅主题
        ArrayList<String> topics = new ArrayList<>();
        topics.add("hello");
        topics.add("first");

        consumer.subscribe(topics);
        // 4.消费
        while (true){
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));

            for (ConsumerRecord<String, String> record : records) {
                System.out.println("消费到:" +record.topic()
                        +":"+record.partition()
                        +":"+record.offset()
                        +":"+record.key()
                        +":"+record.value()
                );
            }
            // 同步提交
            consumer.commitSync();
        }
    }

    public static Properties getProperties(){
        Properties properties = new Properties();
        // 1.bootstrap-server
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");
        // 2.序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        // 3.消费者所属组 没有会自己创建
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "1002");
        // 4.自动提交是否关闭 默认开启  重点
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
        // 5.自动提交间隔    重点
//        properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");

        return properties;
    }
}

3.自定义功能

1.自定义分区规则

MyPartition

package review.self_defiend;

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

import java.util.Map;

/**
 * @ClassName: MyPartitioner
 * @Description:  自定义分区策略   默认在没有指定key,partition的情况下,默认使用 黏性分区规则
 *
 * @Author: jjj
 * @Date: 2021/12/25 21:43
 **/
public class MyPartitioner implements Partitioner {
    // 核心业务逻辑

    /**
     *
     * @param topic 消息分区
     * @param key   消息 的key
     * @param keyBytes key 的字节数组
     * @param value  消息的 value
     * @param valueBytes value 的字节数组
     * @param cluster
     * 需求 : 将value以 jsh 开头的给 分区0
     *         其他   开头的给 分区1
     *
     *  注: 一般 都是对value操作的
     * @return
     */
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {

        if (value.toString().startsWith("jsh"))
            return 0; // 分区编号
        else
            return 1;

    }

    // 结尾
    @Override
    public void close() {

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

    }
}

MypartitionProducer

package review.self_defiend;

import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import review.producer.Async;

import java.util.Properties;

/**
 * @ClassName: Async
 * @Description: 异步不带回调生产信息
 *                要用到的类
 *                KafkaProducer:需要创建一个生产者对象,用来发送数据
 *                ProducerConfig:获取所需的一系列配置参数
 *                ProducerRecord:每条数据都要封装成一个ProducerRecord对象
 *
 *                1. 配置的key值 可以使用 ProducerConfig 类的常量
 *                2. 配置的value值  可以点进ProducerConfig 类 每一个key都会有说明  该填的类型及可选项
 * @Author: jjj
 * @Date: 2021/12/25 19:00
 **/
public class MyPartitionProducer {
    // 1.生产配置对象




    public static void main(String[] args) {

        // 1.写配置信息 properties

        Properties properties = Async.getProperties();

        // 指定分区类
        properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, "review.self_defiend.MyPartitioner");
        // 2.创建 kafkaProducer对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);

        // 3.发送信息 注 : 信息需要用ProduceRecord封装
        for (int i = 0; i < 100; i++) {
           String value;
           if (i % 2 == 0)
               value = "jsh"+i;
           else
               value = "lw" + i;
            ProducerRecord<String, String> record = new ProducerRecord<>("first", value);
            producer.send(record);
        }
        // 关闭连接
        producer.close();
    }


}

2.自定义拦截器链

one

package review.self_defiend;

import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.Map;
import java.util.Timer;

/**
 * @ClassName: MyInterceptorOne
 * @Description:
 * @Author: jjj
 * @Date: 2021/12/25 22:02
 **/
public class MyInterceptorOne implements ProducerInterceptor {
    /**
     * 可以对消息修改
     * 这个调用时间 在最开始  比序列化 分区还要早
     * @param record 消息对象
     * @return
     * 需求 : 在消息前面 添加时间戳
     */
    @Override
    public ProducerRecord onSend(ProducerRecord record) {
        // 1. 取出value对器做出修改
        String value = record.value().toString();
        value += System.currentTimeMillis();
        // 2. 因为没有set方法  所以要重新创建一个ProducerRecord对象
        return new ProducerRecord(record.topic(), record.partition(), record.timestamp(),  record.key(), value);
    }

    /**
     * 看参数就可以知道跟回调函数很像吧
     * 该方法会在消息从RecordAccumulator成功发送到Kafka Broker之后,或者在发送过程中失败时调用。
     * 并且通常都是在producer回调逻辑触发之前。onAcknowledgement运行在producer的IO线程中,
     * 因此不要在该方法中放入很重的逻辑,否则会拖慢producer的消息发送效率。
     * @param metadata 消息对象的元数据
     * @param exception 异常对象
     *                  这是事后处理
     */
    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {

    }

    @Override
    public void close() {

    }
    // 获取配置信息和初始化数据时调用
    @Override
    public void configure(Map<String, ?> configs) {

    }
}

two

package review.self_defiend;

import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.Map;

/**
 * @ClassName: MyInterceptorTwo
 * @Description:
 * @Author: jjj
 * @Date: 2021/12/25 22:03
 **/
public class MyInterceptorTwo implements ProducerInterceptor {
    private int errorCounter = 0;
    private int successCounter = 0;
    @Override
    public ProducerRecord onSend(ProducerRecord record) {
        return record;
    }

    // 对成功数做统计
    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
        // 统计成功和失败的次数
        if (exception == null) {
            successCounter++;
        } else {
            errorCounter++;
        }
    }

    @Override
    public void close() {
        // 保存结果
        System.out.println("Successful sent: " + successCounter);
        System.out.println("Failed sent: " + errorCounter);
    }

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

    }
}

MyInterceptorProducer

package review.self_defiend;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import review.producer.Async;

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

/**
 * @ClassName: MyInterceptorProducer
 * @Description:
 * @Author: jjj
 * @Date: 2021/12/25 22:20
 **/
public class MyInterceptorProducer {

    public static void main(String[] args) {

        Properties properties = Async.getProperties();

        // 构建拦截链 每个拦截类的引用
        ArrayList<String> interceptors = new ArrayList<>();
        interceptors.add("review.self_defiend.MyInterceptorOne");
        interceptors.add("review.self_defiend.MyInterceptorTwo");

        properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, interceptors);

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

        for (int i = 0; i < 100; i++) {
            ProducerRecord<String, String> record = new ProducerRecord<>("first", i + "***");
            producer.send(record);
        }
        // 关闭资源
        producer.close();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值