一,首先讲kafka的两种模式
1,点对点模式,也就是poll模式,消费者主动拉取数据,消息收到后清除消息
2,发布,订阅模式:
- 可以有多个topic
- 消费者消费数据后,不删除数据
- 每个消费者相互独立,都可以消费到数据(同一条消息可以被不同的消费者同时消费)
kafka的基础架构(瓶颈:当存储的数据量过大时,一台broker很难承受太大的压力,这个时候就需要搭建集群,引入分区)
kafka集群架构(优点:1.对消息进行分区管理,减少压力,2.消费者也要搭建集群,分组,3.多个分区可以被同一个分组消费,但是同一个分区,只能被一个分组消费,4.在每一个broker中存的分区可以建立副本,就可以保证当一台broker宕机后,他的副本可以升为leader继续进行消息存储5.这里提到了zookeeper,在kafka里存的就是哪个broker在运行,以及以及每个分区当前谁是leader)
zookeeper和kafka的安装和启动(需要先安装jdk,这里不做概述)
zookeeper的安装和启动
①:到apache上的zookeeper网站:Apache ZooKeeper
②:选择Getting Started下的Download点击进入:
③选择Download下的Download:
④选择清华大学的站点地址:Index of /apache/zookeeper
⑤三者选择其中之一,然后进行下载:
2、zookeeper的安装:
①:将下载的zookeeper安装包上传到Linux中的/usr/local目录下
②:在/usr/local下新建一个software文件夹,专门用来存放安装的软件
③:解压 tar -zxvf apache-zookeeper-3.5.5-bin.tar.gz
④:mv apache-zookeeper-3.5.5-bin software (将apache-zookeeper-3.5.5-bin移动到software文件夹下)
3、zookeeper的配置:
⑤:在/usr/local/software/apache-zookeeper-3.5.5-bin下创建data、logs文件夹
⑥:cd /usr/local/software/apache-zookeeper-3.5.5-bin/conf,然后cp zoo_sample.cfg zoo.cfg(复制创建一份新的zoo.cfg配置文件)
⑦:vim zoo.cfg将dataDir=/tmp/zookeeper 改为:dataDir=/usr/local/software/apache-zookeeper-3.5.5-bin/data
4、启动:
在/usr/local/software/apache-zookeeper-3.5.5-bin下 bin/zkServer.sh start启动
5、停止:
在/usr/local/software/apache-zookeeper-3.5.5-bin下 bin/zkServer.sh stop 停止
Kafka的下载、安装、配置
1、kafka的下载:
①:进入Apache Kafka ,选择Download
②:选择版本下载:
2、安装
①:将kafka_2.12-2.3.0.tgz上传到/usr/local下
②:解压 tar -zxvf kafka_2.12-2.3.0.tgz
3、配置
在/usr/local/software/kafka_2.12-2.3.0下 运行 vim config/server.properties(编辑server.properties文件)
-
log.dirs=/usr/local/EntyApp/env/app/kafka_2.13-3.0.0/data //kafka的数据存放地址,必须修改,放在kafka的data目录下
-
zookeeper.connect=192.168.159.143:2181/kafka //kafka连接zookeeper的地址,后边必须加kafka,否则在kafka关机后,存放在zookeeper的数据会散落到各个地方,需要手动清除
kafka后台启动的命令:
bin/kafka-server-start.sh -daemon config/server.properties
到这里,kafka就可以使用了
下边来讲一讲kafka生产者发送到broker的过程:
消息生产到发送到broker的流程:
- 首先生产者main线程会创建一个producter消息生产者用来发送消息,然后可以配置拦截器,根据业务场景,决定是否需要,然后会经过序列化器,使用的是kafka自己的序列化器,java的序列化器太重,消息内容繁琐且占用内存大,所以kafka使用的是自己的序列化器,然后会经过分区器,分区器根据你的分区编号来分区
- 分区器根据分区数先在内存中决定要发送到哪个区,为每一个分区创建一个队列,用来存储消息,整个内存消息区一共为32m,每个区为16k为满,当一个区满时或者因为长时间未满,但是达到了消息存储最长时间,这个时候sender线程就出来了(这里的分配区,实际上是一个内存池,当消息内存释放后,会归还给内存池,实现了复用)
- sender线程会把满的队列,或者超时的队列先分配一下,具体发送给哪个broker,每个broker对应多个请求,假如给broker1发送了五个请求,都未应答,则下一个请求就不会往broker1发了,直到,一开始发送的五个请求有了相应,才会继续往该broker发送消息,请求发送和接受使用的是一个selector模型,若收到的响应为成功,则会把sender中的请求清除掉,把分区器分配的该消息占用的内存归还给内存池,如果失败,则会重试发送,重试的次数是int的最大值,这个次数可以自己配置
- 接下来当请求到达broker集群后,若是成功放入了分区,kafka会有一个节点副本同步机制,会把当前节点的数据同步给副本,而kafka给sender的响应时机有三种,第一种就是无需等待应答,sender接着发送,第二种就是,leader收到消息,就响应,第三种就是,整个集群每个副本都同步成功了,才响应
下边来介绍消息生产发送到broker的一些配置
分区策略
那么具体在生产环境中如何设置
ProducerRecord的第二个参数就是分区数,但是必须上传key,我们可以设置为空
kafkaProducer.send(new ProducerRecord<>("first", 1,"","hello" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
if (exception == null){
System.out.println("主题: "+metadata.topic() + " 分区: "+ metadata.partition());
}
}
});
分区呢?
如何提高kafka消息发送的吞吐量呢?
无非就是三个参数,分区队列大小,最长消息存放时间,还有就是整个分区缓冲区大小,一般我们生产环境,存放时间就是5-100ms,队列大小为16k,缓冲区根据实际分区数量来定,一般32m够的
// 0 配置
Properties properties = new Properties();
// 连接kafka集群
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092,hadoop103:9092");
// 序列化
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 缓冲区大小
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,33554432);
// 批次大小
properties.put(ProducerConfig.BATCH_SIZE_CONFIG,16384);
// linger.ms
properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);
// 压缩
properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG,"snappy");
// 1 创建生产者
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
// 2 发送数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("first","atguigu"+i));
}
// 3 关闭资源
kafkaProducer.close();
如何保证数据的可靠性?不丢失且不重复消息
消息不丢失:
首先了解一下kafka消息落盘的响应机制
那么最终保证可靠性的方案是什么呢?
至少成功一次:ask级别设置为-1+加分区副本大于等于2+ISR队列最小副本数量大于等于2(ISR是kafka为了保证follow可以正常向leader同步,如果挂了,那么它在一段时间未向leader发出同步请求,就会被踢出ISR队列)
设置如下:
// acks
properties.put(ProducerConfig.ACKS_CONFIG,"1");
// 重试次数
properties.put(ProducerConfig.RETRIES_CONFIG,3);
she性:上边的方法其实已经很可靠了,但是还有问题,比如leader消息已经落盘了,并且同步好了副本节点,还没有响应,这个时候leader宕机了,那么这次发过来的消息,sender现场会认为失败了,会重复发送,这样就造成了消息重复
要保证单会话的可靠性,其实比较简单,就是使用幂等性,它判断重复的三个主要元素是:
pid(kafka每次重启都会重新生成一个)
partition(分区数)
seqNumber(每个分区消息单调递增)
开启这个参数即可,默认就是开启的:enable.idempotence=true
但是如果kafka节点宕机了,重新启动,还是会有重复的问题,那么到这只能使用事务来保证消息不重复了
// 指定事务id
properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "tranactional_id_01");
// 1 创建kafka生产者对象
// "" hello
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
kafkaProducer.initTransactions();
kafkaProducer.beginTransaction();
try {
// 2 发送数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("first", "atguigu" + i));
}
int i = 1 / 0;
kafkaProducer.commitTransaction();
} catch (Exception e) {
kafkaProducer.abortTransaction();
} finally {
// 3 关闭资源
kafkaProducer.close();
}
切记,一定要指定事务id,负责会失败
数据有序:就是消费者,要按照生产者发送消息的顺序,进行消费
分区broker:这个需求,单节点,好解决,后边再讲,先说多节点,那么唯一的方法就是,消费者把整个sender线程的所有请求全部拿过来,然后再排序,再进行消费,那这样的话,效率自然就会比较低
单机kafka:
每次sender的每个分区最多缓存五个请求,只有全部发送成功,才能再次发送,等消息成功发送到kafka并成功落盘后,会对发过来的数据重新进行排序,这样就保证了消息消费的一个顺序
zookeeper中存储kafka的哪些信息:只列举几个重要的部分
- /kafka/brokers/ids 记录kafka集群节点,存活的
- /kafka/brokers/topics/first/partitions/0/state 记录每个topic每个分区的leader和follow信息
- /kafka/controller 用于注册节点controller,辅助选举leader
kafka中是如何存储数据信息的?
kafka会把每个节点每个分区的数据,采取分片和索引机制,将每个partition分为多个segment。每个segment大小为一个g,包括:".index 文件" ".log 文件" " .timeindex 文件",这些文件位于一个文件夹下,该文件夹的命名规则为topic名称+分区序号,例如first-0
index是如何存储的:
log文件每写入4kb文件就往index文件中写入一条信息,内容为offset(相对当前log起始offset偏移量)和position,采用稀疏索引的方式,当需要找到某个offset的具体位置时,可以先到index文件中找到对应的position位置,再通过position到log文件找到对应的日志消息数据
如何查看segment下的文件呢?
kafka-run-class.sh kafka.tools.DumpLogSegments --files ./0000000000000000.log
kafka分区leader和follower选举策略,完整步骤
首先,每个kafka节点启动先向zookeeper注册节点信息,同时每个节点的controller也向zookeeper的controller注册节点信息,谁第一个注册上去,就由,哪个节点的controller 来负责leader选举,假如broker0的controller为选举者,那么,先从注册列表中,拿出ar注册顺序,然后看isr存活的服务节点,首先存活,然后按照ar顺序选举,谁在第一个就是leader,然后broker0的controller会把分区节点信息放入zookeeper的/kafka/brokers/topics/first/partitions中,然后副本向zookeeper申请同步相关信息,假如选举者broker0挂了,他们可以,及时补充上去,假如leader挂了,那么选举者就会检测到ids服务列表的变化,及时拿到新的节点信息,马上从zookeeper拿到之前的节点信息,按照ar顺序,且在isr列表中的副本中进行选举,到这里就是完整选举步骤了
服务加入新节点:假如希望新添加一个kafka服务器,用来存储副本信息,以减少分区服务器的压力
- 创建一个想要增加节点负载均衡的topic的json文件
vim topics-to-move.json
{
"topics": [
{"topic":"first"}]
"version": 1}
2.生成一个负载均衡计划
bin/kafka-reassign-partitions.sh --bootstrap-server kafka节点ip:port --topics-to-move-json-file 上一步生成文件绝对路径 --broker-list "集群几点brokerid,逗号隔开" --generate
//执行完上边的操作,会生成一个执行计划
3.将上边的执行计划放入一个increase-replication-factor.json文件中
4.执行存储计划
bin/kafka-reassign-partitions.sh --bootstrap-server
hadoop192:9092 --reassianment-ison-file increase-replication-factor.json --execute
5.查看执行结果
bin/kafka-reassign-partitions.sh --bootstrap-server
hadoop192:9092 --reassianment-ison-file increase-replication-factor.json --verify
退役旧节点
和新增节点操作一样
follower节点挂了底层做了什么
首先明白两个概念:
LEO(log end offset) :每个节点分区的消息offset下标+1
HW(High Watermark):当前分区的leader和follower中最小的LEO
首先会把follower从isr队列中移除,然后正常的leader和follower继续接收数据,等挂掉的follower恢复后,会按照它挂时候hw把后边发过来的消息扔掉,它认为这是没有验证的数据,不安全,然后从这个hw位置向leader申请数据同步,等该follower的hw追上正常的leader和follower的hw时,就是大于等于,就可以重新加入ISR队列了
如果只有一个broker是否还有必要分区?
首先要明白分区的目的是什么?
-
容量扩展:多个分区允许你以分布式的方式扩展Kafka集群的容量。当你的数据量增长时,你可以增加更多的broker,并为每个broker分配分区,以增加整体的处理能力。
-
并行消费:即使在单个broker上,多个消费者也可以并行消费多个分区的消息。这样可以提高消费速度,并使多个消费者能够处理更大的消息流。
-
容错性:使用多个分区可以提高系统的容错性。如果某个分区发生故障,其他分区仍然可以正常运行,确保系统的可用性。
-
负载均衡:多个分区可以实现负载均衡,将消息均匀地分布到不同的分区中。这样可以避免某个分区成为瓶颈,影响整体系统的性能。
因此,即使只有一个broker,使用多个分区仍然有助于提高Kafka集群的可扩展性、性能和容错性。然而,具体的分区设置应该根据你的应用需求和数据流量来确定,以确保达到最佳的平衡。
如何修改kafka分区数据的存储位置
- 先创建一个increase-replication-factor.json文件,在里边加入一下内容
//这里就是将三个分区的数据全部存到0,1主机上
{
"version": 1,
"partitions": [
{
"topic": "topic1",
"partitions": 0,
"repalicas": [
1,
0
]
},
{
"topic": "topic1",
"partitions": 1,
"repalicas": [
1,
0
]
},
{
"topic": "topic1",
"partitions": 2,
"repalicas": [
1,
0
]
}
]
}
2.执行文件
bin/kafka-reassign-partitions.sh --bootstrap-server
hadoop192:9092 --reassianment-ison-file increase-replication-factor.json --execute
3.查看执行结果
bin/kafka-reassign-partitions.sh --bootstrap-server
hadoop192:9092 --reassianment-ison-file increase-replication-factor.json --verify
4.查看分区副本存储情况
bin/kafka-topics.sh --bootstrap-server
hadoop192:9092 --describe --topic three
leader Partition负载均衡,自动平衡
当多个broker集群其中部分挂了以后,会将其他主机上的副本升为主机,这样就会造成一台主机上好几个分区的leader同时操作,大家知道不管消费者还是生产者,操作的对象始终是leader,那这样的话他们的压力就会很大
在kafka中有这样一个参数auto.leader.rebalance.enable可以设置是否开启自动均衡,默认就是true,但是生产环境不建议开启,要关闭,可以调整kafka数据存储位置的方式手动调整
kafka的segment文件清理策略
log.cleanup.policy=delete/compact默认是删除,时间为7天删除一次,可通过log.retention.hours设置小时,然后检测周期是默认5分钟,可通过log.retention.check.interval.ms设置值
kafka高效读写数据的原理
1.采用分区技术,可并行操作
2.读取数据采用稀疏索引,可快速定位到要消费的数据
3.按顺序追加数据,会按顺序往log文件中追加信息
4.kafka服务端不处理数据,实现了零拷贝,数据由生产者发过服务端以后,会先缓存在page cache中,由操作系统控制持久化到硬盘里,再需要消费时,先从缓存找,没找到再去硬盘中找,然后不需要返回给kafka服务端,直接通过网卡传输给消费者,由消费者根据自己的需求做特殊处理,kafka服务端只负责存取的职责
kafka消费者组讲解
如果消费者组大于分区数,消费者如何消费对应的分区?
如果是这样的话,多出来的消费者就会闲着
消费者组初始化以及分区的分配流程?
首先来讲一下coordinator,这个东西,是干嘛的呢,首先每个broker节点都会有自己的coordinator,根据消费者组id的hash%50取余数,得到的数字,就是选举的消费者组对应的coorfinator的brokerid,有它负责对接消费者组,那么选举出了消费者组领导人,消费者组每个消费者就会向这个coorfinator发一个加入消费者组请求,然后这个coorfinator会随机选出一个消费者作为leader,然后coorfinator把需要消费的topic信息发给这个leader,由它指定消费方案,发送给coorfinator,它会把具体的消费方案下发给具体的消费者
消费者具体的消费流程?
上个问题解决了分配流程,接下来,每个消费者组拿到了具体的消费方案,开始从kafka消费,首先消费者要向和broker建立连接开始消费,需要有一个consumerNetworkClient网络连接客户端,设置一些拉取参数,然后发送给broker,broker收到拉取请求后,就会调用对应的一个completedFetches成功的回调函数来拿到对应的消息,然后存在一个队列里,再经过反序列化器和拦截器,最终到达消费者,开始处理数据
consumer消费者组leader分配消费方案的策略有几种?
range:
roundRobin:
sticty:
和range在分配数量上是一样的策略,但是,分配的分区是随机的
消费者的消费的offset下标是存在哪里的?
在kafka0.9之前是存在zookeeper中的,但是这样,每次消费都要消耗大量的网络开销,会影响性能,于是在0.9之后,全部放在了kafka主机文件上
如何查看offset的值呢?
- 首先在config/consumer.properties中添加一行exclude.internal.topics=false
- 执行如下命令
bin/kafka-console-consumer.sh --topic consumer offsets --bootstrap-server hadoop102:9092 --consumer.config config/consumer.properties --formatter "kafka.coordinator.group.GroupMetadataManager\$OffsetsMessageFormatter"--from-beginning