使用JavaApi实现模拟Kafka的消息生产者与发送者

目录

1.  生产者消息发送流程

1.1 发送原理

1.2 生产者重要参数列表 

1.3 异步发送 API

1.3.1 普通异步发送

1.3.2 带回调函数的异步发送

1.4 同步发送 API

2. 消费者消费消息

2.1 Kafka 消费者工作流程 

2.2 消费者重要参数

2.3 消费者 API

2.3.1 独立消费者案例(订阅主题)

2.3.2 独立消费者案例(订阅分区)


Kafka基础介绍:

 

1 Producer 消息生产者,就是向 Kafka broker 发消息的客户端。
2 Consumer 消息消费者,向 Kafka broker 取消息的客户端。
3 Consumer Group CG ): 消费者组,由多个 consumer 组成。 消费者组内每个消
费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费;消费者组之间互不
影响。 所有的消费者都属于某个消费者组,即 消费者组是逻辑上的一个订阅者
4 Broker 一台 Kafka 服务器就是一个 broker 。一个集群由多个 broker 组成。一个
broker 可以容纳多个 topic
5 Topic 可以理解为一个队列, 生产者和消费者面向的都是一个 topic
6 Partition 为了实现扩展性,一个非常大的 topic 可以分布到多个 broker (即服
务器)上, 一个 topic 可以分为多个 partition ,每个 partition 是一个 有序的队列
7 Replica 副本。一个 topic 的每个分区都有若干个副本,一个 Leader 和若干个
Follower
8 Leader 每个分区多个 副本的 ,生产者发送数据的对象,以及消费者消费数
据的对象都是 Leader
9 Follower 每个分区多个 副本中的 ,实时从 Leader 中同步数据,保持和
Leader 数据的同步。 Leader 发生故障时,某个 Follower 会成为新的 Leader

1.  生产者消息发送流程

1.1 发送原理

        在消息发送的过程中,涉及到了 两个线程—— main 线程和 Sender 线程 。在 main 线程
中创建了 一个双端队列 RecordAccumulator main 线程将消息发送给 RecordAccumulator
Sender 线程不断从 RecordAccumulator 中拉取消息发送到 Kafka Broker

1.2 生产者重要参数列表 

参数名称
描述
bootstrap.servers
生产者连接集群所需的 broker 地 址 清 单 。 例 如hadoop102:9092,hadoop103:9092,hadoop104:9092,可以 设置 1 个或者多个,中间用逗号隔开。注意这里并非需要所有的 broker 地址,因为生产者从给定的 broker
里查找到其他 broker 信息。
key.serializer value.serializer
指定发送消息的 key value 的序列化类型。一定要写 全类名。
buffer.memory
RecordAccumulator 缓冲区总大小, 默认 32m。
batch.size
缓冲区一批数据最大值, 默认 16k 。适当增加该值,可 以提高吞吐量,但是如果该值设置太大,会导致数据 传输延迟增加。
linger.ms
如果数据迟迟未达到 batch.size sender 等待 linger.time 之后就会发送数据。单位 ms 默认值是 0ms ,表示没 有延迟。生产环境建议该值大小为 5-100ms 之间。
acks
0 :生产者发送过来的数据,不需要等数据落盘应答。
1 :生产者发送过来的数据, Leader 收到数据后应答。
-1 all ):生产者发送过来的数据, Leader+ isr 队列
里面的所有节点收齐数据后应答。 默认值是 -1 -1 和 all 是等价的。
max.in.flight.requests.per.connection
允许最多没有返回 ack 的次数, 默认为 5 ,开启幂等性 要保证该值是 1-5 的数字。
retries
当消息发送出现错误的时候,系统会重发消息。 retries 表示重试次数。 默认是 int 最大值, 2147483647 如果设置了重试,还想保证消息的有序性,需要设置 MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION=1 否则在重试此失败消息的时候,其他的消息可能发送成功了。
compression.type
生产者发送的所有数据的压缩方式。 默认是 none ,也 就是不压缩。 支持压缩类型: none gzip snappy lz4 zstd  
retry.backoff.ms
两次重试之间的时间间隔,默认是 100ms

1.3 异步发送 API

1.3.1 普通异步发送

(1)需求:创建 Kafka 生产者,采用异步的方式发送到 Kafka Broker
(2)导入依赖
<dependencies>
 <dependency>
 <groupId>org.apache.kafka</groupId>
 <artifactId>kafka-clients</artifactId>
 <version>3.0.0</version>
 </dependency>
</dependencies>
(3)代码编写
package com.zsh.kafkatest.producer;
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;
//普通异步发送
public class CustomProducer {
    public static void main(String[] args) throws
            InterruptedException {
        // 1. 创建 kafka 生产者的配置对象
        Properties properties = new Properties();
        // 2. 给 kafka 配置对象添加配置信息:bootstrap.servers
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
                "192.168.140.65:9092");
        // key,value 序列化(必须):key.serializer,value.serializer
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        // 3. 创建 kafka 生产者对象
        KafkaProducer<String, String> kafkaProducer = new
                KafkaProducer<String, String>(properties);
        // 4. 调用 send 方法,发送消息
        for (int i = 0; i < 5; i++) {
            kafkaProducer.send(new
                    ProducerRecord<>("first","atguigu " + i));
        }
        // 5. 关闭资源
        kafkaProducer.close();
    }
}

1.3.2 带回调函数的异步发送

        回调函数会在 producer 收到 ack 时调用,为异步调用,该方法有两个参数,分别是元
数据信息( RecordMetadata )和异常信息( Exception ),如果 Exception null ,说明消息发
送成功,如果 Exception 不为 null ,说明消息发送失败。
package com.zsh.kafkatest.producer;

import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;
//带回调函数的异步发送,注意:消息发送失败会自动重试,不需要我们在回调函数中手动重试。
public class CustomProducerCallback {
    public static void main(String[] args)  throws
            InterruptedException{
//        test1();
        test2();
//        test3();
    }
    //1、、将数据随机发往 partition 的情况下
    public static void test1()throws
            InterruptedException{
        // 1. 创建 kafka 生产者的配置对象
        Properties properties = new Properties();
        // 2. 给 kafka 配置对象添加配置信息
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
                "192.168.140.65:9092");
        // key,value 序列化(必须):key.serializer,value.serializer
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
                StringSerializer.class.getName());

        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
                StringSerializer.class.getName());
        // 3. 创建 kafka 生产者对象
        KafkaProducer<String, String> kafkaProducer = new
                KafkaProducer<String, String>(properties);
        // 4. 调用 send 方法,发送消息
        for (int i = 0; i < 5; i++) {
            // 添加回调
            kafkaProducer.send(new ProducerRecord<>("first",
                    "zsh " + i), new Callback() {

                // 该方法在 Producer 收到 ack 时调用,为异步调用
                @Override
                public void onCompletion(RecordMetadata metadata,
                                         Exception exception) {
                    if (exception == null) {
                        // 没有异常,输出信息到控制台
                        System.out.println(" 主题: " +
                                metadata.topic() + "->" + "分区:" + metadata.partition());
                    } else {
                        // 出现异常打印
                        exception.printStackTrace();
                    }
                }
            });
            // 延迟一会会看到数据发往不同分区
            Thread.sleep(2);
        }
        // 5. 关闭资源
        kafkaProducer.close();
    }
    //2、将数据发往指定 partition 的情况下,例如,将所有数据发往分区 1 中。
    public static void test2()throws InterruptedException{
    // 1. 创建 kafka 生产者的配置对象
            Properties properties = new Properties();

            // 2. 给 kafka 配置对象添加配置信息

            properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.140.65:9092");
            // key,value 序列化(必须):key.serializer,value.serializer
            properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
                    StringSerializer.class.getName());

            properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
                    StringSerializer.class.getName());
            KafkaProducer<String, String> kafkaProducer = new
                    KafkaProducer<>(properties);
            for (int i = 0; i < 5; i++) {
                // 指定数据发送到 1 号分区,key 为空(IDEA 中 ctrl + p 查看参数)
                kafkaProducer.send(new ProducerRecord<>("first",
                        0,"","FENQU " + i), new Callback() {
                    @Override
                    public void onCompletion(RecordMetadata metadata,
                                             Exception e) {
                        if (e == null){
                            System.out.println(" 主题: " +
                                    metadata.topic() + "->" + "分区:" + metadata.partition()
                            );
                        }else {
                            e.printStackTrace();
                        }
                    }
                });
            }
            kafkaProducer.close();
        }
        //3、没有指明 partition 值但有 key 的情况下,将 key 的 hash 值与 topic 的 partition 数进行取余得到 partition 值
        public static void test3()throws InterruptedException{
            Properties properties = new Properties();

            properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.140.65:9092");
            properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
                    StringSerializer.class.getName());

            properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
                    StringSerializer.class.getName());
            KafkaProducer<String, String> kafkaProducer = new
                    KafkaProducer<>(properties);
            for (int i = 0; i < 5; i++) {
                // 依次指定 key 值为 a,b,f ,数据 key 的 hash 值与 3 个分区求余,分别发往 1、2、0
                kafkaProducer.send(new ProducerRecord<>("first",
                        "a","atguigu " + i), new Callback() {
                    @Override
                    public void onCompletion(RecordMetadata metadata,
                                             Exception e) {
                        if (e == null){
                            System.out.println(" 主题: " +
                                    metadata.topic() + "->" + "分区:" + metadata.partition()
                            );
                        }else {
                            e.printStackTrace();
                        }
                    }
                });
            }
            kafkaProducer.close();
        }
}

1.4 同步发送 API

只需在异步发送的基础上,再调用一下 get() 方法即可。
package com.zsh.kafkatest.producer;

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.common.serialization.StringSerializer;

import java.util.Properties;
import java.util.concurrent.ExecutionException;
//同步发送 API,只需在异步发送的基础上,再调用一下 get()方法即可。
public class CustomProducerSync {
    public static void main(String[] args) throws
            InterruptedException, ExecutionException {
        // 1. 创建 kafka 生产者的配置对象
        Properties properties = new Properties();
        // 2. 给 kafka 配置对象添加配置信息

        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.140.65:9092");
        // key,value 序列化(必须):key.serializer,value.serializer
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
                StringSerializer.class.getName());

        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
                StringSerializer.class.getName());
        // 3. 创建 kafka 生产者对象
        KafkaProducer<String, String> kafkaProducer = new
                KafkaProducer<String, String>(properties);
        // 4. 调用 send 方法,发送消息
        for (int i = 0; i < 10; i++) {
            // 异步发送 默认
            kafkaProducer.send(new ProducerRecord<>("first","kafka" + i));
            // 同步发送
            kafkaProducer.send(new
                    ProducerRecord<>("first","myd" + i)).get();
        }
        // 5. 关闭资源
        kafkaProducer.close();
    }
}

2. 消费者消费消息

2.1 Kafka 消费者工作流程 

2.2 消费者重要参数

参数名称
描述
bootstrap.servers
Kafka 集群建立初始连接用到的 host/port 列表。
key.deserializer
value.deserializer
指定接收消息的 key value 的反序列化类型。一定要写全 类名。
group.id
标记消费者所属的消费者组。
enable.auto.commit
默认值为 true ,消费者会自动周期性地向服务器提交偏移 量。
auto.commit.interval.ms
如果设置了 enable.auto.commit 的值为 true , 则该值定义了 消费者偏移量向 Kafka 提交的频率, 默认 5s
auto.offset.reset
Kafka 中没有初始偏移量或当前偏移量在服务器中不存在 (如,数据被删除了),该如何处理? earliest :自动重置偏 移量到最早的偏移量。 latest :默认,自动重置偏移量为最 新的偏移量。 none :如果消费组原来的( previous )偏移量 不存在,则向消费者抛异常。 anything :向消费者抛异常。
offsets.topic.num.partitions
__consumer_offsets 的分区数, 默认是 50 个分区。
heartbeat.interval.ms
Kafka 消费者和 coordinator 之间的心跳时间, 默认 3s 该条目的值必须小于 session.timeout.ms ,也不应该高于 session.timeout.ms 的 1/3
session.timeout.ms
Kafka 消费者和 coordinator 之间连接超时时间, 默认 45s 。 超过该值,该消费者被移除,消费者组执行再平衡。
max.poll.interval.ms
消费者处理消息的最大时长, 默认是 5 分钟 。超过该值,该消费者被移除,消费者组执行再平衡。
fetch.min.bytes
默认 1 个字节。消费者 获取服务器端一批消息最小的字节数。
fetch.max.wait.ms
默认 500ms 。如果没有从服务器端获取到一批数据的最小字节数 。该时间到,仍然会返回数据。
fetch.max.bytes
默认 Default: 52428800 50 m )。消费者 获取服务器端一批 消息最大的字节数。如果服务器端一批次的数据大于该值 (50m )仍然可以拉取回来这批数据,因此,这不是一个绝 对最大值。一批次的大小受 message.max.bytes broker config) or max.message.bytes topic config )影响。
max.poll.records
一次 poll 拉取数据返回消息的最大条数, 默认是 500 条。

2.3 消费者 API

2.3.1 独立消费者案例(订阅主题)

(1)需求: 创建一个独立消费者,消费 first 主题中数据。
注意: 在消费者 API 代码中必须配置消费者组 id 。命令行启动消费者不填写消费者组
id 会被自动填写随机的消费者组 id

 (2)代码

package com.zsh.kafkatest.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 org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
//创建一个独立消费者,消费 first 主题中数据。
public class CustomConsumer {
    public static void main(String[] args) {
        // 1.创建消费者的配置对象
        Properties properties = new Properties();
        // 2.给消费者配置对象添加参数
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.140.65:9092");
        // 配置序列化 必须
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
                StringDeserializer.class.getName());

        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
                StringDeserializer.class.getName());
        // 配置消费者组(组名任意起名) 必须
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test");
        // 创建消费者对象
        KafkaConsumer<String, String> kafkaConsumer = new
                KafkaConsumer<String, String>(properties);
        // 注册要消费的主题(可以消费多个主题)
        ArrayList<String> topics = new ArrayList<>();
        topics.add("first");
        kafkaConsumer.subscribe(topics);
        // 拉取数据打印
        while (true) {
            // 设置 1s 中消费一批数据
            ConsumerRecords<String, String> consumerRecords =
                    kafkaConsumer.poll(Duration.ofSeconds(30));
            // 打印消费到的数据
            for (ConsumerRecord<String, String> consumerRecord :
                    consumerRecords) {
                System.out.println(consumerRecord);
            }
        }
    }
}

2.3.2 独立消费者案例(订阅分区)

(1)需求:创建一个独立消费者,消费 first 主题 0 号分区的数据。

 (2)代码编写

package com.zsh.kafkatest.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 org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
//创建一个独立消费者,消费 first 主题 0 号分区的数据。
public class CustomConsumerPartition {
    public static void main(String[] args) {
        Properties properties = new Properties();

        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.140.65:9092");
        // 配置序列化 必须
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
                StringDeserializer.class.getName());

        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
                StringDeserializer.class.getName());
        // 配置消费者组(必须),名字可以任意起
        properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
        KafkaConsumer<String, String> kafkaConsumer = new
                KafkaConsumer<>(properties);
        // 消费某个主题的某个分区数据
        ArrayList<TopicPartition> topicPartitions = new
                ArrayList<>();
        topicPartitions.add(new TopicPartition("first", 0));
        kafkaConsumer.assign(topicPartitions);
        while (true){
            ConsumerRecords<String, String> consumerRecords =
                    kafkaConsumer.poll(Duration.ofSeconds(1));
            for (ConsumerRecord<String, String> consumerRecord :
                    consumerRecords) {
                System.out.println(consumerRecord);
            }
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值