Kafka
一、Kafka 概述
1.Kafka传统定义:Kafka是一个分布式的基于发布/订阅模式的消息队列,主要应用于大数据实时处理领域
发布/订阅:消息的发布者不会将消息直接发送给特定的订阅者,而是将发布的消息分为不同的类别,订阅者只接受感兴趣的信息
2.Kafka最新定义:Kafka是一个开源的分布式事件流平台,被数千家公司用于高性能数据管道、流分析、数据集成和关键任务应用
3.传统消息队列应用场景:缓存/消峰、解耦和异步通信
1)缓冲/消峰:有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况
2)解耦:允许你独立的扩展或修改两边的处理过程,只要确保他们遵守同样的接口约束
3)异步通信:允许用户把一个消息放入队列,但不立即处理它,然后在需要的时候再去处理它们
4.消息队列的两种模式
(1)点对点模式:消费者主动拉取数据,消息收到后清除消息,消息生产者生产消息发送到Queue中,然后消费者从Queue中取出消息并消费,消息消费后,Queue中不会再存储,所以消费者不可能消费到已经被消费的消息,Queue支持存在多个消费者,但是对于一个消息而言,只会有一个消费者可以消费
(2)发布/订阅模式:消息生产者将消息发布到Topic(主题)中,同时有多个消息消费者(订阅)消费该消息,消费者消费消息后,消息数据不会被删除,其他消费者也可以消费这些消息,每个消费者是独立的,都可以消费到数据
5.Kafka基础架构
1) 在生产者生产大量信息数据的情况下,单个Kafka服务器存储不下大量数据,这时需要使用Kafka集群将一个Topic
(主题)的信息数据分解为多个partition
(部分)存储在不同Kafka服务器中
2)在有大量消息数据的情况下,提出消费者组的概念,一个消费者组由多个消费者构成,组内的每个消费者并行消费,每个消费者消费不同分区的信息,即一个分区只能由一个组内的一个消费者消费,消费者组之间不互相影响,即消费者组是逻辑上的一个订阅者
3)Replica:副本,为保证奇群中的某个节点发生故障时,该节点上的partition数据不丢失,且kafka仍能继续工作,kafka提供了副本机制,一个topic的每个分区都有若干个副本,一个leader和若干个follower
4)leader / follower : Kafka的副本分为leader和Follower,副本之间是没有差别的,在角色上分为leader 和 follower,在生产和消费消息时,只针对于Leader进行操作,而Follower的工作只是实时同步数据,当leader出现故障挂掉后,follower有条件可以成为leader,进行数据操作
6.KafKa的安装
下载kafka 的压缩包,在linux中解压并配置相关配置信息
7.Shell 脚本 服务器的同步分发
1) 同步分发:使用shell脚本对服务器中的文件同步上传到其他服务器中,也可以同步执行命令
2) 如果想要在任意位置下执行Shell脚本,则需要创建不同的环境变量,root用户的环境变量为 /usr/bin,将脚本复制到此位置下即可执行
3)同步分发脚本
#!/bin/bash
#1. 判断参数个数
if [ $# -lt 1 ]
then
echo Not Enough Arguement!
exit;
fi
#2. 遍历集群所有机器
for host in 192.168.114.100 192.168.114.101 192.168.114.102
do
echo ==================== $host ====================
#3. 遍历所有目录,挨个发送
for file in $@
do
#4. 判断文件是否存在
if [ -e $file ]
then
#5. 获取父目录
pdir=$(cd -P $(dirname $file); pwd)
#6. 获取当前文件的名称
fname=$(basename $file)
ssh $host "mkdir -p $pdir"
rsync -av $pdir/$fname $host:$pdir
else
echo $file does not exists!
fi
done
done
4)在普通用户创建脚本:
1.添加环境变量 /用户/bin
2.添加脚本文件
3.测试脚本文件
注意 环境变量文件不能同步分发 因为没有权限
5)在root用户创建脚本
1.在 /usr/目录中创建 myShell 文件夹 ,将脚本文件复制到文件夹中
2.添加环境变量 /usr/myShell/bin
6)免密登入:查看Hadoop视频 p29 目前只配置了root用户对其他服务器进行免密登入 其他用户需要重新配置
7)Java_Home 报错:由于使用的是root用户登入,所以在脚本中使用ssh命令时会寻找在 .bashrc中的配置 ,所以我们需要将Java_HOME配置在这个文件中,这个文件是隐藏文件,如果我们使用的是其他用户,则会寻找用户独立建立的 /etc/profile.d/myenv.sh文件中的环境变量 所以找不到
8.Topic 入门命令
1)使用命令创建 topic 创建 分区 1,副本 3 并测试
kafka-topics.sh --bootstrap-server 192.168.114.100:9092 --topic first --create --partitions 1 --replication-factor 3
2)注意:
- 在连接时总会出现超时问题,需要在server.propertis 中配置 advertised.listeners=PLAINTEXT://192.168.114.101:9092 否则会超时,也有可能是zookeeper的连接过慢 可以延长一下配置中zookeeper连接的超时时间
- 目前使用的是新版本的Kafka 有些Kafka的命令与参数 和旧版本的不相同,这里可以去参照 UncleHang的博客去看https://hang.domcer.com/
- 在修改分区时 ,修改后的分区数量的大小不能小于原先的数量,也不能大于Kafka集群服务器的数量,否则报错,这里是因为其他消费者在消费数据的时候,如果合并了分区,那么消费者的就会不知道去哪里消费信息了
- 命令行不能修改Kafka topic副本的数量
生产者
9.入门_命令行操作
1)开启Kafka集群,创建Topic后 使用命令 kafka-console-producer.sh --bootstrap-server 192.168.114.100:9092 --topic first 发送数据
2)另开客户端 ,使用命令 kafka-console-consumer.sh --bootstrap-server 192.168.114.100:9092 --topic first 接收数据,注意:开启消费者后,消费者只能接收到开启之后发送的数据,不能消费到之前的数据
3)使用 kafka-console-consumer.sh --bootstrap-server 192.168.114.100:9092 --topic first --from-beginning 接收查看历史数据
10.生产者发送消息流程
11.JavaAPI生产消息 同步/异步发送数据
package com.atguigu.kafka.producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
/**
* Kafka 同步/异步发送数据
* @author 昭浅
*/
public class CustomProducer {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 0.配置
Properties properties =new Properties();
// 连接集群
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.114.100:9092,192.168.114.101:9092");
// 指定对应keu和value的序列化类型 key.serializer
// properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.serialization.StringSerializer");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// "" hello
// 1.创建Kafka生产者对象
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
// 2.发送数据
for (int i = 0; i < 5; i++) {
// 同步发送数据
RecordMetadata first = producer.send(new ProducerRecord<>("first", "hello" + i)).get();
// 异步发送数据
// producer.send(new ProducerRecord<>("first","hello"+i));
}
// 3.关闭资源
producer.close();
}
}
12.异步发送回调函数
package com.atguigu.kafka.producer;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
/**
* @author 昭浅
*/
public class CustomProducerCallBack {
public static void main(String[] args) {
// 0.配置
Properties properties =new Properties();
// 连接集群
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.114.100:9092,192.168.114.101:9092");
// 指定对应keu和value的序列化类型 key.serializer
// properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.serialization.StringSerializer");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// "" hello
// 1.创建Kafka生产者对象
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
// 2.发送数据
for (int i = 0; i < 5; i++) {
producer.send(new ProducerRecord<>("first", "hello" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
// Exception 返回的错误信息 metadate 元数据
if(exception==null){
System.out.println("主题:"+metadata.topic()+" 分区:"+metadata.partition());
}
}
});
}
// 3.关闭资源
producer.close();
}
}
13.生产者分区
Kafka 分区好处:
(1)便于合理使用存储资源,每个Partition在一个Broker上存储,可以把海量的数据按照分区切割成一
块一块数据存储在多台Broker上。合理控制分区的任务,可以实现负载均衡的效果。
(2)提高并行度,生产者可以以分区为单位发送数据;消费者可以以分区为单位进行消费数据。
Kafka分区策略:
在执行 send() 方法时,需要new一个ProducerRecord() 在其中配置分区策略
自定义分区策略
/**
* 自定义分区策略
* @author 昭浅
*/
public class MyPartitioner implements Partitioner {
/**
*
* @param topic
* @param key
* @param keyBytes 序列化后的key
* @param value
* @param valueBytes 序列化后的value
* @param cluster
* @return
*/
@Override
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
// 获取数据
String msgValue = value.toString();
int partition;
if(msgValue.contains("atguigu")){
partition=0;
}else{
partition=1;
}
return partition;
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> configs) {
}
}
在 properties 中添加配置项
// 关联自定义分区器
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.atguigu.kafka.producer.MyPartitioner");
生产者如何提高吞吐量
在配置中添加配置项:
//增加吞吐量 的操作
// (1) 调整缓冲区大小 默认缓冲区 32M
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,33554432);
// (2) 修改批次大小 默认每个队列 16k
properties.put(ProducerConfig.BATCH_SIZE_CONFIG,16384);
// (3) linger.ms 默认是0 每0秒发送一次数据
properties.put(ProducerConfig.LINGER_MS_CONFIG,1);
// (4) 压缩 压缩数据的类型 gzip snappy lz4 zstd
properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG,"snappy");
14.ACK应答级别:数据可靠性
1)各个应答级别可能会发生的问题:
2)可靠性总结:
3)数据重复分析:
当Leader和所有Follower完成落盘后,Leader即将应答成功时,Leader宕机了,则会选举为其他Follower为Leader,由于宕机前的Leader没有应答成功,则send线程会重新发送数据,新选举的Leader会接收数据,造成数据重复
15.生产者_数据重复问题
幂等性:
开启参数: enable.Idempotence 默认为ture false为关闭
Kafka 事务原理
开启事务
public class CustomProducerTranactions {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 0.配置
Properties properties = new Properties();
// 连接集群
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.114.100:9092,192.168.114.101:9092");
// 指定对应keu和value的序列化类型 key.serializer
// properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.serialization.StringSerializer");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// 配置 事务 ID
properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"transcational_id_0");
// 1.创建Kafka生产者对象
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
// 开启Kafka 事务
producer.initTransactions();
producer.beginTransaction();
try {
// 2.发送数据
for (int i = 0; i < 5; i++) {
producer.send(new ProducerRecord<>("first", "hello" + i));
}
// 模拟 出现异常 事务失败
int j = 10/0;
// 提交事务
producer.commitTransaction();
} catch (Exception e) {
// 回滚事务
producer.abortTransaction();
} finally {
// 3.关闭资源
producer.close();
}
}
}
Broker
16.生产经验=数据的有序
1) 解决数据乱序
- Producer 的send线程会缓存producer已经发出但没有收到服务端响应的请求, 每个客户端与broker的网络连接的最多缓存请求数,默认是5,超过这个值,客户端不再向这个连接发送更多的请求,可以设置请求缓存数为1,使得请求未处理之前,不能发送其他请求
- 在Kafka1.x之后的版本,由于幂等性的出现,Kafka 服务端会缓存producer发来的最近5个request的元数据,开启了幂等性之后,会存在SeqNumber(自增序列化号),服务端会根据它来进行排序
17.Zookeeper中存储的Kafka信息
18.Kafka Broker总体工作流程
19.Broker节点操作
1) 服役新节点
1.克隆虚拟机,修改ip等配置文件,开启Kafka服务端,连接至Kafka集群
2. vim topics-to-move.json )
{
"topics": [
{"topic": "first"}
],
"version": 1
}
3. 执行命令 bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --topics-to-move-json-file topics-to-move.json --broker-list "0,1,2,3" --generate生成负载均衡计划,使得副本们负载均衡在所有服务器中,而不是集中
4. 将生成的计划复制在 vim increase-replication-factor.json 文件中
5. 根据命令执行计划 bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --execute
6. 执行命令bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --verify 验证计划是否应用成功
2) 退役旧节点
1.修改命令生成负载均衡计划 减少需要退役的节点并生成计划
bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --topics-to-move-json-file topics-to-move.json --broker-list "0,1,2,3" --generate
2.将计划复制在increase-replication-factor.json 文件中,执行命令bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --execute执行新计划 退役旧节点
3.当副本数只有一个时,退役节点会造成数据丢失,所以副本数最少有两个
20.Kafka副本
(1)Kafka 副本作用:提高数据可靠性。
(2)Kafka 默认副本 1 个,生产环境一般配置为 2 个,保证数据可靠性;太多副本会增加磁盘存储空间,增加网络上数据传输,降低效率。
(3)Kafka 中副本分为:Leader 和 Follower。Kafka 生产者只会把数据发往 Leader,然后 Follower 找 Leader 进行同步数据。
(4)Kafka 分区中的所有副本统称为 AR(Assigned Repllicas)。
AR = ISR + OSR
ISR:表示和 Leader 保持同步的 Follower 集合。如果 Follower 长时间未向 Leader 发送通信请求或同步数据,则该 Follower 将被踢出ISR。 该时间阈值由 replica.lag.time.max.ms
参数设定,默认 30s。Leader 发生故障之后,就会从 ISR 中选举新的 Leader。
OSR:表示 Follower 与 Leader 副本同步时,延迟过多的副本。
21.Leader选举流程
1)Controller辅助选举Leader流程
1.不同Broker中的Controller会先抢先在Zookeeper中注册信息,由最先注册的Controller来进行辅助选举Leader,Controller只是辅助选举,而并非决定谁是Leader,Controller将节点的信息上传到ZK
2.当参与辅助选举Leader的Controller所在的Broker宕机以后,其他Broker的Controller会同步Zookeeper中的节点信息并重新进行抢先注册,参与辅助选举Leader
2)Leader选举流程
-
当有Broker节点宕机时,会出现分区数大于目前所存活的Broker节点,kafka的分区数量可以大于broker节点数量,当分区数量大于broker节点数量时,在broker节点的data目录下会有同一个topic的两个分区的数据,如:topicName-0,topicName-1。所以会出现不同分区存在相同Leader的情况
-
Broker宕机后,各分区重新选举Leader的流程,会根据AR(所有副本,包括宕机的副本)的顺序来进行选举,而不是ISR(目前存活的副本),例如 Replicas(AR): 1,0,2,3 , 如果 此时 副本1宕机,则会选举0为此分区的Leader,如果此时 副本 0宕机,那么就会从头开始 选举副本1为当前分区的Leader,当遇到宕机的副本时,会自动跳过选举,继续选择下一位
-
服务器宕机重启后,在ISR中顺序会排在末尾
22.Follower故障处理细节
offset是什么?
对于每一个topic, Kafka集群都会维持一个分区日志
每个分区都是有序且顺序不可变的记录集,并且不断地追加到结构化的log文件。分区中的每一个记录都会分配一个id号来表示顺序,我们称之为offset,offset用来唯一的标识分区中每一条记录。
LEO (Log End Offset ):每个副本的最后一个offset LEO就是最新的offset + 1
HW ( High Watermark ):所有副本中最小的LEO,当设置ACK为-1(all)时,Follower还没有同步完成,Leader不会返回成功信息,所以用户只能查询到Leader之前的数据,也就是Follower最小offset同步的信息
- LogStartOffset:表示一个Partition的起始位移,初始为0,虽然消息的增加以及日志清除策略的影响,这个值会阶段性的增大。
- ConsumerOffset:消费位移,表示Partition的某个消费者消费到的位移位置。
- HighWatermark:简称HW,代表消费端所能“观察”到的Partition的最高日志位移,HW大于等于ConsumerOffset的值。
- LogEndOffset:简称LEO, 代表Partition的最高日志位移,其值对消费者不可见。
1)当Follower发生故障,Follower会被踢出ISR队列,期间,Leader和其他Follower会继续接受数据,当此Follower恢复后,会先将log文件中始于宕机前所记录的HW
的所有数据截取掉,从HW开始向Leader重新开始同步,当同步到目前存活的副本中的HW时,此Follower才会重新返回ISR队列
23.Leader故障处理细节
24.分区副本分配规则
25.生产环境中,指定分区副本存储情况
编辑 执行计划修改分区副本存储情况 vim increase-replication-factor.json
{
"version":1,
"partitions":[{"topic":"three","partition":0,"replicas":[0,1]},
{"topic":"three","partition":1,"replicas":[0,1]},
{"topic":"three","partition":2,"replicas":[1,0]},
{"topic":"three","partition":3,"replicas":[1,0]}]
}
执行命令 修改副本bin/kafka-reassign-partitions.sh – bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --execute
26.Partition自动平衡
原则上不建议开启自动平衡,因为浪费集群性能
27.增加副本因子
修改并执行新计划,添加副本,不能使用命令行来直接修改副本数量
{“version”:1,
“partitions”:[{“topic”:“four”,“partition”:0,“replicas”:[0,1,2]},{“topic”:“four”,“partition”:1,“replicas”:[0,1,2]},{“topic”:“four”,“partition”:2,“replicas”:[0,1,2]}]
}
28.Kafka文件存储机制
1)Kafka 存储文件中的索引index为稀疏索引,大约每往log文件写入4kb数据,会往index文件写入一条索引,参数log.index.interval.bytes默认为4kb
2) 当 .log文件存储大约4kb文件内容时才会向 .index文件中存储一条索引
29.Kafka文件清除策略
Kafka 中默认的日志保存时间为 7 天,可以通过调整如下参数修改保存时间。
log.retention.hours,最低优先级小时,默认 7 天。
log.retention.minutes,分钟。
log.retention.ms,最高优先级毫秒。
log.retention.check.interval.ms,负责设置检查周期,默认 5 分钟。
那么日志一旦超过了设置的时间,怎么处理呢?
Kafka 中提供的日志清理策略有 delete 和 compact 两种。
1)delete 日志删(默认)除:将过期数据删除
log.cleanup.policy = delete 所有数据启用删除策略
(1)基于时间:默认打开。以 segment 中所有记录中的最大时间戳作为该文件时间戳。
(2)基于大小:默认关闭。超过设置的所有日志总大小,删除最早的 segment。
log.retention.bytes,默认等于-1,表示无穷大。
思考:如果一个 segment 中有一部分数据过期,一部分没有过期,怎么处理?
Kafka会以segment中最大时间戳作为该文件的时间戳,该文件的时间戳是未过期文件的时间戳,该文件不会过期,就不会被删除
2)compact 日志压缩
30.Kafka 高效读写数据
1)Kafka本身是分布式集群,可采用分区技术,消费者并行消费数据
2)读数据采用稀疏索引,可以快速定位要消费的位置
3)顺序写磁盘:
Kafka的producer生产数据,要写入到log文件中,写的过程是一致追加到文件末端,为顺序写,而随机写由于需要需要磁头寻找会浪费大量时间
4)零拷贝+页缓存
页缓存(PageCache):Kafka会依赖Linux底层操作系统中提供的PageCache功能,将数据信息缓存在Linux内核的内存中,当需要查询数据时会优先查询PageCache,如果缓存中没有则会去磁盘中查询到并刷写到PageCache中
零拷贝:当消费者消费数据时,Kafka通过PageCache查询到数据后,会直接数据复制到Socket Cache中在通过网卡发送数据,而不需要在将数据返回到Kafka的应用层,减少了一次IO操作
内核态:处于内核态的 CPU 可以访问任意的数据,包括外围设备,比如网卡、硬盘等,处于内核态的 CPU 可以从一个程序切换到另外一个程序,并且占用 CPU 不会发生抢占情况,一般处于特权级 0 的状态我们称之为内核态。
用户态:处于用户态的 CPU 只能受限的访问内存,并且不允许访问外围设备,用户态下的 CPU 不允许独占,也就是说 CPU 能够被其他程序获取。
通俗来说用户态就是Kafka应用层,有受限的访问权限,内核态是Linux的内核,具有访问网卡,读取磁盘等权限等特级权限
消费者
31.Kafka消费数据的两种方式
32.消费者工作流程
1)一个独立的消费者消费多个分区的数据
2)一个分区的数据只能有消费者组中的一个消费者进行消费,其他消费者不能消费此分区的数据
3)消费者消费到分区数据的位置即为offset,新版本Kafka的offset由消费者提交保存到Kafka系统主题中,由Kafka来进行保存,旧版Kafka会将offset将数据保存到Zookeeper中,由于在查询位置时会和Zookeeper进行大量网络通信,造成资源浪费,所以被弃用,有Kafka自行保存
33.消费者组
由多个consumer组成,形成一个消费者组的条件,是所有消费者的groupId相同
消费者组内每个消费者负责消费不同分区的数据,一个分区只能有一个组内消费者消费
消费者组之间互不影响,所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者
当消费者组中的消费者数量大于分区数量时,则会与一部分消费者闲置,不会接受任何消息
34.消费者组初始化流程
1)coordinator:辅助实现消费者组的初始化和分区的分配,每一个broker都有coordinator
coordinator节点选择 = groupid的hashcode值 % 50 (_consumer_offsets的分区数量)
例如:groupId的hashcode值=1 ,1%50 =1,那么_consumer_offsets主题的1号分区,在那个broker上,就选择这个节点的coordinator作为这个消费者组的老大,消费者组下的所有消费者提交offset的时候,就往这个分区区提交offset
2)消费者组分配流程
- 每个消费者都会发送JoinGroup请求到coordinatore中,Kafka通过计算后决定将这些请求发送到某个broker节点中的coordinator用来分配消费者组的辅助组件
- coorainator会随机选取消费者组中的一个consumer作为 Leader Consumer,并将要消费的Topic情况发送给Leader Consumer
- Leader Consumer会负责制定出消费方案,来决定消费者组中的消费者对应消费哪个分区,并将消费方案发还给coordinator
- coordinator会将消费方案发送给其他消费者组中的消费者
- 每个消费者都会和coorinator保持心跳(默认3s),一旦超过(session.timeout.ms=45s),该消费者会被移除,并触发再平衡,其他消费者会完成该消费者的任务,或者消费者处理消息的时间过长(max.poll.interval.ms 默认5分钟)也会触发再平衡
35.消费者组消费流程
1)消费者组发送sendFetches请求,创建网络连接客户端(ConsumerNetworkClient),当以下参数任意条件满足,都会拉取数据
Fetch.min bytes 每批次最小抓取大小 默认1字节
fetch.max.wait.ms 一批数据最小值未达到的超时时间 默认500ms
Fetch.max.bytes 每批次最大抓取大小 默认50M
2)ConsumerNetworkClient通过回调函数将数据拉取过来,存储到消息队列中
3)FetchedRecords从队列中抓取数据,一次拉取数据返回消息的最大条数默认为500条
4)数据会先经过反序列化–>拦截器–>处理数据
36.消费者组消费API
独立消费者
/**
* 独立消费者 消费信息数据
* @author 昭浅
*/
public class CustomConsumer {
public static void main(String[] args) {
// 0 配置
Properties properties = new Properties();
// 连接bootstrap-server
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.114.100:9092,192.1168.114.101:9092");
// 反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
// 配置 groupId 单个独立消费者也必须配置
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
// 1.创建一个消费者和
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// 2.定义消费的主题 first
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
// 3.消费数据
while (true){
ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofSeconds(1));
records.forEach(record->{
System.out.println(record);
});
}
}
}
独立消费者消费指定分区
/**
* 独立消费者 消费指定分区的数据
* @author 昭浅
*/
public class CustomConsumerPartition {
public static void main(String[] args) {
// 0 配置
Properties properties = new Properties();
// 连接bootstrap-server
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.114.100:9092,192.1168.114.101:9092");
// 反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
// 配置 groupId 单个独立消费者也必须配置
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
// 1.创建一个消费者和
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// 2. 指定主题 以及消费的分区
ArrayList<TopicPartition> topicPartitions = new ArrayList<>();
topicPartitions.add(new TopicPartition("first",0));
kafkaConsumer.assign(topicPartitions);
// 3.消费数据
while (true){
ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofSeconds(1));
records.forEach(record->{
System.out.println(record);
});
}
}
}
消费者组案例
创建三个相同groupId的消费者,接收数据,会发现不同消费者消费不同分区的数据
37.消费者分区分配策略
1)Kafka有四种主流的分区分配策略:Range
、RoundRobin
、Sticky
(黏性)、CooperativeSticky
(合作者黏性)
可以通过修改配置参数 partition.assignment.strategy,修改分区的分配策略,默认策略是Range + CooperativeSticky,Kafka可以同时使用多个分区分配策略
2)Range :Range是对每个topic而言的,首先对同一个topic里面的分区按照序号进行排序,并对消费者按照字母顺序进行排序,通过partitions数/consumer数来决定每个消费者应该消费几个分区,如果除不尽,那么前面几个消费者将会多消费一个分区
注意
:当消费者组中的某个消费者宕机后,45s中内,该消费者负责的分区会统一落到某一消费者身上,当超过45s后,消费者组中的消费者会重新进行Range重新分配一次
3)RoundRobin :针对集群中所有得到Topic而言,RoundRobin轮询分区策略,是将所有topic中的所有partition和consumer都根据字典顺序列出来,然后按照轮询算法来分配partition给各个消费者
注意
:当消费者组中的某个消费者宕机后,45s中内,该消费者负责的分区会轮询分发到其他消费者身上,45s后,消费者组会重新轮询进行分区分配
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,“org.apache.kafka.clients.consumer.RoundRobinAssignor”);
4)Sticky以及再平衡:黏性(随机)分区,可以理解为分配的结果待带有黏性的,即在执行一次新的分配之前,考虑上一次分配的结果,尽量少的调整分配的变动,即Sticky分区策略是和Range策略相似的策略,只是在平均分配分区时,Sticky策略并不是按照顺序分配分区的,而是随机选择分区,均匀且随机
注意
:当消费者组中的某个消费者宕机后,45s中内,该消费者负责的分区会黏性分发到其他消费者身上,45s后,消费者组会重新进行黏性分区分配
38.offset 位移
1)offset默认维护位置
- 自0.9版本开始,consumer默认将offset保存在Kafka一个内置的topic中,该topic为_consumer-offsets
- 在0.9版本之前,consumer将offset保存在Zookeeper中
__consumer_offsets
:Kafka的内置主题,consumer将消费到的Consumer_Offset存储到这个topic中,该主题使用key和value的方式存储数据,key是group.id+topic+分区号,value就是当前offset的值,每隔一段时间,Kafka内部会对这个topic进行compact(压缩),就是每个key(group.id+topic+分区号)只保留最新数据
想要查看该主题的数据,需要将consumer的配置文件添加配置项,在配置文件 config/consumer.properties 中添加配置 exclude.internal.topics=false,默认是 true,表示不能消费系统主题。为了查看该系统主题数据,所以该参数修改为 false。
2)自动提交 offset
为了使我们能够专注于自己的业务逻辑,Kafka提供了自动提交的功能,我们无需向__consumner_offsets
enable.auto.commit:是否开启自动提交offset功能,默认是true
auto.commit.interval.ms:自动提交offset的时间间隔,默认是5s
3)手动提交 offset
自动提交offset十分方便,但是由于是基于时间提交,开发人员难以把握offset提交的时机,Kafka提供了手动提交的API
commitSync
(同步提交):将本次提交的一批数据最高的偏移量提交,同步提交会阻塞当前线程,知道提交成功,如果提交失败,则会自动失败重试
commitAsync
(异步提交):将本次提交的一批数据最高的偏移量提交,不会阻塞当前线程,没有自动失败重试机制,故而可能提交失败
// 配置手动提交 offset
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,false);
// 在消费者拉取数据后 手动提交offset
// 同步提交
//kafkaConsumer.commitSync();
// 异步提交 offset
kafkaConsumer.commitAsync();
4)指定 offset 消费
auto.offset.rest
= eraliest | latest | none 默认是 latest
当Kafka 中没有初始偏移量(消费者是第一次消费)或服务器上不再存在当前偏移量时(例如当前数据已经被删除)
earliest
:自动将偏移量重置为最早的偏移量 --from-beginning,消费者可以获取到最早的数据latest(默认值)
:自定将偏移量重置为最新偏移量,消费者只能从最新消息开始获取none
:如果未找到消费者组的先前偏移量,则向消费者抛出异常
/**
* 指定 offset开始消费
* @author 昭浅
*/
public class CustorConsumerSeek {
public static void main(String[] args) {
// 0 配置信息
Properties properties = new Properties();
// 连接 bootstrap-server
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.114.100:9092,192.168.114.100:9092");
// 配置反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// 配置 group id
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test6");
// 1.创建消费者
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// 2.订阅主题
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
// 指定位置进行消费
// 遍历所有分区信息 指定分区文件中的offset进行消费
Set<TopicPartition> assignment = kafkaConsumer.assignment();
// 保证分区分配方案已经制定完毕
while(assignment.size()==0){
kafkaConsumer.poll(Duration.ofSeconds(1));
assignment = kafkaConsumer.assignment();
}
assignment.forEach(assign ->{
kafkaConsumer.seek(assign,600);
});
// 3.消费 拉取数据
while (true){
ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofSeconds(1));
records.forEach(record ->{
System.out.println(record);
});
}
}
}
5)指定 时间 进行消费
需求
:在生产环境中,会遇到最近消费的几个小时数据异常,想要重新按照时间消费,例如要求按照时间消费前一天的数据,需要怎样处理
/**
* 指定 offset开始消费
* @author 昭浅
*/
public class CustorConsumerSeekByTime {
public static void main(String[] args) {
// 0 配置信息
Properties properties = new Properties();
// 连接 bootstrap-server
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.114.100:9092,192.168.114.100:9092");
// 配置反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// 配置 group id
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test8");
// 1.创建消费者
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// 2.订阅主题
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
// 指定位置进行消费
// 遍历所有分区信息 指定分区文件中的offset进行消费
Set<TopicPartition> assignment = kafkaConsumer.assignment();
// 保证分区分配方案已经制定完毕
while(assignment.size()==0){
kafkaConsumer.poll(Duration.ofSeconds(1));
assignment = kafkaConsumer.assignment();
}
// 把时间转换为对用的offset
HashMap<TopicPartition, Long> topicPartitionLongHashMap = new HashMap<>();
// 封装对应集合 分区-》offset
assignment.forEach(topicPartition -> {
topicPartitionLongHashMap.put(topicPartition,System.currentTimeMillis()-1 *24 * 3600 * 1000);
});
// 通过时间转换获取分区对应时间的offset集合
Map<TopicPartition, OffsetAndTimestamp> topicPartitionOffsetAndTimestampMap = kafkaConsumer.offsetsForTimes(topicPartitionLongHashMap);
assignment.forEach(topicPartition ->{
// 获取当前分区对应 时间的offset
OffsetAndTimestamp offsetAndTimestamp = topicPartitionOffsetAndTimestampMap.get(topicPartition);
kafkaConsumer.seek(topicPartition,offsetAndTimestamp.offset());
});
// 3.消费 拉取数据
while (true){
ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofSeconds(1));
records.forEach(record ->{
System.out.println(record);
});
}
}
}
39.漏消费和重复消费
重复消费:已经消费了数据,但是 offset 没提交。
漏消费:先提交 offset 后消费,有可能会造成数据的漏消费
40.生产经验——消费者事务
41.生产经验——数据积压(消费者如何提高吞吐量)
42.Kafka-Eagle 监控
Kafka-Eagle 框架可以监控Kafka集群的整体运行情况,在生产环境中经常使用
1)Mysql环境准备:Kafka-Eagle的安装依赖Mysql,Mysql主要用来存储可视化展示的数据
2)Kafka环境准备:
1. vim bin/kafka-server-start.sh 修改如下参数值,改变Kafka启动内存大小:
if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then
export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G"
fi 为
if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then
export KAFKA_HEAP_OPTS="-server -Xms2G -Xmx2G -
XX:PermSize=128m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -
XX:ParallelGCThreads=8 -XX:ConcGCThreads=5 -
XX:InitiatingHeapOccupancyPercent=70"
export JMX_PORT="9999"
#export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G"
fi
修改后,将文件分发到其他服务器,
3) Kafka-Eagle 安装
安装后修改配置文件,配置ZK地址,配置Mysql,将offset保存到kafka,运行eagle,登入eagle并查看
43.Kafka-Kraft 模式
左图为 Kafka 现有架构,元数据在 zookeeper 中,运行时动态选举 controller,由
controller 进行 Kafka 集群管理。右图为 kraft 模式架构(实验性),不再依赖 zookeeper 集群,
而是用三台 controller 节点代替 zookeeper,元数据保存在 controller 中,由 controller 直接进
行 Kafka 集群管理。
整合SpringBoot
44.SpringBoot整合Kafka
1)SpringBoot整合Kafka 生产者
- 安装lombok插件 方便使用
- 创建SpringBoot项目,修改配置项
# 连接kafka集群配置
spring.kafka.bootstrap-servers=192.168.114.100:9092,192.168.114.101:9092,192.168.114.102:9092
#key value 的序列化
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
- 创建controller 使用KafkaTemplate.send() 发送数据,并在服务器端创建消费者接收数据进行测试
@RestController
public class ProducerController {
@Autowired
KafkaTemplate<String,String> kafkaTemplate;
@RequestMapping("/atguigu")
public String data(String msg){
kafkaTemplate.send("first",msg);
return "ok";
}
}
1)SpringBoot整合Kafka 消费者
- 安装lombok插件 方便使用
- 创建SpringBoot项目,修改配置项
# 连接kafka集群配置
spring.kafka.bootstrap-servers=192.168.114.100:9092,192.168.114.101:9092,192.168.114.102:9092
# key value 反序列化
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
# 消费者组id groupid
spring.kafka.consumer.group-id=test1
- 创建消费者配置类,监听Kafka 发送数据,并在网页发送数据接受测试
// 将消费者定义成配置类,监听会一直生效
@Configuration
public class KafkaConsumer {
// kafka注解 消费消息
@KafkaListener(topics="first")
public void consumerTopic(String msg){
System.out.println("收到消息:"+msg);
}
}
45.集群压力测试
1)Kafka压测:使用Kafka官方自带的脚本,对Kafka进行压测
生产者压测:kafka-producer-perf-test.sh
消费者压测:kafka-consumer-perf-test.sh
生产者压力测试
[atguigu@hadoop105 kafka]$ bin/kafka-producer-perf-test.sh --topic test --record-size 1024 --num-records 1000000 --throughput
10000 --producer-props bootstrap.servers=hadoop102:9092,hadoop103:9092,hadoop104:9092 batch.size=16384 linger.ms=0 compression.type=lz4
消费者压力测试
[atguigu@hadoop105 kafka]$ bin/kafka-consumer-perf-test.sh --bootstrap-server hadoop102:9092,hadoop103:9092,hadoop104:9092
–topic test --messages 1000000 --consumer.config config/consumer.properties