第三章 Spring Boot 整合 Kafka消息队列 消息者
前言
Kafka 是一个消息队列产品,基于Topic partitions的设计,能达到非常高的消息发送处理性能。本文主是基于Spirng Boot封装了Apache 的Kafka-client,用于在Spring Boot 项目里快速集成kafka。
一、Kafka 是什么?
Apache Kafka是分布式发布-订阅消息系统。
它最初由LinkedIn公司开发,之后成为Apache项目的一部分。
Kafka是一种快速、可扩展的、设计内在就是分布式的,分区的和可复制的提交日志服务。
二、消息者
1.引入库
引入需要依赖的jar包,引入POM文件
<dependencies>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
</dependency>
</dependencies>
2.配置文件
spring:
custom:
kafka:
username: admin
password: admin-secret
partitions: 1
enable-auto-commit: false
topics: CHANNEL-BodyBusiness-dataDev,CHANNEL-BodyBusiness-pushDev
groupId: consumer-group-lms-dev
batch-listener: false
bootstrap-servers:
- 192.168.1.95:9092

3.端启动类
启动类名 EnableAutoKafkaClient
package com.cdkjframework.kafka.consumer.annotation; import com.cdkjframework.kafka.consumer.config.KafkaClientMarkerConfiguration;
import org.springframework.context.annotation.Import; import java.lang.annotation.*; /**
* @ProjectName: cdkj-framework
* @Package: com.cdkjframework.kafka.consumer.annotation
* @ClassName: EnableAutoKafkaClient
* @Description: Kafka客户端自动启动类
* @Author: xiaLin
* @Date: 2023/7/18 9:20
* @Version: 1.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({KafkaClientMarkerConfiguration.class})
public @interface EnableAutoKafkaClient {
}
4.spring.factories配置文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.cdkjframework.kafka.consumer.config.KafkaClientAutoConfiguration

5.配置类
5.1 Kafka客户端配置
package com.cdkjframework.kafka.consumer.config; import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration; import java.util.List; /**
* @ProjectName: cdkj-framework
* @Package: com.cdkjframework.kafka.consumer.config
* @ClassName: KafkaClientConfig
* @Description: Kafka客户端配置
* @Author: xiaLin
* @Version: 1.0
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.custom.kafka")
public class KafkaClientConfig { /**
* 服务列表
*/
private List<String> bootstrapServers; /**
* 主题
*/
private List<String> topics; /**
* 账号
*/
private String username; /**
* 密码
*/
private String password; /**
* 延迟为1毫秒
*/
private Integer linger = 1; /**
* 批量大小
*/
private Integer batchSize = 16384; /**
* 重试次数,0为不启用重试机制
*/
private Integer retries = 0; /**
* 人锁
*/
private Integer maxBlock = 6000; /**
* acks
*/
private String acks = "1"; /**
* security.providers
*/
private String securityProviders; /**
* 启用自动提交
*/
private boolean enableAutoCommit = true; /**
* 会话超时
*/
private String sessionTimeout = "5000"; /**
* 会话超时
*/
private Integer maxPollInterval = 10000; /**
* 组ID
*/
private String groupId = "defaultGroup"; /**
* 最大投票记录
*/
private Integer maxPollRecords = 1; /**
* 并发性
*/
private Integer concurrency = 3; /**
* 拉取超时时间
*/
private Integer pollTimeout = 60000; /**
* 批量监听
*/
private boolean batchListener = false; /**
* 副本数量
*/
private String sort = "1"; /**
* 分区数
*/
private Integer partitions = 3; /**
* 消费者默认支持解压
*/
private String compressionType = "none"; /**
* offset偏移量规则设置
*/
private String autoOffsetReset = "earliest"; /**
* 自动提交的频率
*/
private Integer autoCommitInterval = 100; /**
* 生产者可以使用的总内存字节来缓冲等待发送到服务器的记录
*/
private Integer bufferMemory = 33554432; /**
* 消息的最大大小限制
*/
private Integer maxRequestSize = 1048576;
}

5.2 Kafka客户端自动配置
package com.cdkjframework.kafka.consumer.config; import com.cdkjframework.kafka.consumer.ConsumerConfiguration;
import com.cdkjframework.kafka.consumer.service.ConsumerService;
import com.cdkjframework.kafka.consumer.listener.ConsumerListener;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy; /**
* @ProjectName: cdkj-framework
* @Package: com.cdkjframework.kafka.consumer.config
* @ClassName: KafkaClientAutoConfiguration
* @Description: Kafka客户端自动配置
* @Author: xiaLin
* @Date: 2023/7/18 9:21
* @Version: 1.0
*/
@Lazy(false)
@RequiredArgsConstructor
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(KafkaClientConfig.class)
@AutoConfigureAfter({WebClientAutoConfiguration.class})
@ImportAutoConfiguration(ConsumerConfiguration.class)
@ConditionalOnBean(KafkaClientMarkerConfiguration.Marker.class)
public class KafkaClientAutoConfiguration { /**
* 消费者服务接口
*/
private final ConsumerService consumerService; /**
* kafka topic 启动触发器
*
* @return 返回结果
*/
@Bean
@ConditionalOnMissingBean
public ConsumerListener kafkaConsumer() {
return new ConsumerListener(consumerService);
}
}
5.3 Kafka客户端标记配置
package com.cdkjframework.kafka.consumer.config; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka; /**
* @ProjectName: cdkj-framework
* @Package: com.cdkjframework.kafka.consumer.config
* @ClassName: KafkaClientMarkerConfiguration
* @Description: Kafka客户端标记配置
* @Author: xiaLin
* @Date: 2023/12/6 13:11
* @Version: 1.0
*/
@EnableKafka
@Configuration(proxyBeanMethods = false)
public class KafkaClientMarkerConfiguration { @Bean
public Marker kafkaMarker() {
return new Marker();
} public static class Marker { }
}
6.消费者配置
package com.cdkjframework.kafka.consumer; import com.cdkjframework.kafka.consumer.config.KafkaClientConfig;
import com.cdkjframework.util.log.LogUtils;
import com.cdkjframework.util.tool.JsonUtils;
import com.cdkjframework.util.tool.StringUtils;
import lombok.RequiredArgsConstructor;
import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.consumer.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.common.TopicPartition;
import org.apache.kafka.common.config.SaslConfigs;
import org.apache.kafka.common.security.auth.SecurityProtocol;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.config.KafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.listener.BatchErrorHandler;
import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
import org.springframework.kafka.listener.ConsumerAwareErrorHandler; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; /**
* @ProjectName: cdkj-framework
* @Package: com.cdkjframework.kafka.consumer
* @ClassName: ProducerConfiguration
* @Description: 设置@Configuration、@EnableKafka两个注解,声明Config并且打开KafkaTemplate能力。
* @Author: xiaLin
* @Version: 1.0
*/
@Configuration
@RequiredArgsConstructor
public class ConsumerConfiguration { /**
* 日志
*/
private final LogUtils logUtils = LogUtils.getLogger(ConsumerConfiguration.class); /**
* JAAS配置
*/
private String JAAS_CONFIG = "org.apache.kafka.common.security.plain.PlainLoginModule required username=%s password=%s;"; /**
* 配置
*/
private final KafkaClientConfig kafkaClientConfig; /**
* 监听容器工厂
*
* @return 返回结果
*/
@Bean(name = "kafkaListenerContainerFactory")
public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String>
factory = new ConcurrentKafkaListenerContainerFactory<>();
// 设置消费者工厂
factory.setConsumerFactory(consumerFactory());
// 消费者组中线程数量
factory.setConcurrency(kafkaClientConfig.getConcurrency());
// 拉取超时时间
factory.getContainerProperties().setPollTimeout(kafkaClientConfig.getPollTimeout());
// 当使用批量监听器时需要设置为true
factory.setBatchListener(kafkaClientConfig.isBatchListener());
// 将单条消息异常处理器添加到参数中
factory.setErrorHandler(new ConsumerAwareErrorHandler() {
@Override
public void handle(Exception thrownException, ConsumerRecord<?, ?> data, Consumer<?, ?> consumer) {
logUtils.error("// 将单条消息异常:" + thrownException.getMessage());
logUtils.error("// 将单条消息:" + data.toString() + "," + consumer.toString());
Iterator<TopicPartition> iterator = consumer.assignment().iterator();
if (iterator.hasNext()) {
// 提交重新消费
consumer.seek(iterator.next(), data.offset());
}
}
});
if (kafkaClientConfig.isBatchListener()) {
// 将批量消息异常处理器添加到参数中
factory.setBatchErrorHandler(new BatchErrorHandler() {
@Override
public void handle(Exception thrownException, ConsumerRecords<?, ?> data) {
logUtils.error("// 将批量消息异常:" + thrownException.getMessage());
logUtils.error(thrownException);
logUtils.error(JsonUtils.objectToJsonString(data));
}
});
}
// factory.setContainerCustomizer(); return factory;
} /**
* 消费者工厂
*
* @return 返回消费工厂
*/
@Bean
public ConsumerFactory<String, String> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(consumerConfig());
} /**
* 消费者配置
*
* @return 返回结果
*/
@Bean
public Map<String, Object> consumerConfig() {
Map<String, Object> propsMap = new HashMap<>();
// Kafka地址
propsMap.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaClientConfig.getBootstrapServers());
//配置默认分组,这里没有配置+在监听的地方没有设置groupId,多个服务会出现收到相同消息情况
propsMap.put(ConsumerConfig.GROUP_ID_CONFIG, kafkaClientConfig.getGroupId());
// 是否自动提交offset偏移量(默认true)
propsMap.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, kafkaClientConfig.isEnableAutoCommit());
// 心跳机制
propsMap.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, kafkaClientConfig.getMaxPollInterval());
// 每次读取最大记录
propsMap.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, kafkaClientConfig.getMaxPollRecords());
if (kafkaClientConfig.isEnableAutoCommit()) {
// 自动提交的频率(ms)
propsMap.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, kafkaClientConfig.getAutoCommitInterval());
}
// 键的反序列化方式
propsMap.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
// 值的反序列化方式
propsMap.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
// offset偏移量规则设置:
// (1)、earliest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
// (2)、latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据
// (3)、none:topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常
propsMap.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, kafkaClientConfig.getAutoOffsetReset()); // 安全认证 账号密码
if (StringUtils.isNotNullAndEmpty(kafkaClientConfig.getUsername()) &&
StringUtils.isNotNullAndEmpty(kafkaClientConfig.getPassword())) {
propsMap.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, SecurityProtocol.SASL_PLAINTEXT.name);
String SASL_MECHANISM = "PLAIN";
propsMap.put(SaslConfigs.SASL_MECHANISM, SASL_MECHANISM);
propsMap.put(SaslConfigs.SASL_JAAS_CONFIG, String.format(JAAS_CONFIG, kafkaClientConfig.getUsername(), kafkaClientConfig.getPassword()));
} return propsMap;
}
}

7.消费者服务
package com.cdkjframework.kafka.consumer.service; import com.cdkjframework.exceptions.GlobalException; /**
* @ProjectName: cdkj-framework
* @Package: com.cdkjframework.kafka.consumer
* @ClassName: com.cdkjframework.kafka.consumer.service.ConsumerService
* @Description: 消费者服务
* @Author: xiaLin
* @Version: 1.0
*/
public interface ConsumerService { /**
* 消息内容
*
* @param topics 主题
* @param message 内容
* @throws GlobalException 异常信息
*/
void onMessage(String topics, String message) throws GlobalException;
}

8.消费者监听器
package com.cdkjframework.kafka.consumer.listener; import com.cdkjframework.kafka.consumer.service.ConsumerService;
import com.cdkjframework.util.log.LogUtils;
import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener; /**
* @ProjectName: cdkj-kafka-client
* @Package: com.cdkjframework.kafka.consumer
* @ClassName: com.cdkjframework.kafka.consumer.listener.ConsumerListener
* @Description: 消费者监听器
* @Author: xiaLin
* @Version: 1.0
*/
public class ConsumerListener { /**
* 日志
*/
private final LogUtils logUtils = LogUtils.getLogger(ConsumerListener.class); /**
* 消费者服务接口
*/
private final ConsumerService consumerService; /**
* 构造函数
*
* @param consumerService 消费者服务接口
*/
public ConsumerListener(ConsumerService consumerService) {
this.consumerService = consumerService;
} /**
* 单条监听MQ消息
*
* @param data 消息内容
* @param consumer 消息者
*/
@KafkaListener(topics = "#{'${spring.custom.kafka.topics}'.split(',')}", groupId = "${spring.custom.kafka.groupId}")
public void listener(ConsumerRecord<String, String> data, Consumer consumer) {
try {
consumerService.onMessage(data.topic(), data.value());
consumer.commitAsync();
} catch (Exception e) {
logUtils.error(e);
// 抛出异常,以重试消费
throw new RuntimeException(e.getMessage());
}
}
}

总结
例如:以上就是今天要讲的内容,本文仅仅简单介绍了 Spring Boot 集成消息消费者的封装。
相对应的开源项目欢迎访问:维基框架
第三章 Spring Boot 整合 Kafka消息队列 消息者的更多相关文章
- Spring Boot 整合 Kafka
Kafka 环境搭建 kafka 安装.配置.启动.测试说明: 1. 安装:直接官网下载安装包,解压到指定位置即可(kafka 依赖的 Zookeeper 在文件中已包含) 下载地址:https:// ...
- spring boot整合kafka
最近项目需求用到了kafka信息中间件,在此做一次简单的记录,方便以后其它项目用到. 引入依赖 <dependency> <groupId>org.springframewor ...
- Spring Boot2 系列教程(三十)Spring Boot 整合 Ehcache
用惯了 Redis ,很多人已经忘记了还有另一个缓存方案 Ehcache ,是的,在 Redis 一统江湖的时代,Ehcache 渐渐有点没落了,不过,我们还是有必要了解下 Ehcache ,在有的场 ...
- spring boot 2.x 系列 —— spring boot 整合 kafka
文章目录 一.kafka的相关概念: 1.主题和分区 2.分区复制 3. 生产者 4. 消费者 5.broker和集群 二.项目说明 1.1 项目结构说明 1.2 主要依赖 二. 整合 kafka 2 ...
- kafka学习(五)Spring Boot 整合 Kafka
文章更新时间:2020/06/08 一.创建Spring boot 工程 创建过程不再描述,创建后的工程结构如下: POM文件中要加入几个依赖: <?xml version="1.0& ...
- Netty学习第四章 spring boot整合netty的使用
现在大多数项目都是基于spring boot进行开发,所以我们以spring boot作为开发框架来使用netty.使用spring boot的一个好处就是能给将netty的业务拆分出来,并通过spr ...
- 上手spring boot项目(三)之spring boot整合mybatis进行增删改查的三种方式。
1.引入依赖. <!--springboot的web起步依赖--><dependency> <groupId>org.springframework.boot< ...
- 上手spring boot项目(三)之spring boot整合mybatis进行增删改查
使用mybatis框架进行增删改查大致有两种基础方式,一种扩展方式.两种基础方式分别是使用xml映射文件和使用方法注解.扩展方式是使用mybatis-plus的方式,其用法类似于spring-data ...
- spring boot 整合kafka 报错 Exception thrown when sending a message with key='null' and payload=JSON to topic proccess_trading_end: TimeoutException: Failed to update metadata after 60000 ms.
org.springframework.kafka.support.LoggingProducerListener- Exception thrown when sending a message w ...
- Spring Boot 整合JDBC 实现后端项目开发
一.前言 二.新建Spring Boot 项目 三.Spring Boot 整合JDBC 与MySQL 交互 3.1 新建数据表skr_user 3.2 Jdbcproject 项目结构如下 3.3 ...
随机推荐
- Iceberg metrics导致的问题
一.问题描述 在iceberg rewrite时报错:org.apache.iceberg.exceptions.ValidationException: Cannot commit, found n ...
- SQL优化的20条军规
前言 作为一个写SQL的程序员,代码写得好不好是一回事,但SQL写得烂,性能拉胯,全公司都得为你的慢查询买单,尤其在大数据量表上,SQL写不好就是"内鬼"级别的错误. 今天不整那些 ...
- Qt QString的格式化与QString的拼接
1. QString 与 QString 直接用 + 号也可以 QString date = "昨晚"; QString msg = "你真棒": QStrin ...
- 公众号已上线 Ask AI 功能
Get新技能,给公众号接入AI智能体,没花一分钱. 不禁感慨这时代的进步也太快了,曾经科幻小说中描绘的未来已成现实! 下面是笔者在腾讯元宝中创建的智能体"鲸鱼小助手": 如果今后要 ...
- springboot接入方式对接股票数据源API接口
为了创建一个Java项目来对接StockTV的API接口,我们可以使用HttpURLConnection或第三方库如OkHttp来发送HTTP请求,并使用Java-WebSocket库来处理WebSo ...
- uniapp vue3 setup + 云开发开发个人小程序
最近使用uniapp vue3 setup + 云开发开发了个人小程序,设计使用figma软件,看下成品截图吧(可以直接微信搜索[识光]小程序体验,或者最底部有码可以直接扫)
- MySQL 是否可以用 Docker 容器化?
容器 容器是为了解决 "在切换运行环境时,如何保证软件能够正常运行",容器是轻量级应用代码包,它包含在任何环境中运行所需的所有元素的软件包.容器可以虚拟化操作系统,包含依赖项,例如 ...
- 请求方法:GET 与 POST
根据 RFC 规范,GET 的语义是从服务器获取指定的资源,GET 请求的参数位置一般是写在 URL 中,URL 规定只能支持 ASCII,所以 GET 请求的参数只允许 ASCII 字符 ,而且浏览 ...
- Java使用多线程处理未知任务数方案
知道任务个数,你可以定义好线程数规则,生成线程数去跑 代码说明: 虚拟线程池: 使用 Executors.newVirtualThreadPerTaskExecutor() 创建虚拟线程池,每个任务将 ...
- bug|Git Hooks pre-commit|git 提交代码报错|error: 'describe' 'it' 'expect' is not defined (no-undef)|pre-commit hook failed (add --no-verify to bypass)|
前言 今天学习 jest 的 vue-test-utils 的配置及使用. 报错原因为 jest 全局变量 git 提交代码报错,使用除了参考链接里的解决方案,正好复习一下之前学习的 Git Hook ...