Spring Boot 2.x 快速集成Kafka
1 Kafka
Kafka是一个开源分布式的流处理平台,一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者在网站中的所有动作流数据。Kafka由Scala和Java编写,2012年成为Apache基金会下顶级项目。
2 Kafka优点
- 低延迟:Kafka支持低延迟消息传递,速度极快,能达到200w写/秒
- 高性能:Kafka对于消息的分布,订阅都有高吞吐量。即使存储了TB级的信息,依然能够保证稳定的性能
- 可靠性:Kafka是分布式,分区,复制和容错的,保证零停机和零数据丢失
- 可扩展:用户可以从但个代理Broker开始作POC,然后慢慢扩展到由三个Broker组成的小型开发集群,接着扩展到数十个甚至数百个Broker集群进入生产阶段,可以在集群联机时进行扩展,而不会影响整个系统的可用性
- 多个生产者:无论这些客户使用相同Topic还是多个Topic,Kafka都能无缝处理多个生产者,使得系统可以非常容易聚合来自许多前端系统的数据并使其保持一致
- 多个消费者:Kafka具有多个消费者设计,可以读取任何但个消息流而不会相互干扰。多个Kafka消费者可以组成一个消费组进行操作并共享消息流,从而确保每一条消息只被整个消费组处理一次
- 基于磁盘的保留:Kafka使用分布式提交日志,消息能够快速持久化到磁盘上。消息持久化意味着如果消费者落后,无论是由于处理速度缓慢还是突然的消息涌入都不会有丢失数据的危险,也意味着消费者可以被停止。消息将保留在Kafka中,允许消费者重新启动并且从中断处获取处理信息而不会丢失数据
3 Kafka相关术语
- Broker:Kafka集群包含一个或多个服务器,这种服务器称为Broker
- Topic:每条发布到Kafka的消息都有一个类别,这个类别称为Topic。物理上不同Topic的消息分开存储,逻辑上Topic的消息虽然保存在一个或多个Broker上,但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存放于何处
- Partition:每个Topic包含一个或多个Partition
- Producer:生产者,负责发布消息到Broker
- Consumer:消费者,向Broker读取消息的客户端
- Consumer Group:每个Consumer属于一个特定的Consumer Group,可以为每个Consumer指定Group Name,否则属于默认Group
4 动手干活
4.1 环境
- Spring Boot 2.3.1
- IDEA 2020.1.1
- OpenJDK 11.0.7
- Kafka 2.5.0
- Kotlin 1.3.72
4.2 下载Kafka
官网戳这里。
下载并解压(注意需要Kafka与Spring Boot版本对应,可以参考这里):
tar -xvf kafka_2.12-2.5.0.tgz
cd kafka_2.12-2.5.0
接着启动ZooKeeper与Kafka:
bin/zookeeper-server-start.sh -daemon config/zookeeper.properties
bin/kafka-server-start.sh config/server.properties
Kafka需要用到ZooKeeper,需要在启动Kafka之前启动ZooKeeper(ZooKeeper是一个开源的分布式应用程序协调服务,是Hadoop的组件,主要用于解决分布式应用中的一些数据管理问题)。
Kafka默认使用9092端口,部署在服务器上需要注意防火墙以及安全组的处理。
4.3 新建工程
考虑到Spring Boot在2.3.0M1中(截至本文写作日期2020.07.14Spring Boot已更新到2.4.0M1)首次采用Gradle而不是Maven来构建项目,换句话说日后Spring Boot的构建工具将从Maven迁移到Gradle,Spring Boot团队给出的主要原因是可以减少项目构建所花费的时间,详情可以戳这里瞧瞧。
另外由于另一个基于JVM的语言Kotlin的日渐崛起,后端开始逐渐有人采用Kotlin(尽管不多,不过语法糖真的香,JetBrains家的语言配合IDE,爽得飞起),因此本示例项目将采用两种方式搭建:
- Java+Maven
- Kotlin+Gradle
选择的依赖如下(当然您喜欢的话可以在pom.xml
或者build.gradle.kts
里面加,对于Kotlin不需要Lombok
):
4.4 项目结构
Java版:
Kotlin版:
serialize
:序列化/反序列化实体类Constant.java
/Constant.kt
:常量类Consumer.java
/Consumer.kt
:消费者类Entity.java
/Entity.kt
:实体类Producer.java
/Product.kt
:生产者类TestApplicationTets
:测试类
4.5 常量类
包含Topic与GroupId:
public class Constants {
public static final String TOPIC = "TestTopic";
public static final String GROUP_ID = "TestGroupId";
}
Kotlin版:
object Constants
{
const val TOPIC = "TestTopic"
const val GROUP_ID = "TestGroupId"
}
4.6 实体类
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class Entity {
private long id;
private String name;
private int num;
}
说一下Lombok的几个注解:
@AllArgsConstructor
/@NoArgsConstructor
:生成所有参数/无参数构造方法@Data
:@Setter+@Getter+@RequiredArgsConstrucotr+@ToString+@EqualAndHashCode
@Builder
:可以通过建造者模式创建对象
Kotlin版:
class Entity {
var id: Long = 0
var name: String = ""
var num: Int = 0
constructor()
constructor(id:Long,name:String,num:Int)
{
this.id = id
this.name = name
this.num = num
}
}
4.7 生产者
@Component
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class Producer {
private final KafkaTemplate<String, Entity> kafkaTemplate;
public void send(Entity entity) {
//发送消息
ListenableFuture<SendResult<String, Entity>> future =
kafkaTemplate.send(Constants.TOPIC, entity);
//回调函数
future.addCallback(new ListenableFutureCallback<>() {
@Override
public void onFailure(Throwable throwable) {
log.info("Send message failed");
}
@Override
public void onSuccess(SendResult<String, Entity> stringEntitySendResult) {
log.info("Send message success");
}
});
}
}
这里的send
有两个参数,第一个为消息的Topic,第二个为消息体,一般使用String或者Json。
Kotlin版:
@Component
class Producer
{
@Autowired
private var kafkaTemplate:KafkaTemplate<String,Entity> ? = null
private val log = LoggerFactory.getLogger(this.javaClass)
fun send(entity: Entity)
{
val future = kafkaTemplate!!.send(Constants.TOPIC,entity);
future.addCallback(object : ListenableFutureCallback<SendResult<String?, Entity?>?>{
override fun onSuccess(result : SendResult<String?,Entity?>?)
{
log.info("Send success");
}
override fun onFailure(e:Throwable)
{
log.info("Send failed");
}
})
}
}
4.8 消费者
@Component
@Slf4j
public class Consumer {
@KafkaListener(topics = Constants.TOPIC,groupId = Constants.GROUP_ID)
public void consume(Entity entity)
{
log.info("Consume a entity, id is "+entity.getId());
}
}
使用@KafkaListener
注解,第一个参数表示需要消费的消息的Topic,可以是String []
,第二个是消费者组的id。生产者的消息Topic必须与消费者的Topic保持一致否则不能消费,这里简单处理打印日志。
Kotlin版:
@Component
class Consumer {
private val log = LoggerFactory.getLogger(this.javaClass)
@KafkaListener(topics = [Constants.TOPIC],groupId = Constants.GROUP_ID)
fun consume(entity: Entity) {
log.info("Consume a entity, id is "+entity.id.toString())
}
}
4.9 序列化/反序列化
这里自定义了序列化/反序列化类,序列化/反序列化类需要实现org.apache.kafka.common.serialization.Serializer<T>/Deserializer<T>
接口,其中T
是想要序列化的类型,这里是Entity
。序列化接口如下:
public interface Serializer<T> extends Closeable {
default void configure(Map<String, ?> configs, boolean isKey) {
}
byte[] serialize(String var1, T var2);
default byte[] serialize(String topic, Headers headers, T data) {
return this.serialize(topic, data);
}
default void close() {
}
}
反序列化接口如下:
public interface Deserializer<T> extends Closeable {
default void configure(Map<String, ?> configs, boolean isKey) {
}
T deserialize(String var1, byte[] var2);
default T deserialize(String topic, Headers headers, byte[] data) {
return this.deserialize(topic, data);
}
default void close() {
}
}
也就是只需要实现其中的serialize/deserialize
方法即可。这里序列化/反序列化用到了自带的Jackson:
@Slf4j
public class Serializer implements org.apache.kafka.common.serialization.Serializer<Entity> {
public byte [] serialize(String topic, Entity entity)
{
try {
return entity == null ? null : new ObjectMapper().writeValueAsBytes(entity);
} catch (JsonProcessingException e) {
e.printStackTrace();
log.error("Can not serialize entity in Serializer");
}
return null;
}
}
反序列化:
@Slf4j
public class Deserializer implements org.apache.kafka.common.serialization.Deserializer<Entity> {
public Entity deserialize(String topic,byte [] data)
{
try {
return data == null ? null : new ObjectMapper().readValue(data,Entity.class);
} catch (IOException e) {
e.printStackTrace();
log.error("Can not deserialize entity in Deserializer");
}
return null;
}
}
Kotlin版:
class Serializer : org.apache.kafka.common.serialization.Serializer<Entity?>
{
private val log = LoggerFactory.getLogger(this.javaClass)
override fun serialize(topic: String?, data: Entity?): ByteArray? {
try {
return if (data == null) null else ObjectMapper().writeValueAsBytes(data)
}
catch (e:JsonProcessingException)
{
e.printStackTrace()
log.error("Can not serialize entity in Serializer")
}
return null
}
}
class Deserializer : org.apache.kafka.common.serialization.Deserializer<Entity?>
{
private val log = LoggerFactory.getLogger(this.javaClass)
override fun deserialize(topic: String?, data: ByteArray?): Entity? {
try
{
return ObjectMapper().readValue(data, Entity::class.java)
}
catch (e:IOException)
{
e.printStackTrace()
log.error("Can not deserialize entity in Deserializer")
}
return null
}
}
4.10 配置文件
application.properties
:
# 地址
spring.kafka.bootstrap-servers=localhost:9092
# 消费者组id
spring.kafka.consumer.group-id=TestGroupId
spring.kafka.consumer.auto-offset-reset=earliest
# 消费者键反序列化类
spring.kafka.consumer.key-deserializer=org.ap饭ache.kafka.common.serialization.StringDeserializer
# 消费者值反序列化类
spring.kafka.consumer.value-deserializer=com.test.serialize.Deserializer
# 生产者键序列化类
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
# 生产者值序列化类
spring.kafka.producer.value-serializer=com.test.serialize.Serializer
对于auto-offest-rest
,该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下怎么处理,有四个取值:
earliest
:当各分区有已提交的offest
时,从提交的offest
开始消费,无提交的offest
时,从头开始消费latest
(默认):当各分区有已提交的offest
时,从提交的offest
开始消费,无提交的offest
时,消费新产生的该分区下的数据none
:各分区都存在已提交的offest
时,从offest
后消费,只要有一个分区不存在已提交的offest
,则抛出异常exception
:其他情况将抛出异常给消费者
对于序列化/反序列化,String可以使用自带的序列化/反序列化类:
org.apache.kafka.common.serialization.StringSerializer
org.apache.kafka.common.serialization.StringDeserializer
至于Json可以使用:
org.springframework.kafka.support.serializer.JsonSerializer
org.springframework.kafka.support.serializer.JsonDeserializer
其他自定义的请实现org.apache.kafka.common.serialization.Serializer<T>/Deserializer<T>
接口。
yml版:
spring:
kafka:
bootstrap-servers: localhost:9092
consumer:
group-id: TestGroupId
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: com.test.serialize.Deserializer
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: com.test.serialize.Serializer
5 测试
5.1 测试类
@SpringBootTest
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
class TestApplicationTests {
private final Producer producer;
@Test
void contextLoads() {
Random random = new Random();
for (int i = 0; i < 1000; i++) {
long id = i+1;
String name = UUID.randomUUID().toString();
int num = random.nextInt();
producer.send(Entity.builder().id(id).name(name).num(num).build());
}
}
}
生产者发送1000条消息。
Kotlin版:
@SpringBootTest
class TestApplicationTests {
@Autowired
private val producer:Producer? = null
@Test
fun contextLoads() {
for(i in 0..1000)
{
val id = (i + 1).toLong()
val name = java.util.UUID.randomUUID().toString()
val num = (0..100000).random()
producer!!.send(Entity(id,name,num))
}
}
}
5.2 测试
控制台输出如下:
所有消息被成功发送并且被成功消费。
最后可以去验证一下Kafka的Topic列表,可以看到配置文件中的Topic的值(TestTopic
),进入Kafka目录:
bin/kafka-topics.sh --list --zookepper localhost:2181
6 源码
7 参考
1、CSDN-Kafka优点
2、简书-Spring Boot 2.x 快速集成整合消息中间件 Kafka
3、简书-springboot 之集成kafka
如果觉得文章好看,欢迎点赞。
同时欢迎关注微信公众号:氷泠之路。
Spring Boot 2.x 快速集成Kafka的更多相关文章
- Spring Boot 2.0 快速集成整合消息中间件 Kafka
欢迎关注个人微信公众号: 小哈学Java, 每日推送 Java 领域干货文章,关注即免费无套路附送 100G 海量学习.面试资源哟!! 个人网站: https://www.exception.site ...
- Spring Boot系列 八、集成Kafka
一.引入依赖 <dependency> <groupId>org.springframework.kafka</groupId> <artifactId> ...
- spring boot / cloud (十七) 快速搭建注册中心和配置中心
spring boot / cloud (十七) 快速搭建注册中心和配置中心 本文将使用spring cloud的eureka和config server来搭建. 然后搭建的模式,有很多种,本文主要聊 ...
- Spring Boot 2.x 快速入门(下)HelloWorld示例详解
上篇 Spring Boot 2.x 快速入门(上)HelloWorld示例 进行了Sprint Boot的快速入门,以实际的示例代码来练手,总比光看书要强很多嘛,最好的就是边看.边写.边记.边展示. ...
- Spring Boot微服务如何集成fescar解决分布式事务问题?
什么是fescar? 关于fescar的详细介绍,请参阅fescar wiki. 传统的2PC提交协议,会持有一个全局性的锁,所有局部事务预提交成功后一起提交,或有一个局部事务预提交失败后一起回滚,最 ...
- Spring Boot与ActiveMQ的集成
Spring Boot对JMS(Java Message Service,Java消息服务)也提供了自动配置的支持,其主要支持的JMS实现有ActiveMQ.Artemis等.本节中,将以Active ...
- spring boot与ElasticSearch的集成
本文主要介绍Spring boot与ElasticSearch的集成,因为Spring boot的教程以及ElasticSearch的学习其他博客可能更优秀,所以建议再看这篇文章前先学习学习一下Spr ...
- Spring Boot 微服务应用集成Prometheus + Grafana 实现监控告警
Spring Boot 微服务应用集成Prometheus + Grafana 实现监控告警 一.添加依赖 1.1 Actuator 的 /prometheus端点 二.Prometheus 配置 部 ...
- Spring Boot(六)集成 MyBatis 操作 MySQL 8
一.简介 1.1 MyBatis介绍 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC代码和手动设置参数以及获取结果集. ...
随机推荐
- InnoDB -- 行记录格式
本文转载自InnoDB -- 行记录格式 分类 Named File Format InnoDB早期的文件格式(页格式)为Antelope,可以定义两种行记录格式,分别是Compact和Redunda ...
- 【OI向】快速傅里叶变换(Fast Fourier Transform)
[OI向]快速傅里叶变换(Fast Fourier Transform) FFT的作用 在学习一项算法之前,我们总该关心这个算法究竟是为了干什么. (以下应用只针对OI) 一句话:求多项式 ...
- oracle 中的左外连接、右外连接、全连接
左外连接 左外连接 全连接1.左外连接:表1 left [outer] join 表1 on 条件 在等值连接的基础上会把表1中的其他内容也展示出来 而表2只会显示符合条件的内容 . outer 可省 ...
- 如何读写拥有命名空间xmlns 属性的Xml文件(C#实现)
我们在进行C#项目Xml读写开发时经常遇到一些读写问题,今天我要介绍的是遇到多个命名空间xmlns属性时如何读写此类文件. 比如下面这个Xml文件: <?xml version="1. ...
- Java 学习阶段性感想
阶段性感想·操千曲而后晓声 回顾 从2月17日 到 今天 4月19日,我算是暂时完成了Java入门的学习了. 从基本语法到面向对象,从常见API到字符串集合,从文件处理到多线程,我学到了很多,很多很多 ...
- 无限可能 | Flutter 2 重点更新一览
我们非常高兴在本周发布了 Flutter 2.自 Flutter 1.0 发布至今已有两年多的时间,在如此短暂的时间内,我们解决了 24,541 个 issue,合并了来自 765 个贡献者的 17, ...
- Java 树结构实际应用 一(堆排序2秒排完800w数据)
堆排序 1 堆排序基本介绍 1) 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复 杂度均为 O(nlogn),它也是不稳定排序. 2) 堆是具有以下性 ...
- Codeforces Round #538 D. Lunar New Year and a Wander
题面: 传送门 题目描述: Bob想在公园散步.公园由n个点和m条无向边组成.当Bob到一个未经过的点时,他就会把这个点的编号记录在笔记本上.当且仅当Bob走完所有的点,他才会停下来.这时,Bob的笔 ...
- Dart 学习
语言特性 Dart所有的东西都是对象, 即使是数字numbers.函数function.null也都是对象,所有的对象都继承自Object类. Dart动态类型语言, 尽量给变量定义一个类型,会更安全 ...
- java mvc 及其缓存
使用Spring框架的好处是什么? - 轻量:Spring 是轻量的,基本的版本大约2MB. - 控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们. ...