Kafka 02-JavaApi的使用

281 阅读4分钟

这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

一、Java Api使用

1.1 maven依赖

<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
    <version>2.7.0</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.30</version>
</dependency>

1.2 producer类

分为同步确认、异步确认两种;

public static void main(String[] args) throws ExecutionException, InterruptedException {

    Map<String, Object> configs = new HashMap<>();
    // 指定初始连接用到的broker地址
    configs.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.11.200:9092");
    // 指定key的序列化类
    configs.put("key.serializer", IntegerSerializer.class);
    // 指定value的序列化类
    configs.put("value.serializer", StringSerializer.class);
//        configs.put("acks", "all");
//        configs.put("reties", "3");

    KafkaProducer<Integer, String> producer = new KafkaProducer<Integer, String>(configs);

    // 用于设置用户自定义的消息头字段
    List<Header> headers = new ArrayList<>();
    headers.add(new RecordHeader("biz.name", "producer.demo".getBytes()));

    for (int i = 0; i < 100; i++) {

        ProducerRecord<Integer, String> record = new ProducerRecord<Integer, String>(
                "topic_a",
                0,
                i,
                "hello kafka " + i,
                headers
        );

        // 消息的同步确认
        final Future<RecordMetadata> future = producer.send(record);
        final RecordMetadata metadata = future.get();
        System.out.println("消息的主题:" + metadata.topic());
        System.out.println("消息的分区号:" + metadata.partition());
        System.out.println("消息的偏移量:" + metadata.offset());

        // 消息的异步确认
        /*producer.send(record, new Callback() {
            @Override
            public void onCompletion(RecordMetadata metadata, Exception exception) {
                if (exception == null) {
                    System.out.println("消息的主题:" + metadata.topic());
                    System.out.println("消息的分区号:" + metadata.partition());
                    System.out.println("消息的偏移量:" + metadata.offset());
                } else {
                    System.out.println("异常消息:" + exception.getMessage());
                }
            }
        });*/
    }

    // 关闭生产者
    producer.close();
}

1.3 consumer类

public static void main(String[] args) {

    Map<String, Object> configs = new HashMap<>();
    configs.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.11.200:9092");
    // 使用常量代替手写的字符串,配置key的反序列化器
    configs.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, IntegerDeserializer.class);
    // 配置value的反序列化器
    configs.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
    // 配置消费组ID
    configs.put(ConsumerConfig.GROUP_ID_CONFIG, "consumer_demo");
    // 如果找不到当前消费者的有效偏移量,则自动重置到最开始
    // latest表示直接重置到消息偏移量的最后一个
    configs.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");

    KafkaConsumer<Integer, String> consumer = new KafkaConsumer<Integer, String>(configs);

    // 先订阅,再消费
    consumer.subscribe(Arrays.asList("topic_a"));

//        while (true) {
//            final ConsumerRecords<Integer, String> consumerRecords = consumer.poll(3_000);
//        }
    // 如果主题中没有可以消费的消息,则该方法可以放到while循环中,每过3秒重新拉取一次
    // 如果还没有拉取到,过3秒再次拉取,防止while循环太密集的poll调用。

    // 批量从主题的分区拉取消息
    final ConsumerRecords<Integer, String> consumerRecords = consumer.poll(3_000);

    // 遍历本次从主题的分区拉取的批量消息
    consumerRecords.forEach(new Consumer<ConsumerRecord<Integer, String>>() {
        @Override
        public void accept(ConsumerRecord<Integer, String> record) {
            System.out.println(record.topic() + "\t"
                    + record.partition() + "\t"
                    + record.offset() + "\t"
                    + record.key() + "\t"
                    + record.value());
        }
    });

    consumer.close();

}

二、整合SpringBoot

⚠️直接在项目中使用不存在的topic,会不会报错?

--> 项目在启动时,会初始化一个KafkaAmdin对象,如果用到的topic不存在,则会自动创建

2.1 配置与pom

spring.application.name=springboot-kafka
server.port=8080

# kafka的配置
spring.kafka.bootstrap-servers=192.168.11.200:9092

#producer配置
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.IntegerSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
# 生产者每个批次最多放多少条记录
spring.kafka.producer.batch-size=16384
# 生产者一端总的可用发送缓冲区大小,此处设置为32MB
spring.kafka.producer.buffer-memory=33554432

#consumer配置
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.IntegerDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.group-id=springboot-consumer02
# 如果在kafka中找不到当前消费者的偏移量,则直接将偏移量重置为最早的
spring.kafka.consumer.auto-offset-reset=earliest
# 消费者的偏移量是自动提交还是手动提交,此处自动提交偏移量
spring.kafka.consumer.enable-auto-commit=true
# 消费者偏移量自动提交的时间间隔
spring.kafka.consumer.auto-commit-interval=1000
 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

2.2 producer类

有两个接口方法,分别对应同步确认、异步确认

@RestController
public class MyProducer {

    @Autowired
    private KafkaTemplate<Integer, String> kafkaTemplate;

    @GetMapping("sync/send/{msg}")
    public String syncSend(@PathVariable String msg) {
        ListenableFuture<SendResult<Integer, String>> future = kafkaTemplate.send("topic-spring", 0, 1, msg);
        // 同步发送消息
        try {
            final SendResult<Integer, String> sendResult = future.get();
            final RecordMetadata metadata = sendResult.getRecordMetadata();

            System.out.println(metadata.topic() + "\t" + metadata.partition() + "\t" + metadata.offset());

        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        return "success";
    }


    @GetMapping("async/send/{msg}")
    public String asyncSend(@PathVariable String msg) {
        final ListenableFuture<SendResult<Integer, String>> future = kafkaTemplate.send("topic-spring", 0, 1, msg);

        // 设置回调函数,异步等待broker端的返回结果
        future.addCallback(new ListenableFutureCallback<SendResult<Integer, String>>() {
            @Override
            public void onFailure(Throwable throwable) {
                System.out.println("发送消息失败:" + throwable.getMessage());
            }

            @Override
            public void onSuccess(SendResult<Integer, String> result) {
                final RecordMetadata metadata = result.getRecordMetadata();

                System.out.println("发送消息成功:" + metadata.topic() + "\t" + metadata.partition() + "\t" + metadata.offset());
            }
        });

        return "success";
    }

}

2.3 consumer监听器类:

@Component
public class ConsumerListener {
    @KafkaListener(topics = "topic-spring")
    public void onMessage(ConsumerRecord<Integer, String> record) {
        System.out.println("消费者收到的消息:"
                + record.topic() + "\t"
                + record.partition() + "\t"
                + record.offset() + "\t"
                + record.key() + "\t"
                + record.value());
    }
}

启动后,分别调用http://localhost:8080/sync/send/hello-synchttp://localhost:8080/async/send/hello-async发送消息,可以看到控制台打印类似如下信息:

发送消息成功:topic-spring	0	2
消费者收到的消息:topic-spring	0	2	1	hello-sync
发送消息成功:topic-spring	0	3
消费者收到的消息:topic-spring	0	3	1	hello-async

在项目中,可以自定义KafkaAdmin、KafkaTemplate、

三、整合SpringCloudStream(略)

四、服务端参数配置

4.1 listeners配置

PLAINTEXT在默认情况下,既用作监听器的名称,又用做通信协议名称

  • listeners:是kafka真正bind的地址;
  • advertised.listeners:是暴露给外部的listeners,如果没有设置,会用listeners;
  • listeners
    • 用于配置broker监听的URI以及监听器名称列表,使用逗号隔开多个URI及监听器名称。
    • 如果监听器名称代表的不是安全协议,必须配置listener.security.protocol.map,每个监听器必须使用不同的网络端口(其他listener配置都是这里配置的listeners的子集)。
  • listener.security.protocol.map:配置监听者的安全协议的,格式是:监听器名称:协议名称,比如listener.security.protocol.map = INTERNAL:SSL,EXTERNAL:SSL,协议名称有如下可选项:PLAINTEXTSSLSASL_PLAINTEXTSASL_SSL
  • advertised.listeners:发布到Zookeeper中的broker对外连接地址;
  • inter.broker.listener.name:专门用于Kafka集群中Broker之间的通信(从advertised.listeners中选)

如果一台服务器拥有2张网卡,则一个典型的配置如下:

listener.security.protocol.map=INTERNAL:PLANTEXT,EXTERNAL:PLANTEST
listeners=INTERNAL://192.168.22.200:9092,EXTERNAL://192.168.22.201:9093
# 暴露给producer、consumer的链接
advertised.listener=EXTERNAL://192.168.22.200:9092
# broker直接通信的链接
inter.broker.listener.name=EXTERNAL

4.2 zookeeper.connect

该参数用于配置Kafka要连接的Zookeeper/集群的地址。它的值是一个字符串,使用逗号分隔Zookeeper的多个地址。Zookeeper的单个地址是 host:port 形式的,可以在最后添加Kafka在Zookeeper中的根节点路径。

4.3 broker.id

该属性用于唯一标记一个Kafka的Broker,它的值是一个任意integer值。当Kafka以分布式集群运行的时候,尤为重要。

最好该值跟该Broker所在的物理主机有关的,如主机名为 host1.lagou.com ,则 broker.id=1 ,如果主机名为 192.168.100.101 ,则 broker.id=101 等等。

4.4 log.dir

通过该属性的值,指定Kafka在磁盘上保存消息的日志片段的目录。

它是一组用逗号分隔的本地文件系统路径。如果指定了多个路径,那么broker 会根据“最少使用”原则,把同一个分区的日志片段保存到同一个路径下。

broker 会往拥有最少数目分区的路径新增分区,而不是往拥有最小磁盘空间的路径新增分区。