天行健,君子以自强不息;地势坤,君子以厚德载物。
每个人都有惰性,不断学习是好好生活的根本,共勉!
文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。
文章目录
一、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异步提交,组合提交