目录
[1、 消息中间件概述](#1、 消息中间件概述)
[1.1 消息队列简介](#1.1 消息队列简介)
[1.2 消息队列应用场景](#1.2 消息队列应用场景)
[1.2.1 系统解耦](#1.2.1 系统解耦)
[1.2.2 流量消锋](#1.2.2 流量消锋)
[1.2.3 异步通信](#1.2.3 异步通信)
[1.3 MQ的优缺点](#1.3 MQ的优缺点)
[1.4 Kafka简介](#1.4 Kafka简介)
[1.5 MQ产品比对](#1.5 MQ产品比对)
[2、 Kafka环境搭建](#2、 Kafka环境搭建)
[2.1 Kafka集群机制](#2.1 Kafka集群机制)
[2.2 Kafka基础架构](#2.2 Kafka基础架构)
[2.3 Kafka集群搭建](#2.3 Kafka集群搭建)
[2.4 Kafka集群测试](#2.4 Kafka集群测试)
[3、Kafaka的 Java API入门案例](#3、Kafaka的 Java API入门案例)
[3.1 发送消息](#3.1 发送消息)
[3.2 消费消息](#3.2 消费消息)
[4、 在Spring Boot中集成Kafka](#4、 在Spring Boot中集成Kafka)
[4.1 生产者](#4.1 生产者)
[4.1.1 环境搭建](#4.1.1 环境搭建)
[4.1.2 发送消息](#4.1.2 发送消息)
[4.1.3 发送方式](#4.1.3 发送方式)
[4.1.4 拦截器配置](#4.1.4 拦截器配置)
[4.1.6 分区](#4.1.6 分区)
[4.1.7 生产者常见属性](#4.1.7 生产者常见属性)
[4.2 消费者](#4.2 消费者)
[4.2.1 环境搭建](#4.2.1 环境搭建)
[4.2.2 消费消息](#4.2.2 消费消息)
[4.2.3 消费者的手动位移提交](#4.2.3 消费者的手动位移提交)
[4.2.4 消费时的异常处理](#4.2.4 消费时的异常处理)
[Zookeeper 服务](#Zookeeper 服务)
[Kafka 实例](#Kafka 实例)
[Kafka Eagle 服务](#Kafka Eagle 服务)
1、 消息中间件概述
1.1 消息队列简介
消息队列(message queue)简称MQ,是一种以"先进先出"的数据结构为基础的消息服务器。
消息:两个系统间要传输的数据
作用:实现消息的传递
原始的数据传递方式:
上述的数据传输方式为同步传输【作为调用方必须等待被调用方执行完毕以后,才可以继续传递消息】,同步传输存在的弊端:传输效率较低。
基于MQ实现消息的传输,如下图所示:
上述的数据的传输方式属于异步传输【作为调用方法不用等待被调用方执行完毕就可以接续传递消息】,数据传输的效率较高。
1.2 消息队列应用场景
首先我们先说一下消息中间件的主要的作用:
[1]系统解耦
[2]流量消锋
[3]数据分发
上面的三点是我们使用消息中间件最主要的目的。
1.2.1 系统解耦
系统的耦合性越高,容错性 【是指系统在部分组件(一个或多个)发生故障时仍能正常运作的能力】就越低。以电商应用为例,用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障或者因为升级等原因暂时不可用,都会造成下单操作异常,影响用户使用体验。
如下下图所示:
使用消息队列以后,整个下单操作的架构如下图所示:
使用消息队列解耦合,系统的耦合性就会降低了,容错性就提高了。比如物流系统发生故障,需要几分钟才能来修复,在这段时间内,物流系统要处理的数据被缓存到消息队列中,用户的下单操作正常完成。当物流系统恢复后,补充处理存在消息队列中的订单消息即可,终端系统感知不到物流系统发生过几分钟故障。
1.2.2 流量消锋
流量消锋:消除系统中的高峰值流量(流量可以理解为就是请求)
现有一个电商系统下单初始架构如下所示:
假设用户每秒需要发送5k个请求,而我们的A系统每秒只能处理2K个请求,这样就会导致大量的下单请求失败。而且由于实际请求的数量远远超过系统的处理能力,此时也有可能导致系统宕机。
使用消息队列改进以后的架构如下所示:
用户每秒发送5k个请求,我们可以先将下单请求数据存储到MQ中,此时在MQ中就缓存了很多的下单请求数据,而A系统根据自己的处理能力从MQ中获取数据进行下单操作,有了MQ的缓存层以后,就可以保证每一个用户的下单请求可以得到正常的处理,并且这样可以大大提高系统的稳定性和用户体验。
1.2.3 异步通信
假设A系统进行了某一个业务操作以后,需要将这个业务操作结果通知给其他的系统,原始的架构如下所示:
此时B系统、C系统、D系统就需要提供对应的接口,然后让A系统进行调用。如果此时不需要通知D系统了,那么就需要更改A系统的代码,将调用D系统的代码删除掉。并且如此时项目中添加了一个新的系统E,A系统也需要将处理结果通知给E系统,那么同时也需要更改A系统的代码。这样就不利于后期的维护。
使用MQ改进以后的架构如下所示:
A系统需要将业务操作结果通知给其他的系统时,A系统只需要将结果发送到MQ中。其他的系统只需要从MQ中获取结果即可,如果不需要结果了,此时只需要取消从MQ中获取结果的操作即可。并且如果新增了一个系统需要获取结果,只需要从MQ中获取结果数据就可以了,A系统的代码不需要进行改动。这样就大大的提高了系统的维护性。
1.3 MQ的优缺点
优点:
1、应用解耦提高了系统的容错性
2、流量消锋提高了系统的并发能力
3、异步通信提高了系统的可维护性
缺点:
1、系统可用性降低:系统引入的外部依赖越多,系统稳定性越差。一旦MQ宕机,就会对业务造成影响。
2、系统复杂度提高:MQ的加入大大增加了系统的复杂度。
MQ的选择依据是什么? 调用方是否需要获取到被调用方的执行结果,如果需要获取到结果,那么就需要使用同步通信,如果不需要就可以使用异步通信。
1.4 Kafka简介
Kafka是Apache开源的一款基于zookeeper协调的分布式消息系统,具有高吞吐率、高性能、实时、高可靠等特点,可实时处理流式数据。它最初由LinkedIn公司开发,使用Scala语言编写。
Kafka历经数年的发展,从最初纯粹的消息引擎,到近几年开始在流处理平台生态圈发力,多个组织或公司发布了各种不同特性的产品。
常见产品如下:
1、Apache Kafka :最"正统"的Kafka也是开源版,它是后面其他所有发行版的基础。
2、Cloudera/Hortonworks Kafka: 集成了目前主流的大数据框架,能够帮助用户实现从分布式存储、集群调度、流处理到机器学习、实时数据库等全方位的数据理。
3、Confluent Kafka :主要提供基于Kafka的企业级流处理解决方案。
Apache Kafka,它现在依然是开发人数最多、版本迭代速度最快的Kafka,我们使用此产品学习。我们使用版本kafka_2.13-2.8.1
官网地址:Apache Kafka
下载:Apache Kafka
1.5 MQ产品比对
市面上常见的消息队列产品:
1、ActiveMQ
2、RabbitMQ
3、RocketMQ
4、Kafka
常见特性比对:
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
开发语言 | java | erlang | java | scala |
单机吞吐量 | 万级 | 万级 | 10万级 | 100万级 |
时效性 | ms | us | ms | ms级以内 |
可用性 | 高(主从) | 高(主从) | 非常高(分布式) | 非常高(分布式) |
功能特性 | 成熟的产品、较全的文档、各种协议支持好 | 并发能力强、性能好、延迟低,社区活跃度高,数据量没有那么大,优先选择功能比较完备的RabbitMQ | MQ功能比较完善,扩展性佳,可靠性要求高的金融互联网领域使用多,稳定性高,经历了多次阿里双11考验 | 只支持主要的MQ功能,大数据领域使用多,追求高吞吐量,适合产生大量数据的互联网服务的数据收集业务 |
2、 Kafka环境搭建
准备linux基础环境:只用安装docker+关闭了防火墙
1.将需要的镜像文件放到Linux中的/opt目录下:
2.然后把tar文件恢复成为镜像
docker load -i zookeeper.tar
docker load -i kafka.tar
docker load -i efak.tar
docker images
3.把docker-compose-kafka.yml也上传到Linux中的/opt目录下:
- 主要修改这个文件里面的地址,详情请看最末尾的补充章节,再修改
4.在 docker-compose文件所在的目录中运行以下命令:
docker compose -f docker-compose-kafka.yml up -d
#重启
docker compose -f docker-compose-kafka.yml restart
5.验证服务是否运行
docker ps
6.访问efak主页:虚拟机ip:8084:
-
账号:admin;密码:123456
2.1 Kafka集群机制
集群机制说明:
1、Kafka是天然支持集群的,哪怕是一个节点实际上也是集群模式
2、Kafka集群依赖于zookeeper进行协调,并且在早期的Kafka版本中很多数据都是存放在Zookeeper中的
3、Kafka节点只要注册到同一个Zookeeper上就代表它们是同一个集群的
4、Kafka通过brokerId(kafka节点的id) 来区分集群中的不同节点
2.2 Kafka基础架构
Kafka的核心角色介绍:
角色名称 | 具体含义 |
---|---|
Broker | Broker是一个kafka实例,简单说就是一台kafka服务器,kafkaCluster表示集群。 |
Topic | 主题 ,Kafka将消息进行分类,每一类的消息称之为一个主题。 |
Producer | 生产者,向Broker topic 发布消息的客户端。 |
Consumer | 消费者,从Broker topic 订阅消息的客户端。 |
Partition | Topic的分区,每个 Topic 可以有多个分区,同一个Topic中,不同分区的数据是不重复的 。每个partition都由一系列有序的、不可变的消息组成,这些消息被连续的追加到partition中。分区作用是做负载,提高 kafka 的吞吐量。 |
Replication | Partition(分区)的副本。每个分区可以有多个Replication,由一个Leader和若干个Follower组成 。Leader负责接收生产者push的消息和消费者poll消费消息。Follower会实时从自己的Leader中同步数据保持同步。Leader故障时,某个Follower会上位为新的Leader。分区副本的作用是保证高可用。 |
ConsumerGroup | 同一个消费者组中的多个消费者分摊 一个topic中的消息,不同消费者组中的多个消费者共同消费一个topic中的相同消息 |
In-sync Replicas(ISR) | (ISR)已同步副本:表示存活且副本都已和Leader同步的的broker集合,是Leader所有replicas副本的子集。如果某个副本节点宕机,该副本就会从ISR集合中剔除。 |
ZooKeeper | Kafka使用ZooKeeper来进行集群管理、协调和元数据存储。Kafka中的Broker、Topic、Consumer都会注册到zookeeper。 |
2.3 Kafka集群搭建
为了测试方便,我们选择搭建伪分布式Kafka集群,在同一台虚拟机上启动一个zookeeper实例 ,三个Kafka实例 。并且使用docker compose进行搭建,具体的docker compose文件的内容参考课程资料: docker-compose-kafka.yml
访问EFAK账号和密码: admin/123456
这部分其实就是第2章最开始的环境搭建
2.4 Kafka集群测试
进入任意一个容器:
docker exec -it kafka1 /bin/bash
topic操作相关命令:
java
# 创建主题
kafka-topics.sh --create --topic hellokafka-topic --bootstrap-server 192.168.80.129:9092
# 创建主题,在这个kafka容器中也可以创建其他kafka集群的主题
kafka-topics.sh --create --topic hellokafka-topic1 --bootstrap-server 192.168.80.129:9093
# 创建3个分区,3个副本的kafka
kafka-topics.sh --create --topic myclustertopic --partitions 3 --replication-factor 3 --bootstrap-server 192.168.80.129:9092
# 查看系统中所有kafka集群的topic,可以尝试从其他的节点上进操作
kafka-topics.sh --list --bootstrap-server 192.168.80.129:9092
# 查看某个topic的详情信息
kafka-topics.sh --describe --topic hellokafka-topic --bootstrap-server 192.168.80.129:9092
上述查询信息的说明:
Topic: 主题名称
TopicId: 主题id
PartitionCount:分区个数
ReplicationFactor:复制副本数
Configs: 主题配置信息
Partition:分区的id(分区编号)
Leader:主分区所在的Kafka服务器的id,第0号分区在第2台kafka主机上
Replicas:副本分区所在的kafka服务器的id
Isr:已同步副本所在的节点id
发送以及接收消息:
# 使用生产者脚本发送消息
kafka-console-producer.sh --topic hellokafka-topic --bootstrap-server 192.168.100.102:9092
# 使用生产者脚本接收消息
# 从当前位置开始消费
kafka-console-consumer.sh --topic hellokafka-topic --bootstrap-server 192.168.100.102:9092
# --from-beginning: 表示从最开始的位置进行消费消费
kafka-console-consumer.sh --topic hellokafka-topic --from-beginning --bootstrap-server 192.168.100.102:9092
注意:EFAK删除主题的时候获取admin token需要进入到容器中,查看system-config.properties配置文件进行获取。
docker exec -it eagle /bin/bash
cat conf/system-config.properties
得到如下配置:可知token是keadmin
######################################
# delete kafka topic token
# Set to delete the topic token, so that administrators can have the right to delete
######################################
efak.topic.token=keadmin
3、Kafaka的 Java API入门案例
3.1 发送消息
具体步骤如下所示:
1、创建一个kafka-parent父工程,删除src目录,并加入如下依赖:
XML
<!-- 父工程 , JDK选择17 -->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>3.0.5</version>
</parent>
<!--kafka-clients 2023.8:Kafka的Java API依赖-->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
2、在kafka-parent父工程下创建kafka-producer子工程
3、创建启动类
java
package com.atguigu.kafka;
@SpringBootApplication
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class , args) ;
}
}
5、生产者代码实现:
官网示例代码:KafkaProducer (kafka 3.5.2 API)
测试代码:
java
// 可以事先不用创建主题,会自动创建
package com.atguigu.kafka;
@SpringBootTest(classes = ProducerApplication.class) //classes属性;加载启动类的环境
public class ProducerDemo01 {
// 定义主题的名称
public static final String TOPIC_NAME = "hellokafka";
@Test
public void sendMsg() {
//创建Properties对象,配置 Kafka 生产者的各种参数
Properties properties = new Properties() ;
//配置Kafka生产者要连接的 Kafka 集群的地址和端口
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG , "192.168.100.102:9092") ;
//指定了用于序列化消息键的类。在这个例子中,使用的是 StringSerializer,这意味着消息的键将被序列化为字符串格式
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG , "org.apache.kafka.common.serialization.StringSerializer") ;
//指定了用于序列化消息值的类。与键相同,这里也使用 StringSerializer,表示消息的值同样将被序列化为字符串格式。
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG , "org.apache.kafka.common.serialization.StringSerializer") ;
// 传入Properties对象 根据配置 创建Kafka的生产者对象KafkaProducer
KafkaProducer kafkaProducer = new KafkaProducer(properties) ;
// 调用send方法发送消息
for (int i = 0; i < 10; i++) {
//创建生产者消息对象,指定向哪个主题发送什么消息
ProducerRecord producerRecord = new ProducerRecord(TOPIC_NAME , "helloKafka~" + i) ;
// 调用KafkaProducer对象的send方法发送消息
kafkaProducer.send(producerRecord) ;
}
// 关闭资源
kafkaProducer.close();
}
}
消息发送完毕以后可以通过eagle系统查看主题消息。
3.2 消费消息
具体步骤如下所示:
1、在kafka-parent父工程下创建kafka-consumer子工程
2、创建启动类
java
package com.atguigu.kafka;
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class , args) ;
}
}
3、消费者代码实现:
官网示例代码:KafkaConsumer (kafka 3.5.2 API)
测试代码:
java
package com.atguigu.kafka;
@SpringBootTest(classes = ConsumerApplication.class) //classes属性;加载启动类的环境
public class ConsumerDemo01 {
// 定义主题的名字
public static final String TOPIC_NAME = "hellokafka";
@Test
public void consumerMsg() {
// 创建属性对象,配置 Kafka 消费者的各种参数
Properties properties = new Properties() ;
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG , "192.168.100.102:9092") ;
properties.put(ConsumerConfig.GROUP_ID_CONFIG , "group01") ; //指定消费者组id
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG , "org.apache.kafka.common.serialization.StringDeserializer") ;
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG , "org.apache.kafka.common.serialization.StringDeserializer") ;
// earliest:最开始第一次向一个topic发消息时,如果没有消费位移,那么此时从最早(最小)偏移量开始读取消息
// latest:最开始第一次向一个topic发消息时,如果没有消费位移,那么此时从最新(最大)偏移量开始读取消息
// 查看消费位移的命令如下:
// kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group group01 --describe
//生产者已经发了些消息了,然后消费者才开始监听,加这个属性后,就会从消息队列的头部开始消费;写laters则只会监听现在发来的消息,以前发来的不监听
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG , "earliest") ;
//入Properties对象 根据配置 创建Kafka的消费者对象KafkaConsumer
KafkaConsumer kafkaConsumer = new KafkaConsumer(properties) ;
// 调用KafkaConsumer对象的subscribe方法,让消费者订阅某些主题
kafkaConsumer.subscribe(Arrays.asList(TOPIC_NAME));
while (true) {
//调用kafkaConsumer对象的poll方法从主题中拉取一批消息,kafka发生消息是一批一批的发
ConsumerRecords<String , String> consumerRecords = kafkaConsumer.poll(Duration.ofMillis(100)); // 参数表示拉取消息的时间间隔
// 消费消息
for (ConsumerRecord<String , String> record : consumerRecords) {
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value()); // offset表示消费位移
}
}
}
}
4、 在Spring Boot中集成Kafka
4.1 生产者
4.1.1 环境搭建
具体步骤如下所示:
1、创建一个spring-kafka父工程,并添加如下依赖
XML
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>3.0.5</version>
</parent>
<dependencies>
<!-- web开发起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- spring boot和junit整合时候的起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--spring-kafka-->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.19</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
2、在spring-kafka父工程下创建spring-kafka-producer子工程
3、创建启动类
java
package com.atguigu.kafka;
@SpringBootApplication
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class , args) ;
}
}
4、在spring-kafka-producer子工程中添加application.yml配置文件,并配置生产者如下:
java
# application.yml
server:
port: 8081
# 生产者配置
spring:
kafka:
bootstrap-servers: 192.168.100.102:9092,192.168.100.102:9093,192.168.100.102:9094 #配置集群中的所有节点
producer:
acks: -1
retries: 0
batch-size: 16384 # 批次大小 单位byte,攒到多少byte就把消息批量发送出去
buffer-memory: 33554432
compression-type: gzip
key-serializer: org.apache.kafka.common.serialization.StringSerializer # key的序列化器
value-serializer: org.apache.kafka.common.serialization.StringSerializer # value的序列化器
properties: {
'linger.ms': 10 #最多10ms就会把消息批量发出去
} # 配置其他的属性
5、创建主题的配置类
java
package com.atguigu.kafka.config;
@Configuration
public class KafkaConfig {
@Bean
public NewTopic springTestTopic(){
return TopicBuilder.name("topic-01") //创建主题:启动程序后,这个主题就会自动创建在kafka的服务器中
.partitions(3) //主题的分区数量
.replicas(3) //主题的副本数量
.build();
}
}
6、启动 主启动类
7、启动程序通过eagle查看主题创建情况
4.1.2 发送消息
创建测试类通过KafkaTemplate发送消息,代码如下所示:
java
package com.atguigu.kafka.test
@SpringBootTest(classes = ProducerApplication.class)
public class ProducerApplicationTest {
@Autowired
private KafkaTemplate kafkaTemplate ;
@Test
public void send() {
//向哪个主题发送什么消息
kafkaTemplate.send("topic-01" , "kafka...producer...send...message...") ;
}
}
执行测试代码,通过Eagle控制台可以查看到消息发送成功。
上图中的说明:
- Partition:代表分区的编号,这个topic中有三个分区,分别是0,1,2三个分区
- LogSize:代表此分区中的消息数量
- Leader:代表某个分区在哪台kafka服务器上。以第一行为例,表示2号分区在编号为2的kafka服务器上
- Replicas:某个分区的副本在哪些kafka服务器上(主分区也是算成一个副本)
offset偏移量说明,如下图所示查询topic中某个分区里的消息:
- offset偏移量是什么?
- offset 是 partition 中每条消息的唯一标识,从0开始,每当有新的消息写入分区时,offset 就会加 1。offset 是不可变的,即使消息被删除或过期,offset 也不会改变或重用。
- 消费者在消费 Kafka 消息时,维护了一个当前消费的 offset 值,以及一个已提交的 offset 值。
- 当前消费的 offset 值:表示消费者消费消息的进度;
- 已提交的 offset 值:表示消费者确认消费过的消息的位置。
- 消费者在消费完一条消息后,需要提交 offset 来更新已提交的 offset 值,否则会导致消息重复消费。提交 offset 的方式有两种:自动提交和手动提交。
- 更多offset资料可以参考这篇文章:kafka offset
4.1.3 发送方式
生产者发送消息存在三种方式:
1、不关心发送结果
把消息发送给服务器,但不关心它是否正常到达。这种发送消息的方式是kafka吞吐量 最高的一种方式,生产者发送消息后,不需要等待服务器的响应。但是,此种发送消息的方式也是最不可靠 的一种方式,因为对于发送失败的消息没有做任何处理。
java
// 入门案例的消息发送方式就是该方式
kafkaTemplate.send("topic-01" , "kafka...producer...send...message...") ;
特点:性能最好,可靠性最差。
2、同步消息发送
使用send()方法发送消息,它会返回一个CompletableFuture对象,再调用其get方法,**get方法会一直阻塞到该线程的任务得到返回值,也就是broker返回发送成功。**如果业务上关心发送结果,那么可以使用同步发送的方式。
java
// 实现通过消息发送就需要在调用完send方法以后,再次调用get方法
kafkaTemplate.send("topic-01" , "kafka...producer...send...message...").get() ;
特点:性能最差,可靠性较好。
3、异步消息发送
调用send()方法,并指定一个回调函数,服务器在返回响应时调用该回调函数。
如果业务上关心发送结果,且需要异步发送,那么可以用异步+回调的方式来发送消息。
注意:由于是异步发送消息,测试的时候可以让线程休眠一会儿以等待回调函数的执行
java
// 要实现异步消息发送就需要定义生产者监听器,在发送完毕以后就会根据具体的发送结果调用对应的函数,如下所示:
package com.atguigu.kafka.listener;
@Component
@Slf4j
public class KafkaSendResultHandler implements ProducerListener {
/**
* Kafka发送成功回调
* @param producerRecord
* @param recordMetadata
*/
@Override
public void onSuccess(ProducerRecord producerRecord, RecordMetadata recordMetadata) {
String topic = producerRecord.topic();
String value = producerRecord.value().toString();
Integer partition = recordMetadata.partition();
log.info("topic:{},value:{},partition:{}, 发送成功回调",topic,value, partition);
}
@Override
public void onError(ProducerRecord producerRecord, RecordMetadata recordMetadata, Exception exception) {
String topic = producerRecord.topic();
String value = producerRecord.value().toString();
log.info("topic:{},value:{}, 发送失败,原因:{}",topic,value,exception.getMessage());
}
}
4.1.4 拦截器配置
4.1.6 分区
4.1.7 生产者常见属性
生产者还有很多可配置的参数,在 Kafka文档里都有说明,它们大部分都有合理的默认 值,所以没有必要去修改它们 。不过有几个参数在内存使用、性能和可靠性方面对生产者影响比较大,接下来我们会一一说明。
官网地址:Apache Kafka
batch.size
作用:攒一批消息,然后到达指定的内存大小后就会一起发送出去。按照字节数计算,默认值为16384byte(16K)
linger.ms
作用:该参数指定了生产者在发送批次之前等待更多消息加入批次的时间 。KafkaProduce会在批次填满或linger.ms达到上限时把批次发送出去。
默认值为0:意思就是消息必须立即被发送,但这样会影响性能,一般设置10毫秒左右,就是说这个消息发送完后会进入本地的一个batch,如果10毫秒内,这个batch满了16kb就会随batch一起被发送出去。如果10毫秒内,batch没满,那么也必须把消息发送出去,不能让消息的发送延迟时间太长!
buffer.memory
设置发送消息的本地缓冲区,消息会先发送到本地缓冲区,可以提高消息发送性能,默认值是33554432,即32MB
compression.type
:将消息采用特定的压缩算法进行压缩并存储,待消费时再解压。提高了消息的传输效率并且降低了存储压力。
Kafka中提供了四种压缩算法,对比如下所示:CPU资源充足,带宽资源有限时可以考虑使用压缩算法压缩消息。
压缩类型 | 压缩比率 | CPU 使用率 | 压缩速度 | 带宽使用率 |
---|---|---|---|---|
gzip | 高 | 高 | 慢 | 低 |
snappy | 一般 | 一般 | 一般 | 一般 |
lz4 | 低 | 低 | 快 | 高 |
zstd | 一般 | 一般 | 一般 | 一般 |
acks属性
acks保证生产者将消息可靠的发送到达broker。
常见取值说明:
1、acks=0:生产者发送消息后,就不管了,可靠性差数据会丢,效率最高
2、acks=1:生产者发送消息后,只需要Leader确认即可返回,可靠性中等,效率中等
3、acks=-1(all):生产者发送消息后,需要Leader和ISR队列里面所有Follwer应答,只要其中有一个没有应答就返回消息发送失败,可靠性高效率最低
在生产环境中选择:
1、acks=0,很少使用
2、acks=1,一般用于传输普通日志,允许丢个别数据;
3、aks=-1(all),一般用于传输重要不能丢失的数据(例如:钱、订单、积分等),对可靠性要求比较高的场景。
retries属性
kafka是一种分布式消息系统,常用于大规模数据的收集和分发。在生产者发送消息到kafka集群的过程中,由于多种原因(网络故障、消息格式问题等),可能会发生消息发送失败。为了提高消息传输的可靠性,kafka提供了一种重试机制,即当消息发送失败时,会自动尝试重新发送直到消息成功被写入kafka。作用:消息发送失败后,指定重新发送几次
4.2 消费者
4.2.1 环境搭建
具体步骤如下所示:
1、在spring-kafka父工程下创建spring-kafka-consumer子工程
2、创建启动类
java
package com.atguigu.kafka;
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class , args) ;
}
}
3、创建主题配置类(如果服务器中已经存在topic-01,那么消费者端可以不配置这个Bean)
java
package com.atguigu.kafka.config;
@Configuration
public class KafkaConfig {
@Bean
public NewTopic springTestTopic(){
return TopicBuilder.name("topic-01") //主题名称, 该主题不存在直接创建,如果存在就复用
.partitions(3) //分区数量
.replicas(3) //副本数量
.build();
}
}
4、在application.yml文件中添加如下配置,对消费者进行配置:
java
server:
port: 8120
# 消费者配置
spring:
Kafka:
bootstrap-servers: 192.168.100.102:9092,192.168.100.102:9093,192.168.100.102:9094
consumer: # consumer消费者配置
group-id: group03 # 默认的消费组ID
enable-auto-commit: true # 消费者是否进行自动offset提交
auto-commit-interval: 5000 # 消费者自动提交offset时间间隔为5s。这期间如果kafka服务异常停止时,再次重启消费者服务会导致消息重复消费(enable-auto-commit改成false后这个属性会失效)
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer #配置反序列化器,和传统Java API配置反序列化器是一样的
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
4.2.2 消费消息
java
package com.atguigu.kafka.listener;
@Component
public class KafkaListeners {
/**
* 简单消费:
*/
@KafkaListener(topics = {"topic-01"}) topics属性:指定此消费者要监听的主题列表
//ConsumerRecord<String, String> record: 记录对象,封装消息的相关数据,加上两个String泛型那么record.value拿到的消息内容就是String类型,否则就是Object
public void simpleConsumer(ConsumerRecord<String, String> record ) {
System.out.println("进入simpleConsumer方法");
System.out.printf(
"分区 = %d, 偏移量 = %d, key = %s, 消息的内容 = %s, 时间戳 = %d%n",
record.partition(),
record.offset(),
record.key(),
record.value(), //消息发送的内容
record.timestamp()
);
}
}
最后启动生产者和消费者服务
4.2.3 消费者的手动位移提交
修改消费者的yml文件配置:
java
server:
port: 8120
# 消费者配置
spring:
Kafka:
bootstrap-servers: 192.168.100.102:9092,192.168.100.102:9093,192.168.100.102:9094
listener:
ack-mode: manual_immediate #手动(设置消费者的手动位移提交,添加这个!!!!!)
consumer: # consumer消费者配置
group-id: group03 # 默认的消费组ID
enable-auto-commit: false # 关闭offset自动提交(设置消费者的手动位移提交,这里修改改成false!!!!)
auto-commit-interval: 5000 # 消费者自动提交offset时间间隔为5s。这期间如果kafka服务异常停止时,再次重启消费者服务会导致消息重复消费(enable-auto-commit改成false后这个属性会失效)
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
修改消费者接收消息的代码:
java
package com.atguigu.kafka.listener;
@Component
public class KafkaListeners {
/**
* 简单消费:
* topics:要监听的主题列表
* ConsumerRecord<String, String> record: 记录对象,封装消息记录的相关数据
*/
@KafkaListener(topics = {"topic-01"})
public void simpleConsumer(ConsumerRecord<String, String> record,Consumer consumer) {
System.out.println("进入simpleConsumer方法");
System.out.printf(
"分区 = %d, 偏移量 = %d, key = %s, 内容 = %s, 时间戳 = %d%n",
record.partition(),
record.offset(),
record.key(),
record.value(), //消息发送的内容
record.timestamp()
);
//消费者消息消费完毕以后,进行手动位移提交
consumer.commitAsync();
}
}
5、生产者测试
java
package com.atguigu.kafka.test
@SpringBootTest(classes = ProducerApplication.class)
public class ProducerApplicationTest {
@Autowired
private KafkaTemplate kafkaTemplate ;
@Test
public void send() {
for(int i = 0;i < 5;i++){
kafkaTemplate.send("topic-01" , "kafka...producer...send...message..." + i) ;
}
}
}
4.2.4 消费时的异常处理
消费者 消费 消息时,如果发生了异常需要处理异常。一般我们在@KafkaListener中,只是监听topic中的主题并消费,如果再try catch捕获并处理的话,则会显得代码块非常臃肿不利于维护。
为此,kafka为我们提供了专门的异常处理器ConsumerAwareListenerErrorHandler ,通过它我们可以处理consumer在消费时发生的异常。
具体使用步骤如下所示:
1、定义配置类,并在其中声明ConsumerAwareListenerErrorHandler这个Bean即可。
java
package com.atguigu.kafka.config;
@Configuration
public class CustomListenerErrorHandler {
@Bean
public ConsumerAwareListenerErrorHandler listenerErrorHandler(){
return (Message<?> message, ListenerExecutionFailedException exception, Consumer<?, ?> consumer) -> {
System.out.println("--- 消费时发生异常 ---");
return null;
} ;
}
}
2、在消费者中,使用@KafkaListener(topics = {"topic-01"})注解的errorHandler属性指定使用的异常处理器(异常处理器的id)
- 在消费者中,在提交offset之前发生异常时,就会跳转到消费者的异常处理器中执行
5.Kafka的高级内容
补充:docker-compose文件说明
docker-compose.yml
文件定义了一个包含 Zookeeper 和三个 Kafka 实例以及一个管理工具 (Kafka Eagle) 的多服务 Docker Compose 配置。以下是对每个部分的逐行解释:
文件版本
version: '3'
- version: '3':指定使用 Docker Compose 文件格式的版本 3。这个版本支持多种功能,适用于定义多服务应用。
服务定义
Zookeeper 服务
services:
zookeeper:
image: wurstmeister/zookeeper
container_name: zookeeper
ports:
- 2181:2181
volumes:
- "zookeeper-data:/data"
- "zookeeper-datalog:/datalog"
- "zookeeper-logs:/logs"
restart: always
-
services:定义 Docker Compose 文件中的服务。
-
zookeeper:服务名称,包含 Zookeeper ,用于管理 Kafka 集群的元数据。
-
image: wurstmeister/zookeeper :使用
wurstmeister/zookeeper
镜像。 -
container_name: zookeeper :容器命名为
zookeeper
。 -
ports:
2181:2181
:将主机 2181 端口映射到容器 2181 端口。
-
volumes:
-
"zookeeper-data:/data"
:挂载名为zookeeper-data
的卷到容器内的/data
目录。 -
"zookeeper-datalog:/datalog"
:挂载名为zookeeper-datalog
的卷到容器内的/datalog
目录。 -
"zookeeper-logs:/logs"
:挂载名为zookeeper-logs
的卷到容器内的/logs
目录。
-
-
restart: always:docker重启后容器总是重启。
-
networks:
cluster_net
:将服务连接到名为cluster_net
的网络。
Kafka 实例
kafka1:
image: wurstmeister/kafka
depends_on:
- zookeeper
container_name: kafka1
ports:
- "9092:9092"
environment:
- "KAFKA_BROKER_ID=1"
- "KAFKA_ZOOKEEPER_CONNECT=192.168.100.102:2181"
- "KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.100.102:9092"
- "KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092"
- "KAFKA_LOG_DIRS=/data/kafka-data"
volumes:
- "kafka1-data:/data/kafka-data"
- "kafka1-config:/opt/kafka/config"
restart: always
-
kafka1:第一个 Kafka 服务实例。
-
depends_on:
zookeeper
:表示该服务依赖于zookeeper
服务。
-
container_name: kafka1 :容器命名为
kafka1
。 -
ports:
"9092:9092"
:将主机 9092 端口映射到容器 9092 端口。
-
environment:
-
"KAFKA_BROKER_ID=1"
:Kafka broker 的 ID 为 1。 -
"KAFKA_ZOOKEEPER_CONNECT=192.168.100.102:2181"
:指定 Zookeeper 的连接地址和端口。 -
"KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.100.102:9092"
:客户端链接 Kafka 的地址。 -
"KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092"
:表示 Kafka broker 在所有网络接口的 9092 端口上监听连接,用于Kafka 集群的内部通信。 -
"KAFKA_LOG_DIRS=/data/kafka-data"
:Kafka 日志目录为/data/kafka-data
。
-
-
volumes:
-
"kafka1-data:/data/kafka-data"
:挂载名为kafka1-data
的卷到容器内的/data/kafka-data
目录。 -
"kafka1-config:/opt/kafka/config"
:挂载名为kafka1-config
的卷到容器内的/opt/kafka/config
目录。
-
-
restart: always:docker重启后容器总是重启。
Kafka Eagle 服务
eagle:
container_name: eagle
image: nickzurich/efak:latest
restart: always
ports:
- "8048:8048"
environment:
- "EFAK_CLUSTER_ZK_LIST=192.168.100.102:2181"
volumes:
- "kafka-eagle:/hadoop/kafka-eagle/db"
-
eagle:Kafka Eagle 服务,一个用于 Kafka 集群管理的工具。
-
container_name: eagle :容器命名为
eagle
。 -
image: nickzurich/efak:latest :使用
nickzurich/efak:latest
镜像。 -
restart: always:docker重启后容器总是重启。
-
ports:
"8048:8048"
:将主机 8048 端口映射到容器 8048 端口。
-
environment:
"EFAK_CLUSTER_ZK_LIST=192.168.100.102:2181"
:配置 Kafka Eagle 连接到 Zookeeper 的地址。
-
volumes:
"kafka-eagle:/hadoop/kafka-eagle/db"
:挂载名为kafka-eagle
的卷到容器内的/hadoop/kafka-eagle/db
目录。
数据卷的定义
volumes:
kafka1-data: {}
kafka2-data: {}
kafka3-data: {}
zookeeper-data: {}
zookeeper-datalog: {}
zookeeper-logs: {}
kafka-eagle: {}
kafka1-config: {}
kafka2-config: {}
kafka3-config: {}
-
volumes:定义容器会使用的命名卷。
-
kafka1-data 、kafka2-data 、kafka3-data 、zookeeper-data 、zookeeper-datalog 、zookeeper-logs 、kafka-eagle 、kafka1-config 、kafka2-config 、kafka3-config:各个自定义卷用于持久化存储 Kafka 和 Zookeeper 的数据和配置。