介绍
Kafka是由Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写。Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者在网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。 对于像Hadoop一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka的目的是通过Hadoop的并行加载机制来统一线上和离线的消息处理,也是为了通过集群来提供实时的消息
基本属性
producer:发布消息的对象称为生产者
broker;一个独立的kafaka服务器就被成为broker,多个broker可以组成kafaka集群
topic:kafaka将消息保存到主题中,可以简单理解为topic为文件夹,消息为文件夹中的文件,与传统的消息系统不同的是,消息被消费后不会删除,但是可以设置过期时间
使用流程
生产者
- 构建一个生产者
- 构建一个生产者对象
- 指定要连接的kafaka集群地址
- 创建发送消息对象
- 指定要发送消息的主题
- 发送消息
- 关闭生产者对象
消费者
- 构建一个消费者
- 构建一个消费者对象
- 指定要连接的kafaka集群地址
- 指定要消费者的分组
- 订阅消息主题,也可以理解为对这个主题的一个监控
- 创建拉取消息对象,指定一定时间内重复拉取内容–死循环
- 输出拉取到的消息
kafaka如何保障消息的可靠性
1:生产者数据可靠性的保障
—生产者发送消息的工作流程
1:从集群中订阅到具体的分区的领导者副本
2:将消息发送到领导者副本
3:领导者副本将消息写入到本地文件
4:跟随着副本从领导者副本获取到消息
5:跟随着副本写入消息,返回ack到领导者副本
6:领导者副本接受到所有的跟随着副本的ack以后,向生产者发送ack
—生产者数据可靠性的保障办法
——获取响应结果
如果可以获取到我们生产消息之后的响应结果,我们是不是也就知道了我们消息是否正确写入
- 发送并忘记
把消息发送给服务器,并不关心他是否可以正常到达,大多数情况下,消息都会正常到达,因为kafaka是高可用的
- 同步发送
使用send()方法发送,他会返回一个Future对象,调用get方法进行等待,就知道消息是都发送成功
- 异步发送
调用send方法,并制定一个回调函数,服务器在返回响应时调用函数
——消息确认机制
- acks=0:
生产者在写入消息以后,不管你的kafak服务器是否成功写入,也不管你的跟随着副本是否已经同步,直接认为发送成功,继续发送下一条消息,
因为没有等到消息写入成功返回的响应,所以如果acks等于0的时候,我们是接受不到正确的生产者偏移量,只会接受到一个-1.
优点:速度快、最低的延迟
缺点:消息有丢失的风险,因为一直认为发送成功,也不会去触发重试机制,如果发送消息之后,kafak服务器宕机,我们也不会接受到消息。
- acks=1:
只要是集群中的领导者副本接受到消息,生产者就会收到一个来自服务器的成功响应
优点:可以保证数据成功写入了kafak服务器的领导者副本,如果响应有误,就会触发重试机制
缺点:但是如果是我们的跟随着副本正在同步领导者副本的内容时候,出现宕机等情况。我们是无法得知我们的跟随着副本是否成功的同步完成,还是会有数据丢失的隐患
-
asks=all:
当领导者节点接受到其他跟随着节点响应过来的acks的时候,领导者节点才会向生产者响应成功
优点:解决了不清楚跟随着副本的同步问题,解决了数据丢失。
——重试
- 生产者从服务器收到的错误有可能是临时性错误,在这种情况下,retries参数的值决定了生产者可以重发消息的次数,如果达到这个次数,生产者会放弃重试返回错误。默认情况下,生产者会在每次重试之间等待100ms
- 我们也可以查询一下在java中操作kafka的生产者对象配置中的retries默认值
"retries", Type.INT, 2147483647, Range.between(0, 2147483647)
2:kafaka服务器保障数据
2.1:分区
主题可以分为一个或者多个分区,这些分区也可以存放在不同的机器上,实现了kafaka的伸缩性。
其实就是为了防止我们写入的数据太多,达到我们kafaka主题的最大内存至值,我们可以将用户存入的数据拆分成一个或者多个放在不同的机器上,以达到数据的伸缩性
消息在每个分区按照顺序写入,写入后会记录最新的位置,成为偏移量
2.2:副本
为了防止数据丢失、容错性、高可用性,我们可以使用副本进行处理
kafak中定义了俩种副本
- 领导者副本Leader
- 追随者副本Follower
副本可以备份在不同的服务器上,也可以备份在跨区域的数据中心
一个领导者副本对应一个或者多个领导者副本
2.3:重平衡
重平衡是消费者组下的每一个消费者如何达成一致来进行订阅不同分区下的主题
发生重平衡的时机
- 订阅的主题数发生变化
- 主题分区发生变化
- 消费端的消费者组成员变化
- 消费者正常调用unsubscribe()退出
- 消费者宕机退出消费者组
- 有新成员想要加入消费者组
心跳机制和重平衡的关系
- 消费者通过心跳关系来告诉事务的交易协调员自己的状态和位移
- 重平衡将分配好的结果响应回事务的交易协调员,交易协调员再通过心跳机制来告诉消费者,具体的分区下的主题
消费者组重平衡状态切换
- 一个消费者组最开始的就是Empty状态
Empty:即组内没有任何成员,但消费者组可能存在已提交的位移数据,而且这些位移尚且未过期- 当重平衡过程开启后,它会被至于PreparingRebalance状态,等待成员加入
PerparingRebalance:消费组准备开启重平衡,此时所有成员都要重新请求加入消费者组===- 所有成员加入之后回变更到CompletingRebalance状态,等待分配方案
CompletingRebalance:消费者组下所有成员已经加入,各个成员正在等待分配方案,该状态在老一点的版本中被成为AwaitingSync- 当事务的交易协调员分配完消费者消费的分区后,最后就流转到Stable状态完成重平衡
Stable:消费者组的稳定状态,该状态表名重平衡已经完成,各个消费者稳定上传心跳机制和位移信息- 当有成员加入或者退出的时候,消费组的状态从Stable直接跳到PreparingRebalance状态
- 此时所有的现有成员必须重新申请加入组。
- 当所有的消费组退出消费者组后,消费者组状态变更为Empty
消费者组重平衡状态切换实现流程
既然我们知道了消费者组的重平衡流程,我们也可以了解一下具体是怎么实现的
- 新成员加入组
当组内有新消费者请求加入组的时候,这个消费者会向事务的交易协调员发送JoinGroup(加入组)请求
在这个时候,所有组内的消费者需要重新发送自己的订阅信息,当事务交易协调员收集齐了所有的订阅信息,就会从这些消费者中选出一个作为消费者领导者。
这个消费者领导者会根据消费者的订阅信息做出一套分配方案
发出同步组的请求
在这里其他成员也会向事务交易协调员发送同步组的请求,只不过请求体中没有实际的内容,这一步的主要目的是让事务的交易协调员接收分配方案
消费者领导者将做好的分配方案返回事务的交易协调员
事务交易协调员以同步组的响应方式分发给所有成员,这样组内的成员就都知道自己应该订阅那些分区下的主题 - 组内成员自动离组
指消费者因为所在线程或进程调用close(),或者unsubscribe(),从消费者组重退出的时候
当有消费者申请退出组的时候,首先这个消费者会发送一个请求到事务的交易协调员这里。
事务的交易协调员就会通过心跳机制通知其他组内成员,即将开始重平衡,请重新加入组
其他消费者接收到心跳机制,将自己需要消费的主题列表发送到这个事务的交易协调者,
如果是消费者领导者,事务交易协调中心还会叫组内其他成员的订阅信息告诉当前消费者,如果不是,会告知当前消费者已经成功的加入组
重平衡结束,继续心跳机制,当前消费者组的状态是Sanble - 组内成员崩溃离组
消费者出现严重的故障,突然宕机导致的离组
因为这是外部原因导致的消费者离开当前组,所有他不会通知事务交易协调者说,自己申请离组。但是我们可以通过心跳机制来进行校验当前消费者是否正常
如果超过一定时间,消费者没有发送正确的心跳机制返回事务交易协调中心,事务交易协调中心就会像其他的消费者发送重新入组的请求。将没有信息的消费者交由其他功能处理
重平衡时协调者对组内成员提交位移的处理
正常情况下,每个组内成员都会定期汇报位置给协调者,当重平衡重新开启的情况下,协调者就会给予消费者一段缓冲时间
要求每个成员必须在这段时间内快速的上报自己的位移信息 - 重平衡时协调者对组内成员提交位移的处理
正常情况下,每个组内成员都会定期汇报位置给协调者,当重平衡重新开启的情况下,协调者就会给予消费者一段缓冲时间,要求每个成员必须在这段时间内快速的上报自己的位移信息
总结
重平衡总结
- 事务协调中心感知到消费者组的变化
- 然后再心跳的过程中发送重平衡信号通知各个消费者离组
- 然后各个消费者重新以加入组的请求发送到事务协调中心,并且选出消费者领导者
- 当所有的消费者都加入到了组内,消费者领导者会计算出一套适合的分配流程,然后将这套流程返回事务交易协调中心
- 事务协调中心根据这套方案使用同步组的命令执行下去,通知各个消费者,这样就是一轮的重平衡完成。
3:消费者成功从kafaka消费数据
—3.1:消费者工作原理
1.从集群中获取到分区的领导者副本
2. 从领导者副本拉取消息
3. 领导者副本根据消费者的偏移量信息,查询消息
4. 返回消息
5. 将消费后的偏移量信息提交回kafaka服务器
—3.2:消费者数据可靠性的保障办法
——偏移量的提交
- 自动提交
kafka的默认值,每隔五秒消费者会自动吧从poll()方法接受到的最大偏移量提交上去,如果将enable.auto.commit设置为false,就是关闭自动提交 - 手动提交
当enable.auto.commit设置为false的时候,不会自动提交偏移量,如果这个时候不手动的调用commit()方法进行提交的话,是会重复消费消息
——有新组加入的消费方式
auto.offset.reset设置,这个值可以设置成为不同的值,每一个值都有一个不同的效果
-
earliest
当各分区下都有当前分组提交的偏移量t时,从提交的偏移量开始消费;无提交的偏移量时,从头开始消费
-latest
当各分区下都有当前分组提交的偏移量t时,从提交的偏移量开始消费;无提交的偏移量时,消费最新消息,也就是消费你这个分组加入之后生成上去的消息 -
none
topic各分区都存在已提交的偏移量的时,从偏移量后开始消费;只要是有一个分区不存在已提交的偏移量,则抛出异常 -
anything else
直接向消费者抛出异常
——解决消息重复消费
默认情况下,我们的提交偏移量的方式是自动提交,每隔5秒进行一次提交
举例:
如果在我这次提交偏移量A之后,我继续消费,但是没有达到5秒,也就是没有达到下一次的偏移量提交,我宕机了,这样导致的就是kafka服务器中记录的依旧是我A的偏移量,我再次访问,也是从偏移量A开始访问,这样就导致了消息的重复消费
做法
- 将自动提交改为手动提交,调用commit方法,在每次进行了消费消息之后,都提交一个新的偏移量上去
- 可以不对消费者做任何处理,等到重复的消息到达以后,我对这些重复消息做一个幂等处理就可以
—— 解决消息丢失
默认情况下,我们的提交偏移量的方式是自动提交,每隔5秒进行一次提交
举例:
假如我每隔5秒向服务器中提交一次偏移量,当我接受到一条消息之后,正好到达5秒,我就将这条消息所在的偏移量提交上去,但是这个时候,我是应该在消费这条消息,但是如果这个时候宕机了,我这条消息并没有消费完成,我下一次连接kafka服务器的时候,我就需要kfka将这条数据重新给我发过来,让我处理,但是事与愿违。因为这个时候kafka中记录的就是我这条数据的偏移量,我这次连接kafka进行消费,直接从这条数据的偏移量继续向下拉取了,这个时候这条数据,就丢失了
做法:
- 将自动提交改为手动提交,等我们处理完消息之后,再进行提交
kafka集成Spring Boot
具体步骤
添加依赖
<!-- 继承Spring boot工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.8.RELEASE</version>
</parent>
<properties>
<fastjson.version>1.2.58</fastjson.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- kafkfa -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
</dependencies>
添加配置文件-application.yaml
server:
port: 9111
spring:
application:
name: kafka_demo
kafka:
# 指定要连接的kafka服务器地址
bootstrap-servers: 192.168.85.143:9092
#指定消费者组
consumer:
group-id: "spring_boot_kafka_demo01"
#修改提交方式
enable-auto-commit: true
#设置新加入组的消费方式
auto-offset-reset: latest
#重试
producer:
retries: 10
添加启动类
package com.itheima;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class kafkaApp {
public static void main(String[] args) {
SpringApplication.run(kafkaApp.class,args);
}
}
构建生产者
package com.itheima.producer;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@RestController
@RequestMapping("/kafka")
public class MyProducerTest {
@Autowired
private KafkaTemplate<String,String> kafkaTemplate;
@GetMapping("/demo01")
public Long sendProducer() throws ExecutionException, InterruptedException {
String topic="Spring_boot_kafka";
Future<SendResult<String, String>> send = kafkaTemplate.send(topic, "Spring_boot_kafka_value");
SendResult<String, String> stringStringSendResult = send.get();
RecordMetadata recordMetadata = stringStringSendResult.getRecordMetadata();
long offset = recordMetadata.offset();
return offset;
}
}
构建消费者
package com.itheima.consumer;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Service
public class MyConsumerTest {
/*指定要监听的主题*/
@KafkaListener(topics = "Spring_boot_kafka")
public void HandleMessage(ConsumerRecord<String,String>record){
long offset = record.offset();
String key = record.key();
String value = record.value();
System.out.println("接受到消息的键是:"+key+",这个消息的value是:"+value+",消息的偏移值是:"+offset);
}
}
基本上刚开始搞懂这些就可以,慢慢深入了解吧!乾坤喂马,你我皆是黑头!