文章目录
1. 介绍
Kafka作为一款分布式流处理平台,具有高吞吐量、持久性、容错性等特点,适用于构建大规模的实时数据管道。
Spring Boot作为快速开发框架,提供了简化开发和部署的能力,使得与Kafka的整合变得更加容易。
通过将Spring Boot与Kafka进行整合,具有以下优势
异步消息处理
:Kafka可以作为消息队列,实现异步消息的生产和消费,提高系统的吞吐量和响应速度。解耦和扩展性
:消息队列的引入可以解耦不同系统或模块之间的依赖关系,提高系统的灵活性和可维护性。数据流处理
:Kafka支持流处理,可以用于实时数据分析、日志收集、事件驱动等场景,为业务提供更多的数据洞察。可靠性和容错性
:Kafka具有高度可靠性和容错性,能够保证消息不丢失,并且支持分布式部署,保证系统的稳定性和可靠性。Spring Boot与Kafka整合为开发者提供了一种强大的消息传递解决方案,能够满足现代分布式系统对于消息传递的需求,提高系统的性能、可维护性和可扩展性。
2. Kafka基础
2.1. 安装KafKa
可参考本篇博客
kafka集群搭建_kafka交流群-CSDN博客
Kafka的一些特点
- 发布订阅模型:- 生产者将消息发布到主题,消费者订阅这些主题以接收消息。- 每个主题可以有多个订阅者,消息会广播给所有订阅者。
- 消息日志:- Kafka将消息存储在持久化的日志中,每个消息都有一个唯一的偏移量。- 日志被分割成多个分区,每个分区中的消息有顺序的索引。- 分区允许Kafka在集群中并行处理和存储消息,提高了吞吐量和扩展性。
- 分布式架构:- Kafka是一个分布式系统,集群由多个Broker组成。- 每个分区有副本分布在不同的Broker上,确保消息的可靠性和容错性。- 分布式架构支持水平扩展,能够处理大规模的数据和高并发的请求。
- 高吞吐量:- Kafka被设计为高吞吐量的消息系统,能够处理每秒数百万条消息。- 高效的批量处理和零拷贝机制使得Kafka能够提供低延迟的消息传递服务。
- 持久性:- Kafka的消息是持久化存储的,可以通过配置持久化策略来保留消息的时间和大小。- 消息一旦被写入到Kafka中就不会丢失,即使消费者尚未处理。
- 水平扩展:- Kafka集群可以水平扩展,通过增加Broker和分区来提高系统的容量和吞吐量。- 新的Broker和分区可以动态地加入到集群中,而不会中断服务。
- 可靠性和容错性:- Kafka通过副本机制和ISR(In-Sync Replicas)机制实现高可靠性和容错性。- ISR机制确保了即使部分Broker失效,也能继续保持数据的一致性和可用性。
- 流处理:- Kafka Streams API提供了流处理的能力,允许开发者在Kafka中进行实时数据处理和分析。- 流处理功能使得Kafka能够更灵活地处理实时数据流和生成实时结果。
3. Spring Boot整合Kafka
3.1. 引入Kafka依赖
<dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId><version>2.9.13</version></dependency>
gradle:
implementation 'org.springframework.kafka:spring-kafka:2.9.13'
3.2.编写配置文件
# 配置环境的 指定topic 如果有多个 可以使用 ,进行连接dc:topics:info: ENTRY_USER_INFO
# 指定 组idgroup-id: hrfan-consumer-group
spring:kafka:consumer:bootstrap-servers: 192.168.112.128:9092group-id: hrfan-consumer-group
auto-offset-reset: earliest
# 错误处理key-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
value-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
properties:spring.json.trusted.packages:'*'# 序列化、反序列化一致spring.deserializer.key.delegate.class: org.apache.kafka.common.serialization.StringDeserializer
# 注意 这里需要使用 org.apache.kafka.common.serialization.StringDeserializer# 不能使用 org.springframework.kafka.support.serializer.JsonSerializer 不然会报错 需要保持一致spring.deserializer.value.delegate.class: org.apache.kafka.common.serialization.StringDeserializer
producer:bootstrap-servers: 192.168.112.128:9092key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
4. 生产者(produced)
4.1. 生产者基础案例(基础测试)
/**
* 测试向Kafka发送消息
*
* @author 13723
* @version 1.0
* 2024/3/1 10:35
*/@SpringBootTestpublicclassKafkaProducedTest{privatestaticfinalLogger logger =LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());@Value("${dc.topics.dutyform}")privateString topics;/**
* kafka模板 String消息
*/@ResourceprivateKafkaTemplate<String,String> kafkaTemplate;/**
* kafka模板 Message消息
*/@ResourceprivateKafkaTemplate<String,Message<String>> kafkaTemplateMessage;@Test@DisplayName("测试向KfaKa发送消息")publicvoidtestSend(){// 发送普通消息
kafkaTemplate.send(topics,"测试发送普通消息-无Key");// 发送Key-Value消息
kafkaTemplate.send(topics,"hrfan-key-1","测试发送key-Value消息");// 发送Partition(分区)-Offset(偏移量)消息// 在Apache Kafka中,消息被组织在称为"主题(Topics)"的逻辑类别中。// 每个主题可以被划分为一个或多个"分区(Partitions)"。分区是消息的物理存储单元,它们分布在不同的Kafka服务器上。// 每个分区中的消息都有一个唯一的编号,称为"偏移量(Offset)"。这个偏移量标识了消息在该分区中的位置。偏移量是一个递增的整数,新消息的偏移量比旧消息的偏移量大。
kafkaTemplate.send(topics,0,"hrfan-key-2","测试发送Partition-Offset消息");// 可以理解为拼装JSON类型数据// 发送Message消息// 通过sendDefault()方法发送消息,消息将会被发送到默认的主题中。String event ="测试发送Message消息";Map<String,Object> map =newHashMap<>();
map.put("token",UUID.randomUUID().toString());MessageHeaders headers =newMessageHeaders(map);Message<String> message =MessageBuilder.createMessage(event, headers);// 设置默认topic
kafkaTemplate.setDefaultTopic(topics);// 将消息发送到默认的topic// 注意此时修改 泛型为// @Resource// private KafkaTemplate<String,Message<String>> kafkaTemplateMessage;
kafkaTemplateMessage.sendDefault("hrfan-key-3", message);
logger.info("消息发送成功");}}
5. 消费者
5.1.消费者基本案例(基础测试)
/**
* 模拟Kafka消费者
* @author 13723
* @version 1.0
* 2024/2/29 17:01
*/@ComponentpublicclassKafkaCustomerDemo{privatestaticfinalLogger logger =LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());@KafkaListener(topics ="#{'${dc.topics.dutyform}'.split(',')}", groupId ="${dc.group-id}", autoStartup ="true")publicvoidonMessageSync(ConsumerRecord<String,String> record,@HeadersMap<String,Object> headers){
logger.error("获取到的信息为:{},",record);}}
6.Kafka常用配置
kafka配置文件解释
producer.properties解释
producer.properties:生产端的配置文件
#指定kafka节点列表,用于获取metadata,不必全部指定#需要kafka的服务器地址,来获取每一个topic的分片数等元数据信息。metadata.broker.list=kafka01:9092,kafka02:9092,kafka03:9092
#生产者生产的消息被发送到哪个block,需要一个分组策略。#指定分区处理类。默认kafka.producer.DefaultPartitioner,表通过key哈希到对应分区#partitioner.class=kafka.producer.DefaultPartitioner#生产者生产的消息可以通过一定的压缩策略(或者说压缩算法)来压缩。消息被压缩后发送到broker集群,#而broker集群是不会进行解压缩的,broker集群只会把消息发送到消费者集群,然后由消费者来解压缩。#是否压缩,默认0表示不压缩,1表示用gzip压缩,2表示用snappy压缩。#压缩后消息中会有头来指明消息压缩类型,故在消费者端消息解压是透明的无需指定。#文本数据会以1比10或者更高的压缩比进行压缩。compression.codec=none
#指定序列化处理类,消息在网络上传输就需要序列化,它有String、数组等许多种实现。serializer.class=kafka.serializer.DefaultEncoder
#如果要压缩消息,这里指定哪些topic要压缩消息,默认empty,表示不压缩。#如果上面启用了压缩,那么这里就需要设置#compressed.topics= #这是消息的确认机制,默认值是0。在面试中常被问到。#producer有个ack参数,有三个值,分别代表:#(1)不在乎是否写入成功;#(2)写入leader成功;#(3)写入leader和所有副本都成功;#要求非常可靠的话可以牺牲性能设置成最后一种。#为了保证消息不丢失,至少要设置为1,也就#是说至少保证leader将消息保存成功。#设置发送数据是否需要服务端的反馈,有三个值0,1,-1,分别代表3种状态:#0: producer不会等待broker发送ack。生产者只要把消息发送给broker之后,就认为发送成功了,这是第1种情况;#1: 当leader接收到消息之后发送ack。生产者把消息发送到broker之后,并且消息被写入到本地文件,才认为发送成功,这是第二种情况;#-1: 当所有的follower都同步消息成功后发送ack。不仅是主的分区将消息保存成功了,#而且其所有的分区的副本数也都同步好了,才会被认为发动成功,这是第3种情况。request.required.acks=0#broker必须在该时间范围之内给出反馈,否则失败。#在向producer发送ack之前,broker允许等待的最大时间 ,如果超时,#broker将会向producer发送一个error ACK.意味着上一次消息因为某种原因#未能成功(比如follower未能同步成功)request.timeout.ms=10000#生产者将消息发送到broker,有两种方式,一种是同步,表示生产者发送一条,broker就接收一条;#还有一种是异步,表示生产者积累到一批的消息,装到一个池子里面缓存起来,再发送给broker,#这个池子不会无限缓存消息,在下面,它分别有一个时间限制(时间阈值)和一个数量限制(数量阈值)的参数供我们来设置。#一般我们会选择异步。#同步还是异步发送消息,默认“sync”表同步,"async"表异步。异步可以提高发送吞吐量,#也意味着消息将会在本地buffer中,并适时批量发送,但是也可能导致丢失未发送过去的消息producer.type=sync
#在async模式下,当message被缓存的时间超过此值后,将会批量发送给broker,#默认为5000ms#此值和batch.num.messages协同工作.
queue.buffering.max.ms =5000#异步情况下,缓存中允许存放消息数量的大小。#在async模式下,producer端允许buffer的最大消息量#无论如何,producer都无法尽快的将消息发送给broker,从而导致消息在producer端大量沉积#此时,如果消息的条数达到阀值,将会导致producer端阻塞或者消息被抛弃,默认为10000条消息。queue.buffering.max.messages=20000#如果是异步,指定每次批量发送数据量,默认为200batch.num.messages=500#在生产端的缓冲池中,消息发送出去之后,在没有收到确认之前,该缓冲池中的消息是不能被删除的,#但是生产者一直在生产消息,这个时候缓冲池可能会被撑爆,所以这就需要有一个处理的策略。#有两种处理方式,一种是让生产者先别生产那么快,阻塞一下,等会再生产;另一种是将缓冲池中的消息清空。#当消息在producer端沉积的条数达到"queue.buffering.max.meesages"后阻塞一定时间后,#队列仍然没有enqueue(producer仍然没有发送出任何消息)#此时producer可以继续阻塞或者将消息抛弃,此timeout值用于控制"阻塞"的时间#-1: 不限制阻塞超时时间,让produce一直阻塞,这个时候消息就不会被抛弃#0: 立即清空队列,消息被抛弃queue.enqueue.timeout.ms=-1
#当producer接收到error ACK,或者没有接收到ACK时,允许消息重发的次数#因为broker并没有完整的机制来避免消息重复,所以当网络异常时(比如ACK丢失)#有可能导致broker接收到重复的消息,默认值为3.message.send.max.retries=3#producer刷新topic metada的时间间隔,producer需要知道partition leader#的位置,以及当前topic的情况#因此producer需要一个机制来获取最新的metadata,当producer遇到特定错误时,#将会立即刷新#(比如topic失效,partition丢失,leader失效等),此外也可以通过此参数来配置#额外的刷新机制,默认值600000topic.metadata.refresh.interval.ms=60000
consumer.properties解释
#消费者集群通过连接Zookeeper来找到broker。#zookeeper连接服务器地址zookeeper.connect=zk01:2181,zk02:2181,zk03:2181
#zookeeper的session过期时间,默认5000ms,用于检测消费者是否挂掉zookeeper.session.timeout.ms=5000#当消费者挂掉,其他消费者要等该指定时间才能检查到并且触发重新负载均衡zookeeper.connection.timeout.ms=10000#这是一个时间阈值。#指定多久消费者更新offset到zookeeper中。#注意offset更新时基于time而不是每次获得的消息。#一旦在更新zookeeper发生异常并重启,将可能拿到已拿到过的消息zookeeper.sync.time.ms=2000#指定消费group.id=xxxxx
#这是一个数量阈值,经测试是500条。#当consumer消费一定量的消息之后,将会自动向zookeeper提交offset信息#注意offset信息并不是每消费一次消息就向zk提交#一次,而是现在本地保存(内存),并定期提交,默认为trueauto.commit.enable=true
# 自动更新时间。默认60 * 1000auto.commit.interval.ms=1000# 当前consumer的标识,可以设定,也可以有系统生成,#主要用来跟踪消息消费情况,便于观察conusmer.id=xxx
# 消费者客户端编号,用于区分不同客户端,默认客户端程序自动产生client.id=xxxx
# 最大取多少块缓存到消费者(默认10)queued.max.message.chunks=50# 当有新的consumer加入到group时,将会reblance,此后将会#有partitions的消费端迁移到新 的consumer上,如果一个#consumer获得了某个partition的消费权限,那么它将会向zk#注册 "Partition Owner registry"节点信息,但是有可能#此时旧的consumer尚没有释放此节点, 此值用于控制,#注册节点的重试次数.rebalance.max.retries=5#每拉取一批消息的最大字节数#获取消息的最大尺寸,broker不会像consumer输出大于#此值的消息chunk 每次feth将得到多条消息,此值为总大小,#提升此值,将会消耗更多的consumer端内存fetch.min.bytes=6553600#当消息的尺寸不足时,server阻塞的时间,如果超时,#消息将立即发送给consumer#数据一批一批到达,如果每一批是10条消息,如果某一批还#不到10条,但是超时了,也会立即发送给consumer。fetch.wait.max.ms=5000socket.receive.buffer.bytes=655360# 如果zookeeper没有offset值或offset值超出范围。#那么就给个初始的offset。有smallest、largest、#anything可选,分别表示给当前最小的offset、#当前最大的offset、抛异常。默认largestauto.offset.reset=smallest
# 指定序列化处理类derializer.class=kafka.serializer.DefaultDecoder
server.properties解释
server.properties:服务端的配置文件
#broker的全局唯一编号,不能重复broker.id=0#用来监听链接的端口,producer或consumer将在此端口建立连接port=9092#处理网络请求的线程数量,也就是接收消息的线程数。#接收线程会将接收到的消息放到内存中,然后再从内存中写入磁盘。num.network.threads=3#消息从内存中写入磁盘是时候使用的线程数量。#用来处理磁盘IO的线程数量num.io.threads=8#发送套接字的缓冲区大小socket.send.buffer.bytes=102400#接受套接字的缓冲区大小socket.receive.buffer.bytes=102400#请求套接字的缓冲区大小socket.request.max.bytes=104857600#kafka运行日志存放的路径log.dirs=/export/servers/logs/kafka
#topic在当前broker上的分片个数num.partitions=2#我们知道segment文件默认会被保留7天的时间,超时的话就#会被清理,那么清理这件事情就需要有一些线程来做。这里就是#用来设置恢复和清理data下数据的线程数量num.recovery.threads.per.data.dir=1#segment文件保留的最长时间,默认保留7天(168小时),#超时将被删除,也就是说7天之前的数据将被清理掉。log.retention.hours=168#滚动生成新的segment文件的最大时间log.roll.hours=168#日志文件中每个segment的大小,默认为1Glog.segment.bytes=1073741824#上面的参数设置了每一个segment文件的大小是1G,那么#就需要有一个东西去定期检查segment文件有没有达到1G,#多长时间去检查一次,就需要设置一个周期性检查文件大小#的时间(单位是毫秒)。log.retention.check.interval.ms=300000#日志清理是否打开log.cleaner.enable=true
#broker需要使用zookeeper保存meta数据zookeeper.connect=zk01:2181,zk02:2181,zk03:2181
#zookeeper链接超时时间zookeeper.connection.timeout.ms=6000#上面我们说过接收线程会将接收到的消息放到内存中,然后再从内存#写到磁盘上,那么什么时候将消息从内存中写入磁盘,就有一个#时间限制(时间阈值)和一个数量限制(数量阈值),这里设置的是#数量阈值,下一个参数设置的则是时间阈值。#partion buffer中,消息的条数达到阈值,将触发flush到磁盘。log.flush.interval.messages=10000#消息buffer的时间,达到阈值,将触发将消息从内存flush到磁盘,#单位是毫秒。log.flush.interval.ms=3000#删除topic需要server.properties中设置delete.topic.enable=true否则只是标记删除delete.topic.enable=true
#此处的host.name为本机IP(重要),如果不改,则客户端会抛出:#Producer connection to localhost:9092 unsuccessful 错误!host.name=kafka01
advertised.host.name=192.168.239.128
7.ACK
什么是ACK?
在Apache Kafka中,“Ack” 是 “Acknowledgement” 的缩写,用于表示生产者(producer)发送消息给 Kafka 服务器后,服务器返回的确认信息。
Kafka
提供了
三种ACK级别
:
acks=0
:生产者发送消息后,不等待任何确认,直接发送下一条消息。acks=1
:生产者发送消息后,等待leader
节点成功写入消息后返回确认,然后发送下一条消息。acks=all
:生产者发送消息后,等待所有的follower
节点和leader
节点都成功写入消息后返回确认,然后发送下一条消息。acks=all 是最安全的设置,但是也会导致最慢的性能,因为要等待多个副本的确认。
生产者(produced)
发送消息到leader
,leader
收到消息会发送ACK
-leader
负责处理读写操作 - 如果leader
出现故障,会从follwer
中重新选取leader
-follower
负责副本数据之间的同步 -follower
可以理解为自动备份,会不断从对应分区拉取leader的数据,对数据进行存储leader
和follower
之间同步数据
也会发送ACK
在Spring-Kafka中,提供了集中AckMode模式
org.springframework.kafka.listener.ContainerProperties.AckModepublicstaticenumAckMode{RECORD,BATCH,TIME,COUNT,COUNT_TIME,MANUAL,MANUAL_IMMEDIATE;privateAckMode(){}}
- RECORD:每处理一条消息后立即发送确认。这意味着每处理一条消息,消费者都会向 Kafka 代理发送一个确认消息。
- BATCH:批量确认模式。消费者将处理一批消息后才发送一次确认。这种模式可以减少确认消息的数量,提高性能。
- TIME:定时确认模式。消费者将在一定时间间隔内处理的所有消息后发送一次确认。这可以控制确认消息的发送频率。
- COUNT:计数确认模式。消费者将处理一定数量的消息后发送一次确认。这种模式也有助于控制确认消息的发送频率。
- COUNT_TIME:结合了计数和定时的确认模式。消费者将在达到一定数量的消息或一定时间间隔内发送一次确认,以提高灵活性和性能。
- MANUAL:手动确认模式。消费者需要在处理消息后显式地调用确认操作,以告知 Kafka 代理消息已被处理。
- MANUAL_IMMEDIATE:立即手动确认模式。与上述手动确认模式类似,但在调用确认操作后立即发送确认,而不是等待一定的时间或数量。
7.1手动提交ACK
Kafka中ACK默认是自动提交的,在开发中,有时候我们需要进行手动提交ACK,那么在配置中我们可以做如下修改
- 禁止自动提交
enable-auto-commit=false
,- 设置
ack-mode
为manual_immediat(立即手动确认模式)
@KafkaListener(topics ="#{'${dc.topics.user}'.split(',')}", groupId ="${dc.group-id}", autoStartup ="true")publicvoidonMessageSync(ConsumerRecord<String,String> record,@HeadersMap<String,Object> headers,Acknowledgment ack){
logger.error("获取到的信息为:{},",record);//手动提交offset// ack.acknowledge();}
8.查看指定分区的数据
有时候需要在服务器上查询一些分区的数据,可以使用 kafka提供的工具
kafka-console-consumer
kafka-console-consumer --bootstrap-server 192.168.112.129:9092 --topic ENTRY_LIST_SEND_INFO --from-beginning
版权归原作者 爱编程的饭团 所有, 如有侵权,请联系我们删除。