Kafka简单实现(SpringBoot集成阿里云Kafka)

天行健,君子以自强不息;地势坤,君子以厚德载物。


每个人都有惰性,不断学习是好好生活的根本,共勉!


文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。


一、Kafka简介

Kafka是一个分布式的基于zookeeper的消息队列中间件。
多用于:
用户活动跟踪(记录用户的各种行为信息如浏览、搜索的网页等然后进行数据分析挖掘)、
日志收集(各种服务的日志收集到kafka进行数据或问题分析)、
消息系统(生产、消费、缓存的解耦)、
运营数据监控等,
具有高吞吐量、高并发、低延迟、可扩展、持久可靠及容错等特性
详细介绍请参考阿里云kafka介绍


二、实现步骤

点我查看阿里云官网kafka-java-sdk
开发环境:

JDK版本:1.8
maven版本:3.9.0
开发工具:IDEA社区版ideaIC-2018.3
项目框架:spring boot 版本为 2.6.3 springboot搭建
kafka实例:阿里云kafka实例

1.引入依赖

Kafka所需依赖

        <!--kafka-->
        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>2.4.0</version>
        </dependency>

2.完整依赖

完整pom.xml

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.kafka</groupId>
    <artifactId>kafka_demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!--SpringBoot启动依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.6.3</version>
            <scope>test</scope>
        </dependency>

        <!--kafka-->
        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>2.4.0</version>
        </dependency>

    </dependencies>



</project>

3.配置文件

以下配置文件放在resources包下
启动配置文件application.yml

server:
  port: 8088

SSL根证书kafka.client.truststore.jks
请在官网下载此文件,传送门:kafka.client.truststore.jks下载
JAAS配置文件kafka_client_jaas.conf

KafkaClient {
  org.apache.kafka.common.security.plain.PlainLoginModule required
  username="xxxx"
  password="xxxx";
}; 

注:
最后两行后面的分号必须带上,这是规定格式,不带分号会报错
username和password在kafka的实例->实例详情->配置信息中查看用户名和密码进行复制替换

配置kafka参数文件kafka.properties

##==============================通用配置参数==============================
bootstrap.servers=xxxxxxxxxxxxxxxxxxxxx
#topic=xxx
#group.id=xxx
##=======================以下内容请根据实际情况配置========================
##SSL接入点配置
ssl.truststore.location=D:/xxxx/kafka.client.truststore.jks
java.security.auth.login.config=D:/xxxx/kafka_client_jaas.conf
##SASL接入点PLAIN机制配置
#java.security.auth.login.config.plain=D:/xxxx/kafka_client_jaas_plain.conf
##SASL接入点SCRAM机制配置
#java.security.auth.login.config.scram=D:/xxxx/kafka_client_jaas_scram.conf

注:
bootstrap.servers是接入点地址,请在kafka实例->实例详情->接入点信息->SSL接入点对应的接入点复制过来
ssl.truststore.location是kafka.client.truststore.jks文件存放位置文件全路径
java.security.auth.login.config是kafka_client_jaas.conf文件存放位置全路径
kafka.properties文件在后面项目启动后使用之后中文会变乱码(问号),但不影响正常使用

4.Kafka配置类代码

KafkaConfig.java

package com.kafka.config;

import com.kafka.KafkaApplication;
import java.util.Properties;

/**
 * @ClassDescription: KAFKA配置类
 * @Author:李白
 * @Date:2023/3/8 13:00
 */
public class KafkaConfig {
    private static Properties properties;

    /**
     * 接入点参数配置
     */
    public static void configureSasl() {
        //系统读取参数如果为空则设置为自己配置的参数
        if (null == System.getProperty("java.security.auth.login.config")) {
            //请注意将配置文件kafka.properties中的XXX修改为自己的文件路径参数
            //这个路径必须是以参数形式通过系统读取的,不能被打包到jar中,所以这里不是一个真实路径,而是一个读取路径参数对应的key
            System.setProperty("java.security.auth.login.config", getKafkaProperties().getProperty("java.security.auth.login.config"));
        }
    }

    /**
     * 获取properties文件中的参数
     * @return
     */
    public synchronized static Properties getKafkaProperties() {
        if (null != properties) {
            return properties;
        }
        //获取配置文件kafka.properties的内容
        Properties kafkaProperties = new Properties();
        try {//KafkaApplication为项目启动类
            kafkaProperties.load(KafkaApplication.class.getClassLoader().getResourceAsStream("kafka.properties"));
        } catch (Exception e) {
            //没加载到文件,程序要考虑退出
            e.printStackTrace();
        }
        properties = kafkaProperties;
        return kafkaProperties;
    }
}

5.Kafka发布、消费消息工具类代码

KafkaEasyUtil.java

package com.kafka.utils;

import com.kafka.config.KafkaConfig;
import org.apache.kafka.clients.CommonClientConfigs;
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.config.SaslConfigs;
import org.apache.kafka.common.config.SslConfigs;
import java.time.Duration;
import java.util.Arrays;
import java.util.Properties;
import java.util.concurrent.ExecutionException;

/**
 * @ClassDescription: 简单实现kafka发布和消费消息的功能
 * @Author:李白
 * @Date:2023/3/9 16:51
 */
public class KafkaEasyUtil {
    /**
     * 指定主题发布消息
     * @param topic 主题
     * @param messageValue 消息的Value
     */
    public static void publishMessage(String topic, String messageValue){
        //设置JAAS配置文件的路径。
        KafkaConfig.configureSasl();
        //加载kafka.properties。
        Properties kafkaProperties =  KafkaConfig.getKafkaProperties();
        Properties props = new Properties();
        //设置接入点,请通过控制台获取对应Topic的接入点。
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaProperties.getProperty("bootstrap.servers"));
        //与sasl路径类似,该文件也不能被打包到jar中。
        props.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, kafkaProperties.getProperty("ssl.truststore.location"));
        //根证书store的密码,保持不变。
        props.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, "KafkaOnsClient");
        //接入协议,目前支持使用SASL_SSL协议接入。
        props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
        //SASL鉴权方式,保持不变。
        props.put(SaslConfigs.SASL_MECHANISM, "PLAIN");
        //消息队列Kafka版消息的序列化方式。
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        //请求的最长等待时间。
        props.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 30 * 1000);
        //设置客户端内部重试次数。
        props.put(ProducerConfig.RETRIES_CONFIG, 5);
        //设置客户端内部重试间隔。
        props.put(ProducerConfig.RETRY_BACKOFF_MS_CONFIG, 3000);
        //Hostname校验改成空。
        props.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
        //构造Producer对象,注意,该对象是线程安全的,一般来说,一个进程内一个Producer对象即可。
        //如果想提高性能,可以多构造几个对象,但不要太多,最好不要超过5个。
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);
        //这里的ProducerRecord对象创建时可以添加不同数量的参数(重载),所以也就可以选择不同方式发布消息
        //这里的消息值可在创建producerRecord时定义参数类型
        ProducerRecord<String, String> producerRecord = new ProducerRecord<>(topic, messageValue);
        try {
            //发布消息,并返回获取的实体
            RecordMetadata recordMetadata = producer.send(producerRecord).get();
            //这里的recordMetadata控制台打印出来的是主题-分区@位点(topic-partition@offset),如test_001-1@1
            System.out.println("recordMetadata: "+recordMetadata.toString());
            //主题
            System.out.println("topic: "+recordMetadata.topic());
            //分区
            System.out.println("partition: "+recordMetadata.partition());
            //位点
            System.out.println("offset: "+recordMetadata.offset());
            //时间(long类型)
            System.out.println("timestamp: "+recordMetadata.timestamp());
            producer.flush();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 指定主题、消费组消费消息(消费主题下所有的消息)
     * @param topic 主题
     * @param consumerGroup 消费者组
     */
    public static void consumerMessage(String topic, String consumerGroup){
        //设置JAAS配置文件的路径。
        KafkaConfig.configureSasl();
        //加载kafka.properties
        Properties kafkaProperties =  KafkaConfig.getKafkaProperties();
        Properties props = new Properties();
        //设置接入点,请通过控制台获取对应Topic的接入点。
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaProperties.getProperty("bootstrap.servers"));
        //可更加实际拉去数据和客户的版本等设置此值,默认30s。
        props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 30000);
        //设置SSL根证书的路径,请记得将XXX修改为自己的路径。
        //与SASL路径类似,该文件也不能被打包到jar中。
        props.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, kafkaProperties.getProperty("ssl.truststore.location"));
        //根证书存储的密码,保持不变。
        props.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, "KafkaOnsClient");
        //接入协议,目前支持使用SASL_SSL协议接入。
        props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
        //SASL鉴权方式,保持不变。
        props.put(SaslConfigs.SASL_MECHANISM, "PLAIN");
        //两次Poll之间的最大允许间隔。
        //消费者超过该值没有返回心跳,服务端判断消费者处于非存活状态,服务端将消费者从Group移除并触发Rebalance,默认30s。
        props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 30000);
        //设置单次拉取的量,走公网访问时,该参数会有较大影响。
        props.put(ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, 32000);
        props.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG, 32000);
        //每次poll的最大数量。
        //注意该值不要改得太大,如果poll太多数据,而不能在下次poll之前消费完,则会触发一次负载均衡,产生卡顿。
        props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 30);
        //消息的反序列化方式
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        //当前消费实例所属的消费组,请在控制台申请之后填写。
        //属于同一个组的消费实例,会负载消费消息。
//        props.put(ConsumerConfig.GROUP_ID_CONFIG, kafkaProperties.getProperty("group.id"));
        props.put(ConsumerConfig.GROUP_ID_CONFIG, consumerGroup);
        //如果是SSL接入点实例,请取消注释以下一行代码。
        //Hostname校验改成空。
        props.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
        //构造消息对象,也即生成一个消费实例。
        KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);
        //订阅消息主题(消费者组和主题绑定关系)
        consumer.subscribe(Arrays.asList(topic));
        //循环消费
        while (true){
            //拉取消息的长轮询(通过poll进行消费)
            ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofMillis(1000));
            //遍历打印消费消息的信息
            for (ConsumerRecord<String, String> consumerRecord :
                    consumerRecords) {
                System.out.println(String.format("Consumer partition:%d offset:%d",consumerRecord.partition(),consumerRecord.offset()));
            }
        }
    }
}

6.创建主题和消费者组

a.创建主题

在控制台实例中->Topic管理->创建Topic
在没建立消费关系之前,进入topic会发现【相关的Group数量】为0
在未发消息前【当前服务器上消息总量】为0 这个值会随着发送消息的数量累积,但不会减少

b.创建消费者组

在控制台实例中->Group管理->创建Group
在没建立消费关系之前,进入Group会发现【订阅Topic数量】为0
建立消费关系后
在发消息后,未消费消息前,【消息堆积总量】为发送的消息数,这个值是指未被消费的消息

c.消费者组订阅主题

主题和消费者组订阅是在消费消息的代码中实现的通过subscribe

7.请求控制类

KafakEasyController.java

package com.kafka.controller;

import com.kafka.utils.KafkaEasyUtil;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassDescription: 请求控制测试
 * @Author:李白
 * @Date:2023/3/9 16:59
 */
@RestController
@RequestMapping("kafka-e")
public class KafkaEasyController {
    /**
     * 发布消息
     * @return
     */
    @CrossOrigin
    @RequestMapping(value = "pub", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public String publishMessage(){
        String topic = "test_001";
        String messageValue = "this is a test message for kafka pub";
        KafkaEasyUtil.publishMessage(topic,messageValue);
        return "消息发送成功!";
    }

    /**
     * 消费消息
     * @return
     */
    @CrossOrigin
    @RequestMapping(value = "con", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public String consumingMessage(){
        String topic = "test_001";
        String consumerGroup = "comsumer_test001";
        KafkaEasyUtil.consumerMessage(topic,consumerGroup);
        return "消费成功!";
    }
}

8.postman请求调用

a.启动spring boot项目
b.发布消息

postman请求url

127.0.0.1:8088/kafka-e/pub

控制台打印信息

recordMetadata: test_001-1@7
topic: test_001
partition: 1
offset: 7
timestamp: 1678413658690

发送成功后可在主题中查看【当前服务器上消息总量】新增一条,其余未变
可在主题界面-消息查询-查询指定分区的消息内容

c.消费消息

postman请求url

127.0.0.1:8088/kafka-e/con

控制台打印信息

Consumer partition:1 offset:7

在第一次消费成功后,消费者组和主题建立订阅关系,之后每次发布消息都会在消费者组的【消息堆积总量】中体现

下图为建立订阅关系后(消费一次之后发送了14个消息)的主题界面
在这里插入图片描述
下图为发布一条消息后未被消费时的消费组界面
在这里插入图片描述
下图为消费完消息的消费组界面
在这里插入图片描述


拓展

以上代码均为参考官网KafkaJavaSDK修改有需要的话可在文章开头【二、实现步骤】下方链接进入

关于Kafka的相关名词介绍:
Kafka关键词介绍传送门

写在最后:
这里只是简单实现了kafka的发消息和消费消息功能,如果想更深入了解发布和消费相关内容可参考【Kafka消息发布和消费升级】
升级的点:
可根据不同参数发布、消费消息(如指定主题发布、消费,指定分区发布、消费,多主题订阅,多消费者消费)
commitSync同步提交(手动提交),commitAsync异步提交,组合提交

### 集成 Kafka 和 ELK 到 Spring Boot 项目 #### 使用 Spring Boot 进行 Kafka集成 为了使 Spring Boot 应用程序能够发送和接收消息,可以利用 `spring-kafka` 依赖项来简化这一过程。通过配置文件中的几行设置以及一些简单的 Java 注解就可以实现基本的消息传递功能[^1]。 ```xml <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency> ``` 对于生产者端而言,在应用程序属性文件中定义 Kafka broker 地址和其他必要的参数之后,只需要创建一个带有 `@KafkaListener` 注解的方法即可监听特定主题上的消息;而对于消费者,则可以通过注入 `KafkaTemplate` 来轻松地向指定的主题发布新消息。 #### 将日志数据导入到 ELK 堆栈 为了让 Spring Boot 日志能被收集并处理,通常会采用 Logback 或 Log4j2 等框架作为应用的日志记录器,并配合 Filebeat 工具定期扫描这些日志文件并将它们转发给 Logstash 处理。Logstash 可以解析、过滤和转换接收到的数据流,最终将其存储至 Elasticsearch 中供后续查询分析之用[^2]。 ```json input { beats { port => "5044" } } filter { grok { match => { "message" => "%{TIMESTAMP_ISO8601}@%{JAVACLASS}" } } } output { elasticsearch { hosts => ["http://localhost:9200"] } } ``` 上述配置展示了如何让 Logstash 接收来自 Beats 客户端传输过来的日志条目,并运用 Grok 插件提取其中的时间戳与类名信息以便更好地索引文档结构化字段。最后再把整理好的事件写入本地运行着的 ES 实例里去。 #### 结合使用 Alibaba Cloud 提供的服务 如果考虑部署于云端环境的话,那么借助像阿里云这样的平台所提供的托管解决方案将会大大减轻运维负担。例如官方博客提到过可以在其容器服务之上无缝接入自家的日志管理组件——SLS (Simple Logging Service),从而更高效便捷地完成整个监控体系搭建工作[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值