前言

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消息队列 消息者的更多相关文章

  1. Spring Boot 整合 Kafka

    Kafka 环境搭建 kafka 安装.配置.启动.测试说明: 1. 安装:直接官网下载安装包,解压到指定位置即可(kafka 依赖的 Zookeeper 在文件中已包含) 下载地址:https:// ...

  2. spring boot整合kafka

    最近项目需求用到了kafka信息中间件,在此做一次简单的记录,方便以后其它项目用到. 引入依赖 <dependency> <groupId>org.springframewor ...

  3. Spring Boot2 系列教程(三十)Spring Boot 整合 Ehcache

    用惯了 Redis ,很多人已经忘记了还有另一个缓存方案 Ehcache ,是的,在 Redis 一统江湖的时代,Ehcache 渐渐有点没落了,不过,我们还是有必要了解下 Ehcache ,在有的场 ...

  4. spring boot 2.x 系列 —— spring boot 整合 kafka

    文章目录 一.kafka的相关概念: 1.主题和分区 2.分区复制 3. 生产者 4. 消费者 5.broker和集群 二.项目说明 1.1 项目结构说明 1.2 主要依赖 二. 整合 kafka 2 ...

  5. kafka学习(五)Spring Boot 整合 Kafka

    文章更新时间:2020/06/08 一.创建Spring boot 工程 创建过程不再描述,创建后的工程结构如下: POM文件中要加入几个依赖: <?xml version="1.0& ...

  6. Netty学习第四章 spring boot整合netty的使用

    现在大多数项目都是基于spring boot进行开发,所以我们以spring boot作为开发框架来使用netty.使用spring boot的一个好处就是能给将netty的业务拆分出来,并通过spr ...

  7. 上手spring boot项目(三)之spring boot整合mybatis进行增删改查的三种方式。

    1.引入依赖. <!--springboot的web起步依赖--><dependency> <groupId>org.springframework.boot< ...

  8. 上手spring boot项目(三)之spring boot整合mybatis进行增删改查

    使用mybatis框架进行增删改查大致有两种基础方式,一种扩展方式.两种基础方式分别是使用xml映射文件和使用方法注解.扩展方式是使用mybatis-plus的方式,其用法类似于spring-data ...

  9. 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 ...

  10. Spring Boot 整合JDBC 实现后端项目开发

    一.前言 二.新建Spring Boot 项目 三.Spring Boot 整合JDBC 与MySQL 交互 3.1 新建数据表skr_user 3.2 Jdbcproject 项目结构如下 3.3 ...

随机推荐

  1. Iceberg metrics导致的问题

    一.问题描述 在iceberg rewrite时报错:org.apache.iceberg.exceptions.ValidationException: Cannot commit, found n ...

  2. SQL优化的20条军规

    前言 作为一个写SQL的程序员,代码写得好不好是一回事,但SQL写得烂,性能拉胯,全公司都得为你的慢查询买单,尤其在大数据量表上,SQL写不好就是"内鬼"级别的错误. 今天不整那些 ...

  3. Qt QString的格式化与QString的拼接

    1. QString 与 QString 直接用 + 号也可以 QString date = "昨晚"; QString msg = "你真棒": QStrin ...

  4. 公众号已上线 Ask AI 功能

    Get新技能,给公众号接入AI智能体,没花一分钱. 不禁感慨这时代的进步也太快了,曾经科幻小说中描绘的未来已成现实! 下面是笔者在腾讯元宝中创建的智能体"鲸鱼小助手": 如果今后要 ...

  5. springboot接入方式对接股票数据源API接口

    为了创建一个Java项目来对接StockTV的API接口,我们可以使用HttpURLConnection或第三方库如OkHttp来发送HTTP请求,并使用Java-WebSocket库来处理WebSo ...

  6. uniapp vue3 setup + 云开发开发个人小程序

    最近使用uniapp vue3 setup + 云开发开发了个人小程序,设计使用figma软件,看下成品截图吧(可以直接微信搜索[识光]小程序体验,或者最底部有码可以直接扫)

  7. MySQL 是否可以用 Docker 容器化?

    容器 容器是为了解决 "在切换运行环境时,如何保证软件能够正常运行",容器是轻量级应用代码包,它包含在任何环境中运行所需的所有元素的软件包.容器可以虚拟化操作系统,包含依赖项,例如 ...

  8. 请求方法:GET 与 POST

    根据 RFC 规范,GET 的语义是从服务器获取指定的资源,GET 请求的参数位置一般是写在 URL 中,URL 规定只能支持 ASCII,所以 GET 请求的参数只允许 ASCII 字符 ,而且浏览 ...

  9. Java使用多线程处理未知任务数方案

    知道任务个数,你可以定义好线程数规则,生成线程数去跑 代码说明: 虚拟线程池: 使用 Executors.newVirtualThreadPerTaskExecutor() 创建虚拟线程池,每个任务将 ...

  10. 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 ...