Kafka是最初由Linkedin公司开发,是⼀个分布式
、⽀持分区的
(partition)、多副本的
(replica),基于zookeeper协调
的分布式消息系统
,它的最⼤的特性就是可以实时的处理⼤量数据以满⾜各种需求场景:⽐如基于hadoop的批处理系统、低延迟的实时系统、Storm/Spark流式处理引擎,web/nginx⽇志、访问⽇志,消息服务等等,⽤scala语⾔编写,Linkedin于2010年贡献给了Apache基⾦会并成为顶级开源 项⽬。
Kafka传统定义:Kafka是一个分布式的基于发布/订阅模式
的消息队列(MessageQueue),主要应用于大数据实时处理领域.
发布/订阅:消息的发布者不会将消息直接发送给特定的订阅者,而是将发布的消息分为不同的类别,订阅者只接收感兴趣的消息
。
Kafka最新定义 : Kafka是 一个开源的分布式事件流平台 (Event Streaming Platform),被数千家公司用于高性能数据管道、流分析、数据集成和关键任务应用。
传统的消息队列的主要应用场景包括:缓存/消峰、解耦和异步通信.
缓冲/消峰
:有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。
解耦
:允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
异步通信:允许用户把一个消息放入队列,但并不立即处理它,然后在需要的时候再去处理它们。
(1)Producer:消息生产者
,就是向 Kafka broker 发送消息的客户端。
(2)Consumer:消息消费者
,向 Kafka broker 读取消息的客户端。
(3)Consumer Group(CG):消费者组
,由多个 consumer 组成。消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费
;消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
(4) Broker: 消息中间件处理节点,一个kafka节点就是一个Broker
,一个或者多个Broker可以组成一个Kafka集群.
(5)Topic:主题
,kafka根据topic对消息进行归类,发布到Kafka集群的每条消息都需要指定一个topic.
(6)Partition:为了实现扩展性,一个非常大的 topic 可以分布到多个 broker(即服务器)上,一个 topic 可以分为多个 partition
,每个 partition 内部消息是有序的
.
(7)Replica:副本。一个 topic 的每个分区都有若干个副本,一个 Leader 和若干个Follower。
(8)Leader:每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数据的对象都是 Leader。
(9)Follower:每个分区多个副本中的“从”,实时从 Leader 中同步数据
,保持和Leader 数据的同步。Leader 发生故障时,某个 Follower 会成为新的 Leader。
(10)offset偏移量
a.可以唯一的标识一条消息
b.偏移量决定读取数据的位置,不会有线程安全的问题,消费者通过偏移量来决定下次读取的消息
c.消息被消费之后,并不被马上删除,这样多个业务就可以重复使用kafka的消息
d.我们某一个业务也可以通过修改偏移量达到重新读取消息的目的,偏移量由用户控制
e.消息最终还是会被删除的,默认生命周期为1周
hadoop102 | hadoop103 | hadoop104 |
---|---|---|
zookeeper | zookeeper | zookeeper |
kafka | kafka | kafka |
2.集群部署
1)官方下载地址:http://kafka.apache.org/downloads.html
2)将安装包上传,并将其解压到/opt/module目录下
3)修改解压后的文件名称
4)进入到/opt/module/kafka-3.0.0/config 目录,修改server.properties配置文件
修改以下内容:
#broker 的全局唯一编号,不能重复,只能是数字。
broker.id=0 #修改此处
#处理网络请求的线程数量
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 运行日志(数据)存放的路径,路径不需要提前创建,kafka 自动帮你创建,可以配置多个磁盘路径,路径与路径之间可以用","分隔
log.dirs=/opt/module/kafka-3.0.0/datas #修改此处
#topic 在当前 broker 上的分区个数
num.partitions=1
#用来恢复和清理 data 下数据的线程数量
num.recovery.threads.per.data.dir=1
# 每个 topic 创建时的副本数,默认时 1 个副本
offsets.topic.replication.factor=1
#segment 文件保留的最长时间,超时将被删除
log.retention.hours=168
#每个 segment 文件的大小,默认最大 1G
log.segment.bytes=1073741824
# 检查过期数据的时间,默认 5 分钟检查一次是否数据过期
log.retention.check.interval.ms=300000
#配置连接 Zookeeper 集群地址(在 zookeeper 根目录下创建/kafka,方便管理)
zookeeper.connect=hadoop102:2181,hadoop103:2181,hadoop104:2181/kafka #修改此处
#!/bin/bash
if [ $# -lt 1 ]
then
echo "Not Enough Arguement!"
exit;
fi
for host in hadoop102 hadoop103 hadoop104
do
echo =========== $host ===========
for file in $@
do
if [ -e $file ]
then
pdir=$(cd -P $(dirname $file);pwd)
fname=$(basename $file)
ssh $host "mkdir -p $pdir"
rsync -av $pdir/$fname $host:$pdir
else
echo $file does not exists!
fi
done
done
6)分别在 hadoop103 和 hadoop104 上修改配置文件/opt/module/kafka-3.0.0/config/server.properties中的 broker.id=1、broker.id=2
注:broker.id 不能重复,整个集群中唯一
[hyj@hadoop103 config]$ vim server.properties
修改:
# The id of the broker. This must be set to a unique integer for each broker.
broker.id=1
[hyj@hadoop104 config]$ vim server.properties
修改:
# The id of the broker. This must be set to a unique integer for each broker.
broker.id=2
7)配置环境变量
a. 在/etc/profile.d/my_env.sh 文件中增加 kafka 环境变量配置
添加如下内容:
# KAFKA_HOME
export KAFKA_HOME=/opt/module/kafka-3.0.0
export PATH=$PATH:$KAFKA_HOME/bin
b. 刷新一下环境变量
c. 分发环境变量文件my_env.sh到其他节点,并 source使环境变量生效
[hyj@hadoop102 kafka-3.0.0]$ sudo /home/hyj/bin/xsync /etc/profile.d/my_env.sh
[hyj@hadoop103 kafka-3.0.0]$ source /etc/profile
[hyj@hadoop104 kafka-3.0.0]$ source /etc/profile
8)启动集群
a. 先启动 Zookeeper 集群,然后启动 Kafka
[hyj@hadoop102 kafka-3.0.0]$ zk.sh start
zk.sh脚本的内容:
#!/bin/bash
if [ $# -lt 1 ]
then
echo "Not Enough Arguement!"
exit;
fi
case $1 in
"start"){
for i in hadoop102 hadoop103 hadoop104
do
echo ========= zookeeper $i 启动 ==========
ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh start"
done
};;
"stop"){
for i in hadoop102 hadoop103 hadoop104
do
echo ========= zookeeper $i 停止 =========
ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh stop"
done
};;
"status"){
for i in hadoop102 hadoop103 hadoop104
do
echo ========== zookeeper $i 状态 ==========
ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh status"
done
};;
esac
b. 依次在 hadoop102、hadoop103、hadoop104 节点上启动 Kafka
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-server-start.sh -daemon config/server.properties
[hyj@hadoop103 kafka-3.0.0]$ bin/kafka-server-start.sh -daemon config/server.properties
[hyj@hadoop104 kafka-3.0.0]$ bin/kafka-server-start.sh -daemon config/server.properties
注意:配置文件的路径要能够到 server.properties
9)关闭集群
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-server-stop.sh
[hyj@hadoop103 kafka-3.0.0]$ bin/kafka-server-stop.sh
[hyj@hadoop104 kafka-3.0.0]$ bin/kafka-server-stop.sh
3.集群启停脚本
a. 在/home/hyj/bin 目录下创建文件 mykafka.sh 脚本文件
[hyj@hadoop102 kafka-3.0.0]$ cd
[hyj@hadoop102 ~]$ pwd
/home/hyj
[hyj@hadoop102 ~]$ cd bin/
[hyj@hadoop102 bin]$ vim mykafka.sh
添加如下内容:
#! /bin/bash
if [ $# -lt 1 ]
then
echo "Not Enough Arguement!"
exit;
fi
case $1 in
"start"){
for i in hadoop102 hadoop103 hadoop104
do
echo " --------启动 $i Kafka-------"
ssh $i "/opt/module/kafka-3.0.0/bin/kafka-server-start.sh -daemon /opt/module/kafka-3.0.0/config/server.properties"
done
};;
"stop"){
for i in hadoop102 hadoop103 hadoop104
do
echo " --------停止 $i Kafka-------"
ssh $i "/opt/module/kafka-3.0.0/bin/kafka-server-stop.sh "
done
};;
esac
b. 给mykafka.sh脚本添加执行权限
[hyj@hadoop102 bin]$ chmod +x mykafka.sh
c. 分发mykafka.sh脚本
[hyj@hadoop102 bin]$ xsync mykafka.sh
d. 启动集群命令
[hyj@hadoop102 kafka-3.0.0]$ mykafka.sh start
e. 停止集群命令
[hyj@hadoop102 kafka-3.0.0]$ mykafka.sh stop
注意:停止 Kafka 集群时,一定要等 Kafka 所有节点进程全部停止后再停止 Zookeeper集群。因为 Zookeeper 集群当中记录着 Kafka 集群相关信息,Zookeeper 集群一旦先停止,Kafka 集群就没有办法再获取停止进程的信息,只能手动杀死 Kafka 进程了。
[hyj@hadoop102 kafka-3.0.0]$ mykafka.sh start
2)查看操作主题命令参数
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh
参数 | 描述 |
---|---|
--bootstrap-server |
连接的Kafka Broker主机名称和端口号 |
--topic |
操作的topic名称 |
--create |
创建主题 |
--delete |
删除主题 |
--alter |
修改主题 |
--list |
查看所有主题 |
--describe |
查看主题详细描述 |
--partitions |
设置分区数 |
--replication-factor |
设置分区副本 |
--config |
更新系统默认的配置 |
3)查看当前服务器中的所有 topic
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --list
4)创建 first topic
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --create --partitions 1 --replication-factor 3 --topic first
选项说明:
--topic 定义 topic 名
--replication-factor 定义副本数
--partitions 定义分区数
5)查看 first 主题的详情
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic first --describe
6)修改分区数(注意:分区数只能增加,不能减少
)
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --alter --topic first --partitions 3
7)删除first topic
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --delete --topic first
1)查看操作生产者命令参数
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-producer.sh
参数 | 描述 |
---|---|
--bootstrap-server |
连接的Kafka Broker主机名称和端口号 |
--topic |
操作的topic名称 |
2)发送消息
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-producer.sh --bootstrap-server hadoop102:9092 --topic first
1)查看操作消费者命令参数
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-consumer.sh
参数 | 描述 |
---|---|
--bootstrap-server |
连接的Kafka Broker主机名称和端口号 |
--topic |
操作的topic名称 |
--from-beginning |
从头开始消费 |
--group |
指定消费者组名称 |
2)消费消息
(1)消费 first 主题中的数据
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic first
(2)把主题中所有的数据都读取出来(包括历史数据)
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic first --from-beginning
1.发送原理
在消息发送的过程中,涉及到了两个线程——main 线程和 Sender 线程
。在 main 线程中创建了一个双端队列 RecordAccumulator
。main 线程将消息发送给 RecordAccumulator,Sender 线程不断从 RecordAccumulator 中拉取消息
发送到 Kafka Broker。
2.发送流程
• producer通过⽹络发送消息到Kafka集群,然后consumer来进⾏消费,服务端(brokers)和客户端(producer、consumer)之间通信通过TCP协议来完成。所以需要Serializer序列化器.
• Partitioner分区器决定着数据发往哪个分区.
• 当Partitioner分区器向双端队列 RecordAccumulator发送数据时,会创建批次大小,从内存池中取出内存,当这些数据发送到kafka集群之后,就会把内存释放到内存池当中.
• 当数据成功发送到kafka集群并应答之后,会清掉请求,清掉双端队列 RecordAccumulator当中对应的数据.
好文在此:《Kafka-batch.size属性》
3.生产者重要参数列表
参数名称 | 描述 |
---|---|
bootstrap.servers | 生产者连接集群所需的 broker 地 址 清 单 。 例 如hadoop102:9092,hadoop103:9092,hadoop104:9092,可以设置 1 个或者多个,中间用逗号隔开。注意这里并非需要所有的 broker 地址,因为生产者从给定的 broker里查找到其他 broker 信息 |
key.serializer 和 value.serializer | 指定发送消息的 key 和 value 的序列化类型,一定要写全类名 |
buffer.memory | RecordAccumulator 缓冲区总大小,默认 32m |
batch.size | 缓冲区一批数据最大值,默认 16k。适当增加该值,可以提高吞吐量,但是如果该值设置太大,会导致数据传输延迟增加 |
linger.ms | 如果数据迟迟未达到 batch.size,sender 等待 linger.time之后就会发送数据。单位 ms,默认值是 0ms,表示没有延迟。生产环境建议该值大小为 5-100ms 之间 |
acks | 0:生产者发送过来的数据,不需要等数据落盘应答。1:生产者发送过来的数据,Leader 收到数据后应答。-1(all):生产者发送过来的数据,Leader和 isr 队列里面的所有节点收齐数据后应答。默认值是-1,-1 和all 是等价的 |
max.in.flight.requests.per.connection | 允许最多没有返回 ack 的个数,默认为 5,开启幂等性要保证该值是 1-5 的数字 |
retries | 当消息发送出现错误的时候,系统会重发消息。retries表示重试次数。默认是 int 最大值,2147483647。如果设置了重试,还想保证消息的有序性,需要设置MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION=1否则在重试此失败消息的时候,其他的消息可能发送成功了 |
retry.backoff.ms | 两次重试之间的时间间隔,默认是 100ms |
enable.idempotence | 是否开启幂等性,默认 true,开启幂等性 |
compression.type | 生产者发送的所有数据的压缩方式。默认是 none,也就是不压缩。支持压缩类型:none、gzip、snappy、lz4 和 zstd |
1.需求:创建 Kafka 生产者,采用异步的方式发送到 Kafka Broker
2.代码编写
(1)创建工程 kafka
(2)添加如下依赖
<dependencies>
<dependency>
<groupId>org.apache.kafkagroupId>
<artifactId>kafka-clientsartifactId>
<version>3.0.0version>
dependency>
dependencies>
(3)创建包名:com.hyj.kafka.producer
(4)编写不带回调函数的 API 代码
package com.hyj.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.common.serialization.StringSerializer;
import java.util.Properties;
public class CustomProducer {
public static void main(String[] args) {
// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 2. 给 kafka 配置对象添加配置信息:bootstrap.servers连接集群
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092");
// 指定对应的key,value的序列化类型(必须):key.serializer,value.serializer
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); 把发送的key从字符串序列化为字节数组
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName()); 把发送消息value从字符串序列化为字节数组
/* 等价写法:
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringSerializer");
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringSerializer");
*/
// 3. 创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);
// key:"" value:"hello kafka"+i
for (int i = 0; i < 5; i++) {
// 4. 调用 send 方法,发送消息 (topic,value)
kafkaProducer.send(new ProducerRecord<>("first","hello kafka"+i));
}
//关闭资源
kafkaProducer.close();
}
}
(5)测试:
①在 hadoop102 上开启 Kafka 消费者。
②在 IDEA 中执行代码,观察 hadoop102 控制台中是否接收到消息。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic first
hello kafka0
hello kafka1
hello kafka2
hello kafka3
hello kafka4
回调函数会在 producer 收到 ack 时调用,为异步调用,该方法有两个参数,分别是元数据信息(RecordMetadata)
和异常信息(Exception)
,如果 Exception 为 null,说明消息发送成功,如果 Exception 不为 null,说明消息发送失败.
注意:消息发送失败会自动重试,不需要我们在回调函数中手动重试。
1.编写API代码
package com.hyj.kafka.producer;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class CustomProducerCallback {
public static void main(String[] args) throws InterruptedException {
// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 2. 给 kafka 配置对象添加配置信息:bootstrap.servers连接集群
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092");
// 指定对应的key,value的序列化类型(必须):key.serializer,value.serializer
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 3. 创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);
// key:"" value:"hello kafka"+i
for (int i = 0; i < 5; i++) {
// 4. 调用 send 方法,发送消息 (topic,value)
kafkaProducer.send(new ProducerRecord<>("first", "hello kafka" + i), new Callback() {
//该方法在Producer收到ack时调用,为异步调用
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if(e == null){
//没有异常,输出信息到控制台
System.out.println("主题:"+recordMetadata.topic()+"->"+"分区:"+recordMetadata.partition());
}else{
//出现异常打印
e.printStackTrace();
}
}
});
//延迟一会 会看到数据发往不同分区
Thread.sleep(2);
}
//5.关闭资源
kafkaProducer.close();
}
}
2.测试:
①在 hadoop102 上开启 Kafka 消费者
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic first
②在 IDEA 中执行代码,观察 hadoop102 控制台中是否接收到消息
③在 IDEA 控制台观察回调信息
• replica.lag.time.max.ms:如果一个follower在这时间内没有发送任何通信请求,或者至少在这个时间内没有消耗完leader日志结束偏移量(同步完所有数据),leader将从isr中移除follower.
• 每个partition(分区)之中的每条消息都会被赋予一个叫做offset(偏移量)
的顺序id编号,用于标识它在当前分区日志中的位置。
• request.timeout.ms
:配置控制客户端等待请求响应的最大时间
。如果在超时之前没有收到响应,则客户端将在必要时重新发送请求,或在重试耗尽时使请求失败。这个值应该大于replica.lag.time.max.ms(broker配置),以减少由于不必要的生产者重试而导致的消息重复的可能性。
同步发送
:一定是逐条发送的,第一条响应到达后,才会发送第二条(发送一条消息,所有的后续工作完成以后才能继续下一条消息的发送).
如果需要使用同步发送,可以在每次发送之后使用get方法,因为producer.send方法返回一个Future类型的结果,Future的get方法会一直阻塞直到该线程的任务得到返回值,也就是broker返回发送成功。
因为batch.size默认是16k,如果一条消息的大小<16k,那么此时就会等linger.ms(默认0ms,无延迟)设置的时间到sender才会发送消息.
异步发送
:可以发送一条,也可以批量发送多条,特性是不需等第一次(注意这里单位是次,因为单次可以是单条,也可以是批量数据)响应,就立即发送第二次消息.
当用户调用send()时,就完成数据发送了(对于用户来说),后台线程负责实际发送数据.
同步和异步概念 分为用户线程和发送线程,用户线程有同步和异步之分;发送线程只有异步
用户可以通过send().get() ,把用户主线程改为同步方式(默认为异步方式)
同步发送:send()方法每次只能发送一条数据至InFlightRequest队列
用户线程选择同步
,效果是逐条发送,因为请求队列InFlightRequest中永远最多有一条数据
。
参考链接:【Kafka】深入图解Kafka producer 发送过程
同步发送:只需在异步发送的基础上,再调用一下 get()方法即可
1.代码编写:
package com.hyj.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.common.serialization.StringSerializer;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
public class CustomProducerSync {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 2. 给 kafka 配置对象添加配置信息
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092");
// key,value 序列化(必须):key.serializer,value.serializer
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// 3. 创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
// 4. 调用 send 方法,发送消息
for (int i = 0; i < 10; i++) {
//异步发送(默认): kafkaProducer.send(newProducerRecord<>("first","kafka" + i));
// 同步发送
kafkaProducer.send(new ProducerRecord<>("first","kafka" + i)).get();
}
// 5. 关闭资源
kafkaProducer.close();
}
}
2.测试:
①在 hadoop102 上开启 Kafka 消费者。
②在 IDEA 中执行代码,观察 hadoop102 控制台中是否接收到消息。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic first
kafka0
kafka1
kafka2
kafka3
kafka4
kafka5
kafka6
kafka7
kafka8
kafka9
package com.hyj.kafka.producer;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class CustomProducerCallbackPartitions {
public static void main(String[] args) {
// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 2. 给 kafka 配置对象添加配置信息:bootstrap.servers连接集群
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092");
// 指定对应的key,value的序列化类型(必须):key.serializer,value.serializer
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer .class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 3. 创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);
for (int i = 0; i < 5; i++) {
// 指定数据发送到 1 号分区,key 为空(IDEA 中 ctrl + p 查看参数)
kafkaProducer.send(new ProducerRecord<>("first", 1,"","kafka" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception e) {
if (e == null){
System.out.println(" 主题: " + metadata.topic() + "->" + "分区:" + metadata.partition());
}else {
e.printStackTrace();
}
}
});
}
//关闭资源
kafkaProducer.close();
}
}
测试:
①在 hadoop102 上先修改first主题的分区数,再开启 Kafka 消费者。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --alter --topic first --partitions 3
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic first
②在 IDEA 中执行代码,观察 hadoop102 控制台中是否接收到消息。
③在 IDEA 控制台观察回调信息。
4.案例二
没有指明 partition 值但有 key 的情况下,将 key 的 hash 值与 topic 的partition 数进行取余得到 partition 值。
package com.hyj.kafka.producer;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class CustomProducerCallback1 {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
for (int i = 0; i < 5; i++) {
// 依次指定 key 值为 a,b,f ,数据 key 的 hash 值与 3 个分区求余,分别发往 1、2、0
kafkaProducer.send(new ProducerRecord<>("first", "b","kafka" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception e) {
if (e == null){
System.out.println(" 主题: " + metadata.topic() + "->" + "分区:" + metadata.partition());
}else {
e.printStackTrace();
}
}
});
}
kafkaProducer.close();
}
}
测试:
①key="a"时,在控制台查看结果。
②key="b"时,在控制台查看结果。
③key="f"时,在控制台查看结果。
1.需求
例如:我们实现一个分区器,实现发送过来的数据中如果包含 kafka,就发往 0 号分区,不包含kafka,就发往 1 号分区。
2.实现步骤
(1)定义类实现 Partitioner 接口。
(2)重写 partition()方法。
package com.hyj.kafka.producer2;
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import java.util.Map;
/**
* 1. 实现接口 Partitioner
* 2. 实现 3 个方法:partition,close,configure
* 3. 编写 partition 方法,返回分区号
*/
public class MyPartitioner implements Partitioner {
/*** 返回信息对应的分区
* @param topic 主题
* @param key 消息的 key
* @param keyBytes 消息的 key 序列化后的字节数组
* @param value 消息的 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();
// 创建 partition
int partition;
// 判断消息是否包含kafka
if (msgValue.contains("kafka")) {
partition = 0;
} else {
partition = 1;
}
// 返回分区号
return partition;
}
// 关闭资源
@Override
public void close() {
}
// 配置方法
@Override
public void configure(Map<String, ?> configs) {
}
}
(3)使用分区器的方法,在生产者的配置中添加分区器参数。
package com.hyj.kafka.producer2;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class CustomProducerCallbackPartitions {
public static void main(String[] args) throws InterruptedException {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// 添加自定义分区器
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.hyj.kafka.producer2.MyPartitioner");
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("first", "kafka" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception e) {
if (e == null){
System.out.println(" 主题: " + metadata.topic() + "->" + "分区:" + metadata.partition()
);
}else {
e.printStackTrace();
}
}
});
}
kafkaProducer.close();
}
}
(4)测试
①在 hadoop102 上开启 Kafka 消费者。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic first
②在 IDEA 控制台观察回调信息。
参考链接:如何提升 Kafka 生产者的吞吐量?
可以修改以下4个参数:
batch.size
:批次大小,默认16k (也就是一个batch满了16kb就发送出去).如果 batch 太小,会导致频繁网络请求,吞吐量下降;linger.ms
:等待时间,默认值是0ms(意思就是消息立即被发送,不延迟,来一条发送一条),但是这是不对的。可以将其修改为5-100ms.假如linger.ms设置为为50ms,消息被发送出去后会进入一个batch,如果50ms内,这个batch满了16kb就会被发送出去。但是如果50ms时间到,batch没满,那么也必须把消息发送出去了,不能让消息的发送延迟时间太长,也避免给内存造成过大的一个压力。compression.type
:默认是none,不压缩,可以使用lz4,snappy等压缩,压缩之后可以减小数据量,提升吞吐量,但是会加大producer端的cpu开销。RecordAccumulator(buffer.memory)
:设置发送消息的缓冲区,默认值是33554432(32MB).如果发送消息出去的速度小于写入消息进去的速度,就会导致缓冲区写满,此时生产消息就会阻塞住,所以说这里就应该多做一些压测,尽可能保证说这块缓冲区不会被写满导致生产行为被阻塞住.缓冲区大小,可以将其修改为64m.package com.hyj.kafka.producer2;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;
public class CustomProducerParameters {
public static void main(String[] args) {
// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 2. 给 kafka 配置对象添加配置信息:bootstrap.servers
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");
// key,value 序列化(必须):key.serializer,value.serializer
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
// batch.size:批次大小,默认 16K
properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
// linger.ms:等待时间,默认 0
properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);
// RecordAccumulator:缓冲区大小,默认 32M:buffer.memory
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,33554432);
// compression.type:压缩,默认 none,可配置值 gzip、snappy、lz4 和 zstd
properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG,"snappy");
// 3. 创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);
// 4. 调用 send 方法,发送消息
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("first","kafka " + i));
}
// 5. 关闭资源
kafkaProducer.close();
}
}
测试:
①在 hadoop102 上开启 Kafka 消费者。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic first
②在 IDEA 中执行代码,观察 hadoop102 控制台中是否接收到消息。
1.ack 应答原理
思考:Leader收到数据,所有Follower都开始同步数据,但有一个Follower,因为某种故障,迟迟不能与Leader进行同步,那这个问题怎么解决呢?
Leader维护了一个动态的in-sync replica set(ISR),意为和Leader保持同步的Follower+Leader集合(leader:0,isr:0,1,2)。如果Follower长时间未向Leader发送通信请求或同步数据,则该Follower将被踢出ISR。该时间阈值由replica.lag.time.max.ms参数设定,默认30s。例如2超时,(leader:0, isr:0,1)。这样就不用等长期联系不上或者已经故障的节点。
数据可靠性分析(以下2种情况和ack=1的效果是一样的,仍然有丢数的风险(leader:0,isr:0)):
因此:
数据完全可靠条件 = ACK级别设置为-1 + 分区副本大于等于2 + ISR里应答的最小副本数量大于等于2
可靠性总结:
package com.hyj.kafka.producer2;
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.common.serialization.StringSerializer;
import java.util.Properties;
public class CustomProducerAck {
public static void main(String[] args) {
// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 2. 给 kafka 配置对象添加配置信息:bootstrap.servers
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");
// key,value 序列化(必须):key.serializer,value.serializer
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// 设置 acks
properties.put(ProducerConfig.ACKS_CONFIG, "all");
// 重试次数 retries,默认是 int 最大值,2147483647
properties.put(ProducerConfig.RETRIES_CONFIG, 3);
// 3. 创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
// 4. 调用 send 方法,发送消息
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("first","kafka" + i));
}
// 5. 关闭资源
kafkaProducer.close();
}
}
数据传递语义:
幂等性原理:
Broker端都只会持久化一条
,保证了不重复。
相同主键的消息提交时,Broker只会持久化一条。所以幂等性只能保证的是在单分区单会话内不重复
。
如何使用幂等性:
开启参数 enable.idempotence 默认为 true,false 表示关闭。
1)Kafka 事务原理
● 生产者发送多条消息可以封装在一个事务中,形成一个原子操作。多条消息要么都发送成功,要么都发送失败。
●不同的transactionId 对应不同的事务协调器.
● transaction.state.log.num.partitions可以配置主题__transaction_state
的分区个数.
● 事务日志记录在_transaction_state Topic中。TransactionCoordinator如果发生异常进行恢复或者新选举时,可以通过读取_transaction_state 中的事务日志信息,来恢复其状态信息。
● Producer epoch配合TransactionalId用于唯一标识最新的那个producer,它是一个单调递增的值,在每次初始化事务的时候递增(每个producer通过transactionId获取producer id的时候同时获取到这个值)。它的作用如下:如果有两个producer使用了相同的transactionId,那么比较旧的那个producer会抛出异常,避免事务干扰。
● ControlBatch是producer产生的并写入到Topic的特殊消息,ControlBatch一共有两种类型:COMMIT和ABORT,分别表示事务已经成功提交或者被成功中止。
流程:
Producer 会向 Broker (随机选择一台 broker,一般选择本地连接最少的broker)发送FindCoordinatorRequest请求
(根据事务id去broker端寻找这个id对应的事务协调器),FindCoordinatorRequest中的coordinator_type由原来的0变成1,由此来表示与事务相关联。Kafka 在收到 FindCoorinatorRequest 请求之后,会根据 coordinator_key (也就是transactionalId)查找对应的TransactionCoordinator节点。如果找到,则会返回其相对应的node_id、host和port信息.Producer向TransactionCoordinator发送InitProducerIdRequest请求(请求获取PID)
,注意:如果未开启事务特性而只开启幂等性,那么会随机选择一台 broker 发送InitProducerIdRequest请求.1)TransactionCoordinator为当前 Producer 分配一个 PID并返回给Producer
。分两种情况:2)除了返回PID,InitProducerIdRequest还会触发执行以下任务
:开启事务
read-process-write流程
事务提交或终结 commitTransaction/abortTransaction
2)Kafka 的事务一共有如下 5 个 API
// 1 初始化事务
void initTransactions();
// 2 开启事务
void beginTransaction() throws ProducerFencedException;
// 3 在事务内提交已经消费的偏移量(主要用于消费者)
void sendOffsetsToTransaction(Map<TopicPartition, OffsetAndMetadata> offsets,
String consumerGroupId) throws
ProducerFencedException;
// 4 提交事务
void commitTransaction() throws ProducerFencedException;
// 5 放弃事务(类似于回滚事务的操作)
void abortTransaction() throws ProducerFencedException;
3)单个 Producer,使用事务保证消息的仅一次发送
package com.hyj.kafka.producer2;
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.common.serialization.StringSerializer;
import java.util.Properties;
public class CustomProducerTransactions {
public static void main(String[] args) {
// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 2. 给 kafka 配置对象添加配置信息
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092");
// key,value 序列化
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// 设置事务 id(必须),事务 id 任意起名
properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "transaction_id_1");
// 3. 创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
// 初始化事务
kafkaProducer.initTransactions();
// 开启事务
kafkaProducer.beginTransaction();
try {
// 4. 调用 send 方法,发送消息
for (int i = 0; i < 5; i++) {
// 发送消息
kafkaProducer.send(new ProducerRecord<>("first", "kafka" + i));
}
//int i = 1 / 0;
// 提交事务
kafkaProducer.commitTransaction();
} catch (Exception e) {
// 终止事务
kafkaProducer.abortTransaction();
} finally {
// 5. 关闭资源
kafkaProducer.close();
}
}
}
单分区内,有序;
多分区,分区与分区间无序;
启用幂等性后,kafka服务端会缓存producer发来的最近5个request的元数据
,(1)启动 Zookeeper 客户端。
[hyj@hadoop102 zookeeper-3.5.7]$ bin/zkCli.sh
(2)通过 ls 命令可以查看 kafka 相关信息。
[zk: localhost:2181(CONNECTED) 0] ls /kafka
/latest_producer_id_block
节点来保存PID块,主要用于能够保证生产者的任意写入请求都能够得到响应。参考链接:kafka的内置共识机制与raft算法
参考链接(必看好文):《直击Kafka的心脏——控制器》
1)模拟 Kafka 上下线,Zookeeper 中数据变化
(1)查看/kafka/brokers/ids 路径上的节点。
[zk: localhost:2181(CONNECTED) 1] ls /kafka/brokers/ids
[0, 1, 2]
(2)查看/kafka/controller 路径上的数据。
[zk: localhost:2181(CONNECTED) 4] get /kafka/controller
{"version":1,"brokerid":0,"timestamp":"1651313939544"}
(3)查看/kafka/brokers/topics/first/partitions/0/state 路径上的数据。
[zk: localhost:2181(CONNECTED) 5] get /kafka/brokers/topics/first/partitions/0/state
{"controller_epoch":19,"leader":2,"version":1,"leader_epoch":18,"isr":[2,0,1]}
(4)停止 hadoop104 上的 kafka。
[hyj@hadoop104 kafka-3.0.0]$ bin/kafka-server-stop.sh
(5)再次查看/kafka/brokers/ids 路径上的节点。
[zk: localhost:2181(CONNECTED) 6] ls /kafka/brokers/ids
[0, 1]
(6)再次查看/kafka/controller 路径上的数据.
[zk: localhost:2181(CONNECTED) 7] get /kafka/controller
{"version":1,"brokerid":0,"timestamp":"1651313939544"}
(7)再次查看/kafka/brokers/topics/first/partitions/0/state 路径上的数据。
[zk: localhost:2181(CONNECTED) 8] get /kafka/brokers/topics/first/partitions/0/state
{"controller_epoch":19,"leader":0,"version":1,"leader_epoch":19,"isr":[0,1]}
(8)启动 hadoop104 上的 kafka
[hyj@hadoop104 kafka-3.0.0]$ bin/kafka-server-start.sh -daemon config/server.properties
(9)再次观察(1)、(2)、(3)步骤中的内容
(10) 停止 hadoop102 上的 kafka。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-server-stop.sh
(11) 查看/kafka/controller 路径上的数据.
[zk: localhost:2181(CONNECTED) 9] get /kafka/controller
{"version":1,"brokerid":2,"timestamp":"1651315127904"}
(12) 启动 hadoop102 上的 kafka
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-server-start.sh -daemon config/server.properties
(13) 再次查看/kafka/controller 路径上的数据.
[zk: localhost:2181(CONNECTED) 10] get /kafka/controller
{"version":1,"brokerid":2,"timestamp":"1651315127904"}
(1)关闭 hadoop104,并右键执行克隆操作。
(2)开启 hadoop105,并修改 IP 地址
[root@hadoop104 ~]# vim /etc/sysconfig/network-scripts/ifcfg-ens33
DEVICE=ens33
TYPE=Ethernet
ONBOOT=yes
BOOTPROTO=static
NAME="ens33"
IPADDR=192.168.10.105 #修改此处
PREFIX=24
GATEWAY=192.168.10.2
DNS1=192.168.10.2
(3)在 hadoop105 上,修改主机名称为 hadoop105。
[root@hadoop104 ~]# vim /etc/hostname
hadoop105
(4)重新启动hadoop105。
[root@hadoop104 ~]# reboot
( 5 ) 开启hadoop104
(6)修改 haodoop105 中 kafka 的 broker.id 为 3
[hyj@hadoop105 config]$ vim server.properties
(7)删除 hadoop105 中 kafka 下的 datas 和 logs。(若是不删除的话就会出现这种情况:hadoop105上线hadoop104就下线;hadoop104上线hadoop105就下线)
[hyj@hadoop105 kafka-3.0.0]$ rm -fr datas/ logs/
(8)先启动zookeeper,再启动 hadoop102、hadoop103、hadoop104 上的 kafka 集群。
(9)单独启动 hadoop105 中的 kafka。
[hyj@hadoop105 kafka-3.0.0]$ bin/kafka-server-start.sh -daemon config/server.properties
2)执行负载均衡操作
(1)查看 first 主题的详情
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic first --describe
Topic: first TopicId: WFXVxJNmRgSzothFP56cJg PartitionCount: 3 ReplicationFactor: 3 Configs: segment.bytes=1073741824
Topic: first Partition: 0 Leader: 2 Replicas: 2,0,1 Isr: 1,0,2
Topic: first Partition: 1 Leader: 1 Replicas: 0,1,2 Isr: 1,0,2
Topic: first Partition: 2 Leader: 1 Replicas: 1,2,0 Isr: 1,0,2
(2)创建一个要均衡的主题。
[hyj@hadoop102 kafka-3.0.0]$ vim topics-to-move.json
{
"topics": [
{"topic": "first"} #多个主题 {"topic":"first"},{"topic":"second"}
],
"version": 1
}
(3)生成一个负载均衡的计划
[hyj@hadoop102 kafka-3.0.0]$ 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
Current partition replica assignment
{"version":1,"partitions":[{"topic":"first","partition":0,"replicas":[2,0,1],"log_dirs":["any","any","any"]},{"topic":"first","partition":1,"replicas":[0,1,2],"log_dirs":["any","any","any"]},{"topic":"first","partition":2,"replicas":[1,2,0],"log_dirs":["any","any","any"]}]}
Proposed partition reassignment configuration
{"version":1,"partitions":[{"topic":"first","partition":0,"replicas":[3,1,2],"log_dirs":["any","any","any"]},{"topic":"first","partition":1,"replicas":[0,2,3],"log_dirs":["any","any","any"]},{"topic":"first","partition":2,"replicas":[1,3,0],"log_dirs":["any","any","any"]}]}
(4)创建副本存储计划(所有副本存储在 broker0、broker1、broker2 ,broker3中,与上面的–broker-list "0,1,2,3"对应)。
[hyj@hadoop102 kafka-3.0.0]$ vim increase-replication-factor.json
添加以下内容(添加上面生成的计划(第二个)):
{"version":1,"partitions":[{"topic":"first","partition":0,"replicas":[3,1,2],"log_dirs":["any","any","any"]},{"topic":"first","partition":1,"replicas":[0,2,3],"log_dirs":["any","any","any"]},{"topic":"first","partition":2,"replicas":[1,3,0],"log_dirs":["any","any","any"]}]}
(5)执行副本存储计划。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --execute
(6)验证副本存储计划。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --verify
(7) 再次查看 first 主题的详情
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic first --describe
Topic: first TopicId: WFXVxJNmRgSzothFP56cJg PartitionCount: 3 ReplicationFactor: 3 Configs: segment.bytes=1073741824
Topic: first Partition: 0 Leader: 3 Replicas: 3,1,2 Isr: 1,2,3
Topic: first Partition: 1 Leader: 0 Replicas: 0,2,3 Isr: 0,2,3
Topic: first Partition: 2 Leader: 1 Replicas: 1,3,0 Isr: 1,0,3
1)执行负载均衡操作
先按照退役一台节点,生成执行计划
,然后按照服役时操作流程执行负载均衡
。
(1)创建一个要均衡的主题。
[hyj@hadoop102 kafka-3.0.0]$ vim topics-to-move.json
{
"topics": [
{"topic": "first"}
],
"version": 1
}
(2)创建执行计划。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --topics-to-move-json-file topics-to-move.json --broker-list "0,1,2" --generate
Current partition replica assignment
{"version":1,"partitions":[{"topic":"first","partition":0,"replicas":[3,1,2],"log_dirs":["any","any","any"]},{"topic":"first","partition":1,"replicas":[0,2,3],"log_dirs":["any","any","any"]},{"topic":"first","partition":2,"replicas":[1,3,0],"log_dirs":["any","any","any"]}]}
Proposed partition reassignment configuration
{"version":1,"partitions":[{"topic":"first","partition":0,"replicas":[0,1,2],"log_dirs":["any","any","any"]},{"topic":"first","partition":1,"replicas":[1,2,0],"log_dirs":["any","any","any"]},{"topic":"first","partition":2,"replicas":[2,0,1],"log_dirs":["any","any","any"]}]}
(3)创建副本存储计划(所有副本存储在 broker0、broker1、broker2 中)。
[hyj@hadoop102 kafka-3.0.0]$ vim increase-replication-factor.json
添加如下内容(上面生成的计划):
{"version":1,"partitions":[{"topic":"first","partition":0,"replicas":[0,1,2],"log_dirs":["any","any","any"]},{"topic":"first","partition":1,"replicas":[1,2,0],"log_dirs":["any","any","any"]},{"topic":"first","partition":2,"replicas":[2,0,1],"log_dirs":["any","any","any"]}]}
(4)执行副本存储计划
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --execute
(5)验证副本存储计划。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --verify
(6)查看first主题的详情(此步可省)
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic first --describe
Topic: first TopicId: WFXVxJNmRgSzothFP56cJg PartitionCount: 3 ReplicationFactor: 3 Configs: segment.bytes=1073741824
Topic: first Partition: 0 Leader: 0 Replicas: 0,1,2 Isr: 1,2,0
Topic: first Partition: 1 Leader: 0 Replicas: 1,2,0 Isr: 0,2,1
Topic: first Partition: 2 Leader: 1 Replicas: 2,0,1 Isr: 1,0,2
2)执行停止命令
在 hadoop105 上执行停止命令即可。
[hyj@hadoop105 kafka-3.0.0]$ bin/kafka-server-stop.sh
Kafka 集群中有一个 broker 的 Controller 会被选举为 Controller Leader,负责管理集群broker 的上下线,所有 topic 的分区副本分配和 Leader 选举等工作。
Controller 的信息同步工作是依赖于 Zookeeper 的。
(1)创建一个新的 topic,4 个分区,4 个副本
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --create --topic four --partitions 4 --replication-factor 4
(2)查看 Leader 分布情况
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic four --describe
Topic: four TopicId: thPC9ZElQhqAbFUNpimUQA PartitionCount: 4 ReplicationFactor: 4 Configs: segment.bytes=1073741824
Topic: four Partition: 0 Leader: 0 Replicas: 0,3,1,2 Isr: 0,3,1,2
Topic: four Partition: 1 Leader: 2 Replicas: 2,1,0,3 Isr: 2,1,0,3
Topic: four Partition: 2 Leader: 3 Replicas: 3,0,2,1 Isr: 3,0,2,1
Topic: four Partition: 3 Leader: 1 Replicas: 1,2,3,0 Isr: 1,2,3,0
(3)停止掉 hadoop105 的 kafka 进程,并查看 Leader 分区情况
[hyj@hadoop105 kafka-3.0.0]$ bin/kafka-server-stop.sh
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic four --describe
Topic: four TopicId: thPC9ZElQhqAbFUNpimUQA PartitionCount: 4 ReplicationFactor: 4 Configs: segment.bytes=1073741824
Topic: four Partition: 0 Leader: 0 Replicas: 0,3,1,2 Isr: 0,1,2
Topic: four Partition: 1 Leader: 2 Replicas: 2,1,0,3 Isr: 2,1,0
Topic: four Partition: 2 Leader: 0 Replicas: 3,0,2,1 Isr: 0,2,1
Topic: four Partition: 3 Leader: 1 Replicas: 1,2,3,0 Isr: 1,2,0
(4)停止掉 hadoop104 的 kafka 进程,并查看 Leader 分区情况
[hyj@hadoop104 kafka-3.0.0]$ bin/kafka-server-stop.sh
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic four --describe
Topic: four TopicId: thPC9ZElQhqAbFUNpimUQA PartitionCount: 4 ReplicationFactor: 4 Configs: segment.bytes=1073741824
Topic: four Partition: 0 Leader: 0 Replicas: 0,3,1,2 Isr: 0,1
Topic: four Partition: 1 Leader: 1 Replicas: 2,1,0,3 Isr: 1,0
Topic: four Partition: 2 Leader: 0 Replicas: 3,0,2,1 Isr: 0,1
Topic: four Partition: 3 Leader: 1 Replicas: 1,2,3,0 Isr: 1,0
(5)启动 hadoop105 的 kafka 进程,并查看 Leader 分区情况
[hyj@hadoop105 kafka-3.0.0]$ bin/kafka-server-start.sh -daemon config/server.properties
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic four --describe
Topic: four TopicId: thPC9ZElQhqAbFUNpimUQA PartitionCount: 4 ReplicationFactor: 4 Configs: segment.bytes=1073741824
Topic: four Partition: 0 Leader: 0 Replicas: 0,3,1,2 Isr: 0,1,3
Topic: four Partition: 1 Leader: 1 Replicas: 2,1,0,3 Isr: 1,0,3
Topic: four Partition: 2 Leader: 0 Replicas: 3,0,2,1 Isr: 0,1,3
Topic: four Partition: 3 Leader: 1 Replicas: 1,2,3,0 Isr: 1,0,3
(6)启动 hadoop104 的 kafka 进程,并查看 Leader 分区情况
[hyj@hadoop104 kafka-3.0.0]$ bin/kafka-server-start.sh -daemon config/server.properties
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic four --describe
Topic: four TopicId: thPC9ZElQhqAbFUNpimUQA PartitionCount: 4 ReplicationFactor: 4 Configs: segment.bytes=1073741824
Topic: four Partition: 0 Leader: 0 Replicas: 0,3,1,2 Isr: 0,1,3,2
Topic: four Partition: 1 Leader: 1 Replicas: 2,1,0,3 Isr: 1,0,3,2
Topic: four Partition: 2 Leader: 3 Replicas: 3,0,2,1 Isr: 0,1,3,2
Topic: four Partition: 3 Leader: 1 Replicas: 1,2,3,0 Isr: 1,0,3,2
(7)停止掉 hadoop103 的 kafka 进程,并查看 Leader 分区情况
[hyj@hadoop103 kafka-3.0.0]$ bin/kafka-server-stop.sh
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic four --describe
Topic: four TopicId: thPC9ZElQhqAbFUNpimUQA PartitionCount: 4 ReplicationFactor: 4 Configs: segment.bytes=1073741824
Topic: four Partition: 0 Leader: 0 Replicas: 0,3,1,2 Isr: 0,3,2
Topic: four Partition: 1 Leader: 2 Replicas: 2,1,0,3 Isr: 0,3,2
Topic: four Partition: 2 Leader: 3 Replicas: 3,0,2,1 Isr: 0,3,2
Topic: four Partition: 3 Leader: 2 Replicas: 1,2,3,0 Isr: 0,3,2
LEO(Log End Offset):每个副本的最后一个offset,LEO其实就是最新的offset + 1
。
HW(High Watermark):所有副本中最小的LEO
。
Follower故障
(1) Follower(broker2)发生故障后会被临时踢出ISR(进入OSR)
(2) 这个期间Leader和Follower(broker1)继续接收数据
(3)待该Follower(broker2)恢复后,Follower(broker2)会读取本地磁盘记录的上次的HW,并将log文件高于HW的部分截取掉(删除),从HW开始向Leader进行同步。
(4)等该Follower(broker2)的LEO大于等于该Partition的HW,即
Follower(broker2)追上Leader之后,就可以重新加入ISR了。
Leader故障
(1) Leader发生故障之后,会从ISR中选出一个新的Leader.
(2)为保证多个副本之间的数据一致性,其余的Follower会先将各自的log文件高于HW的部分截掉
,然后从新的Leader同步数据。
注意:这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。
如果 kafka 服务器只有 4 个节点,那么设置 kafka 的分区数大于服务器台数,在 kafka底层如何分配存储副本呢?
1)创建 16 分区,3 个副本
(1)创建一个新的 topic,名称为 second。
[hyj@hadoop102 kafka]$ bin/kafka-topics.sh --bootstrap-server
hadoop102:9092 --create --partitions 16 --replication-factor 3 --
topic second
(2)查看分区和副本情况。
[hyj@hadoop102 kafka]$ bin/kafka-topics.sh --bootstrap-server
hadoop102:9092 --describe --topic second
Topic: second4 Partition: 0 Leader: 0 Replicas: 0,1,2 Isr: 0,1,2
Topic: second4 Partition: 1 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3
Topic: second4 Partition: 2 Leader: 2 Replicas: 2,3,0 Isr: 2,3,0
Topic: second4 Partition: 3 Leader: 3 Replicas: 3,0,1 Isr: 3,0,1
Topic: second4 Partition: 4 Leader: 0 Replicas: 0,2,3 Isr: 0,2,3
Topic: second4 Partition: 5 Leader: 1 Replicas: 1,3,0 Isr: 1,3,0
Topic: second4 Partition: 6 Leader: 2 Replicas: 2,0,1 Isr: 2,0,1
Topic: second4 Partition: 7 Leader: 3 Replicas: 3,1,2 Isr: 3,1,2
Topic: second4 Partition: 8 Leader: 0 Replicas: 0,3,1 Isr: 0,3,1
Topic: second4 Partition: 9 Leader: 1 Replicas: 1,0,2 Isr: 1,0,2
Topic: second4 Partition: 10 Leader: 2 Replicas: 2,1,3 Isr: 2,1,3
Topic: second4 Partition: 11 Leader: 3 Replicas: 3,2,0 Isr: 3,2,0
Topic: second4 Partition: 12 Leader: 0 Replicas: 0,1,2 Isr: 0,1,2
Topic: second4 Partition: 13 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3
Topic: second4 Partition: 14 Leader: 2 Replicas: 2,3,0 Isr: 2,3,0
Topic: second4 Partition: 15 Leader: 3 Replicas: 3,0,1 Isr: 3,0,1
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --create --topic four --partitions 16 --replication-factor 3
(4)查看分区和副本情况。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic four --describe
Topic: second TopicId: 6c9DBNHjRp2i4sxNsgkNvA PartitionCount: 16 ReplicationFactor: 3 Configs: segment.bytes=1073741824
Topic: second Partition: 0 Leader: 1 Replicas: 1,0,2 Isr: 1,0,2
Topic: second Partition: 1 Leader: 0 Replicas: 0,2,3 Isr: 0,2,3
Topic: second Partition: 2 Leader: 2 Replicas: 2,3,1 Isr: 2,3,1
Topic: second Partition: 3 Leader: 3 Replicas: 3,1,0 Isr: 3,1,0
Topic: second Partition: 4 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3
Topic: second Partition: 5 Leader: 0 Replicas: 0,3,1 Isr: 0,3,1
Topic: second Partition: 6 Leader: 2 Replicas: 2,1,0 Isr: 2,1,0
Topic: second Partition: 7 Leader: 3 Replicas: 3,0,2 Isr: 3,0,2
Topic: second Partition: 8 Leader: 1 Replicas: 1,3,0 Isr: 1,3,0
Topic: second Partition: 9 Leader: 0 Replicas: 0,1,2 Isr: 0,1,2
Topic: second Partition: 10 Leader: 2 Replicas: 2,0,3 Isr: 2,0,3
Topic: second Partition: 11 Leader: 3 Replicas: 3,2,1 Isr: 3,2,1
Topic: second Partition: 12 Leader: 1 Replicas: 1,0,2 Isr: 1,0,2
Topic: second Partition: 13 Leader: 0 Replicas: 0,2,3 Isr: 0,2,3
Topic: second Partition: 14 Leader: 2 Replicas: 2,3,1 Isr: 2,3,1
Topic: second Partition: 15 Leader: 3 Replicas: 3,1,0 Isr: 3,1,0
需求:创建一个新的topic,4个分区,两个副本,名称为three。将 该topic的所有副本都存储到broker0和broker1两台服务器上。
手动调整分区副本存储的步骤如下:
(1)创建一个新的 topic,名称为 three.
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --create --
topic three --partitions 4 --replication-factor 2
(2)查看分区副本存储情况。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic three --describe
Topic: three TopicId: Y4nEXv-LRJOFlvZ_HUuaJg PartitionCount: 4 ReplicationFactor: 2 Configs: segment.bytes=1073741824
Topic: three Partition: 0 Leader: 2 Replicas: 2,3 Isr: 2,3
Topic: three Partition: 1 Leader: 3 Replicas: 3,1 Isr: 3,1
Topic: three Partition: 2 Leader: 1 Replicas: 1,0 Isr: 1,0
Topic: three Partition: 3 Leader: 0 Replicas: 0,2 Isr: 0,2
(3)创建副本存储计划(所有副本都指定存储在 broker0、broker1 中)。
[hyj@hadoop102 kafka-3.0.0]$ 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]}] }
(4)执行副本存储计划。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --execute
(5)验证副本存储计划。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --verify
(6)查看分区副本存储情况。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic three --describe
Topic: three TopicId: Y4nEXv-LRJOFlvZ_HUuaJg PartitionCount: 4 ReplicationFactor: 2 Configs: segment.bytes=1073741824
Topic: three Partition: 0 Leader: 0 Replicas: 0,1 Isr: 1,0
Topic: three Partition: 1 Leader: 0 Replicas: 0,1 Isr: 1,0
Topic: three Partition: 2 Leader: 1 Replicas: 1,0 Isr: 1,0
Topic: three Partition: 3 Leader: 0 Replicas: 1,0 Isr: 0,1
正常情况下,Kafka本身会自动把Leader Partition均匀分散在各个机器上
,来保证每台机器的读写吞吐量都是均匀的。但是如果某 些broker宕机,会导致Leader Partition过于集中在其他少部分几台broker上
,这会导致少数几台broker的读写请求压力过高,其他宕机的broker重启之后都是follower partition,读写请求很低,造成集群负载不均衡
。
• auto.leader.rebalance.enable,默认是true。启用自动Leader Partition 平衡
.生产环境中,leader 重新选举的代价比较大,可能会带来性能影响,建议设置为 false 关闭。
• leader.imbalance.per.broker.percentage,默认是10%。每个broker允许的不平衡的leader的比率
。如果某个broker超过了这个值,控制器会触发leader的平衡。
• leader.imbalance.check.interval.seconds,默认值300秒。检查leader负载是否平衡的间隔时间。
针对broker3节点,分区3的AR优先副本是3节点,但是3节点却不是Leader节点,所以不平衡数加1,AR副本总数是4
所以broker3节点不平衡率为1/4>10%,需要再平衡。
broker2和broker3节点不平衡率一样,需要再平衡。
broker0和broker1的不平衡数为0,不需要再平衡.
leader.imbalance.check.interval.seconds时间到后会检查leader负载是否平衡,之后再次查看test1主题的详情可以看到:
在生产环境当中,由于某个主题的重要等级需要提升,我们考虑增加副本。副本数的增加需要先制定计划,然后根据计划执行。
1)创建four topic,3个分区,1个副本
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --create --topic four --partitions 3 --replication-factor 1
2)查看four topic的详情
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic four --describe
Topic: four TopicId: r_l_b3oiQK6zHv5-tRXYFg PartitionCount: 3 ReplicationFactor: 1 Configs: segment.bytes=1073741824
Topic: four Partition: 0 Leader: 2 Replicas: 2 Isr: 2
Topic: four Partition: 1 Leader: 3 Replicas: 3 Isr: 3
Topic: four Partition: 2 Leader: 1 Replicas: 1 Isr: 1
3)手动增加副本存储
(1)创建副本存储计划(所有副本都指定存储在 broker0、broker1,broker2 中)。
[hyj@hadoop102 kafka-3.0.0]$ vim increase-replication-factor.json
输入如下内容:
{"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]}]}
(2)执行副本存储计划。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --execute
4)再次查看four topic的详情
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic four --describe
Topic: four TopicId: r_l_b3oiQK6zHv5-tRXYFg PartitionCount: 3 ReplicationFactor: 3 Configs: segment.bytes=1073741824
Topic: four Partition: 0 Leader: 2 Replicas: 0,1,2 Isr: 2,0,1
Topic: four Partition: 1 Leader: 0 Replicas: 0,1,2 Isr: 2,1,0
Topic: four Partition: 2 Leader: 1 Replicas: 0,1,2 Isr: 1,2,0
1)Topic 数据的存储机制
Topic是逻辑上的概念,而partition是物理上的概念,每个partition对应于一个log文件
,该log文件中存储的就是Producer生产的数据。Producer生产的数据会被不断追加到该log文件末端
,为防止log文件过大导致数据定位效率低下,Kafka采取了分片和索引
机制, 将每个partition分为多个segment
。每个segment包括:“.index”文件(偏移量索引文件)、“.log”文件(日志文件)和.timeindex(时间戳索引文件)
等文件。这些文件位于一个文件夹下,该文件夹的命名规则为:topic名称+分区序号,例如:first-0。
说明:index和log文件以当前segment的第一条消息的offset命名。
2)思考:Topic 数据到底存储在什么位置?
(1)启动生产者,并发送消息
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-producer.sh --bootstrap-server hadoop102:9092 --topic first
>helloworld
(2)查看 hadoop102(或者 hadoop103、hadoop104)的/opt/module/kafka-3.0.0/datas/first-1 (first-0、first-2)路径上的文件。
[hyj@hadoop103 first-1]$ ls
00000000000000000000.index 00000000000000000000.timeindex partition.metadata
00000000000000000000.log leader-epoch-checkpoint
(3)直接查看 log 日志,发现是乱码。
[hyj@hadoop103 first-1]$ cat 00000000000000000000.log
BO%遀ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ helloworld
(4)通过工具查看 index 和 log 信息。
[hyj@hadoop103 first-1]$ kafka-run-class.sh kafka.tools.DumpLogSegments --files ./00000000000000000000.index
Dumping ./00000000000000000000.index
offset: 0 position: 0
[hyj@hadoop103 first-1]$ kafka-run-class.sh kafka.tools.DumpLogSegments --files ./00000000000000000000.log
Dumping ./00000000000000000000.log
Starting offset: 0
baseOffset: 0 lastOffset: 0 count: 1 baseSequence: -1 lastSequence: -1 producerId: -1 producerEpoch: -1 partitionLeaderEpoch: 0 isTransactional: false isControl: false position: 0 CreateTime: 1651381206785 size: 78 magic: 2 compresscodec: none crc: 1335698923 isvalid: true
3)index 文件和 log 文件详解
说明:日志存储参数配置
Kafka 中默认的日志保存时间为 7 天
,可以通过调整如下参数修改保存时间。
那么日志一旦超过了设置的时间,怎么处理呢?
Kafka 中提供的日志清理策略有 delete 和 compact
两种。
以 segment 中所有记录中的最大时间戳作为该文件时间戳
。这种策略只适合特殊场景,比如消息的key是用户ID,value是用户的资料,通过这种压缩策略,整个消息集里就保存了所有用户最新的资料。
追加到文件末端
,为顺序写。官网有数据表明,同样的磁盘,顺序写能到 600M/s,而随机写只有 100K/s。这与磁盘的结构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间。写操作时,操作系统只是将数据写入PageCache
。当读操作发生时,先从PageCache中查找,如果找不到,就会从磁盘读取文件写入 Page Cache 再读取
。实际上PageCache是把尽可能多的空闲内存都当做了磁盘缓存来使用。consumer采用从broker中主动拉取数据。Kafka采用这种方式。
pull模式不足之处是,如 果Kafka没有数据,消费者可能会陷入循环中,一直返回空数据。
Consumer Group(CG):消费者组,由多个consumer组成。形成一个消费者组的条件,是所有消费者的groupid相同。
• 消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费。
• 消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
• 如果向消费组中添加更多的消费者,超过主题分区数量,则有一部分消费者就会闲置,不会接收任何消息。
1)需求:
创建一个独立消费者,消费 first 主题中数据。
注意:在消费者 API 代码中必须配置消费者组 id。命令行启动消费者不填写消费者组id 会被自动填写随机的消费者组 id。
2)实现步骤
(1)创建包名:com.hyj.kafka.consumer
(2)编写代码
package com.hyj.kafka.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
public class CustomConsumer {
public static void main(String[] args) {
// 1.创建消费者的配置对象
Properties properties = new Properties();
// 2.给消费者配置对象添加参数 连接集群
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092");
// 3.反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
// 配置消费者组id
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
// 创建消费者对象
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// 订阅主题(可以消费多个主题)
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
//拉取数据打印
while (true){
//设置1s消费一批数据
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
// 打印消费到的数据
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
3)测试
(1)在 IDEA 中执行消费者程序
(2)在 Kafka 集群控制台,创建 Kafka 生产者,并输入数据
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-producer.sh --
bootstrap-server hadoop102:9092 --topic first
>hello
(3)在 IDEA 控制台观察接收到的数据。
1)需求:创建一个独立消费者,消费 first 主题 0 号分区的数据
2)实现步骤
(1)代码编写
package com.hyj.kafka.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
public class CustomConsumerPartition {
public static void main(String[] args) {
// 1.创建消费者的配置对象
Properties properties = new Properties();
// 2.给消费者配置对象添加参数 连接集群
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092");
// 3.反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer .class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
// 配置消费者组id 名字随便起
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
// 创建消费者对象
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// 消费某个主题的某个分区数据
ArrayList<TopicPartition> topicPartitions = new ArrayList<>();
topicPartitions.add(new TopicPartition("first",0));
kafkaConsumer.assign(topicPartitions);
while (true){
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
3)测试
(1)在 IDEA 中执行消费者程序。
(2)在 IDEA 中执行生产者程序 CustomProducerCallback()在控制台观察生成几个 0 号分区的数据。
(3)在 IDEA 控制台,观察接收到的数据,只能消费到 0 号分区数据表示正确。
1)需求:测试同一个主题的分区数据,只能由一个消费者组中的一个消费.
2)案例实操
(1)复制2份CustomConsumer的代码,在 IDEA 中同时启动,即可启动同一个消费者组中的3个消费者。
package com.hyj.kafka.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
public class CustomConsumer1 {
public static void main(String[] args) {
// 1.创建消费者的配置对象
Properties properties = new Properties();
// 2.给消费者配置对象添加参数 连接集群
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092");
// 3.反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
// 配置消费者组id 名字随便起
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
// 创建消费者对象
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// 订阅主题(可以消费多个主题)
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
//拉取数据打印
while (true){
//设置1s消费一批数据
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
// 打印消费到的数据
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
package com.hyj.kafka.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
public class CustomConsumer2 {
public static void main(String[] args) {
// 1.创建消费者的配置对象
Properties properties = new Properties();
// 2.给消费者配置对象添加参数 连接集群
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092");
// 3.反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
// 配置消费者组id 名字随便起
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
// 创建消费者对象
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// 订阅主题(可以消费多个主题)
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
//拉取数据打印
while (true){
//设置1s消费一批数据
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
// 打印消费到的数据
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
(2)启动代码中的生产者发送消息,在 IDEA 控制台即可看到3个消费者在消费不同分区的数据(如果只发送到一个分区,可以在发送时增加延迟代码 Thread.sleep(2);)
package com.hyj.kafka.producer;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class CustomProducerCallback {
public static void main(String[] args) throws InterruptedException {
// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 2. 给 kafka 配置对象添加配置信息:bootstrap.servers连接集群
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092");
// 指定对应的key,value的序列化类型(必须):key.serializer,value.serializer
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 3. 创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);
// key:"" value:"hello kafka"+i
for (int i = 0; i < 100; i++) {
// 4. 调用 send 方法,发送消息 (topic,value)
kafkaProducer.send(new ProducerRecord<>("first", "hello kafka" + i), new Callback() {
//该方法在Producer收到ack时调用,为异步调用
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if(e == null){
//没有异常,输出信息到控制台
System.out.println("主题:"+recordMetadata.topic()+"->"+"分区:"+recordMetadata.partition());
}else{
//出现异常打印
e.printStackTrace();
}
}
});
//延迟一会 会看到数据发往不同分区
Thread.sleep(2);
}
//5.关闭资源
kafkaProducer.close();
}
}
(3)重新发送到一个全新的主题中,由于默认创建的主题分区数为 1,可以看到只能有一个消费者消费到数据。
到底由哪个consumer来消费哪个partition的数据。
参数partition.assignment.strategy
,修改分区的分配策略。默认策略是Range + CooperativeSticky。Kafka可以同时使用多个分区分配策略。Range 是对每个 topic
而言的。
首先对同一个 topic 里面的分区按照序号进行排序
,并对消费者按照字母顺序进行排序
。
假如现在有 7 个分区,3 个消费者,排序后的分区将会是0,1,2,3,4,5,6;消费者排序完之后将会是C0,C1,C2。
例如,7/3 = 2 余 1 ,除不尽,那么 消费者 C0 便会多消费 1 个分区。 8/3=2余2,除不尽,那么C0和C1分别多消费一个。
通过partitions数/consumer数
来决定每个消费者应该消费几个分区。如果除不尽,那么前面几个消费者将会多消费 1 个分区。
注意:如果只是针对 1 个 topic 而言,C0消费者多消费1个分区影响不是很大。但是如果有 N 多个 topic,那么针对每个 topic,消费者 C0都将多消费 1 个分区,topic越多,C0消费的分区会比其他消费者明显多消费 N 个分区。
容易产生数据倾斜!
2)Range 分区分配策略案例
(1)修改主题 first 为 7 个分区。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --alter --topic first --partitions 7
注意:分区数可以增加,但是不能减少
。
(2)复制 CustomConsumer 类,创建CustomConsumer1和CustomConsumer2。这样可以由三个消费者CustomConsumer、CustomConsumer1、CustomConsumer2 组成消费者组,组名都为“test”,
同时启动 3 个消费者
(3)启动 CustomProducerCallback 生产者,发送 500 条消息,随机发送到不同的分区。
package com.hyj.kafka.producer;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class CustomProducerCallback {
public static void main(String[] args) throws InterruptedException {
// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 2. 给 kafka 配置对象添加配置信息:bootstrap.servers连接集群
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092");
// 指定对应的key,value的序列化类型(必须):key.serializer,value.serializer
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 3. 创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);
// key:"" value:"hello kafka"+i
for (int i = 0; i < 500; i++) {
// 4. 调用 send 方法,发送消息 (topic,value)
kafkaProducer.send(new ProducerRecord<>("first", "hello kafka" + i), new Callback() {
//该方法在Producer收到ack时调用,为异步调用
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if(e == null){
//没有异常,输出信息到控制台
System.out.println("主题:"+recordMetadata.topic()+"->"+"分区:"+recordMetadata.partition());
}else{
//出现异常打印
e.printStackTrace();
}
}
});
//延迟一会 会看到数据发往不同分区
Thread.sleep(2);
}
//5.关闭资源
kafkaProducer.close();
}
}
说明:Kafka 默认的分区分配策略就是[RangeAssignor, CooperativeStickyAssignor]
(Range + CooperativeSticky),所以不需要修改策略。
[RangeAssignor, CooperativeStickyAssignor]表示将默认使用RangeAssignor,但是允许升级到CooperativeStickyAssignor,只需要一个从列表中删除RangeAssignor的滚动反弹。
(4)观看 3 个消费者分别消费哪些分区的数据。
3)Range 分区分配再平衡案例
任务会整体被分配
到 1 号消费者或者 2 号消费者。0 号消费者挂掉后,消费者组需要按照超时时间 45s 来判断它是否退出,所以需要等待,时间到了 45s 后,判断它真的退出就会把任务分配给其他 broker 执行。
消费者 0 已经被踢出消费者组,所以重新按照 range 方式分配。
1)RoundRobin 分区策略原理
RoundRobin 针对集群中所有Topic
而言。
RoundRobin 轮询分区策略,是把所有的 partition 和所有的consumer 都列出来
,然后按照 hashcode 进行排序
,最后通过轮询算法
来分配 partition 给到各个消费者。
2)RoundRobin 分区分配策略案例
(1)依次在 CustomConsumer、CustomConsumer1、CustomConsumer2 三个消费者代码中修改分区分配策略为 RoundRobin。
// 修改分区分配策略
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, "org.apache.kafka.clients.consumer.RoundRobinAssignor");
或者
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, RoundRobinAssignor.class.getName());
(2)重启 3 个消费者,重复发送消息的步骤,观看分区结果.
3)RoundRobin 分区分配再平衡案例
(1)停止掉 0 号消费者,快速重新发送消息观看结果(45s 以内,越快越好)。
1 号消费者:消费到 2、5 号分区数据
2 号消费者:消费到 4、1 号分区数据
0 号消费者的任务会按照 RoundRobin 的方式,把数据轮询分成 0 、6 和 3 号分区数据,分别由
1 号消费者或者 2 号消费者消费。
说明:0 号消费者挂掉后,消费者组需要按照超时时间 45s 来判断它是否退出,所以需要等待,时间到了 45s 后,判断它真的退出就会把任务分配给其他 broker 执行。
(2)再次重新发送消息观看结果(45s 以后)。
1 号消费者:消费到 0、2、4、6 号分区数据
2 号消费者:消费到 1、3、5 号分区数据
说明:消费者 0 已经被踢出消费者组,所以重新按照 RoundRobin 方式分配。
粘性分区定义:可以理解为分配的结果带有“粘性的”。即在执行一次新的分配之前,考虑上一次分配的结果,尽量少的调整分配的变动,可以节省大量的开销。
粘性分区是 Kafka 从 0.11.x 版本开始引入这种分配策略,首先会尽量均衡的放置分区到消费者上面
,在出现同一消费者组内消费者出现问题的时候,会尽量保持原有分配的分区不变化。
Sticky分配策略的原理比较复杂,它的设计主要实现了两个目的:
如果这两个目的发生了冲突,优先实现第一个目的。
粘性分区分配 : 假如现在有7个分区,3个消费者,7 / 3= 2余1,因为分区的分配要尽可能的均匀又随机
,所以3个消费者中的其中两个消费者消费2个分区数据(随机2个分区)
,1个消费者消费3个分区数据(随机3个分区)
。
1)需求
设置主题为 first,7 个分区;准备 3 个消费者,采用粘性分区策略,并进行消费,观察消费分配情况。然后再停止其中一个消费者,再次观察消费分配情况。
2)步骤
(1)修改分区分配策略为粘性。
注意:3 个消费者都应该注释掉,之后重启 3 个消费者,如果出现报错,全部停止等会再重启,或者修改为全新的消费者组。
// 修改分区分配策略
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,StickyAssignor.class.getName());
// 修改消费组id
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test2");
(2)使用同样的生产者发送 500 条消息。
可以看到会尽量保持分区的个数近似划分分区.
3)Sticky 分区分配再平衡案例
(1)停止掉 0 号消费者,快速重新发送消息观看结果(45s 以内,越快越好)。
1 号消费者:消费到 2、5、3 号分区数据。
2 号消费者:消费到 4、6 号分区数据。
0 号消费者的任务会按照粘性规则,尽可能均衡的随机分成 0 和 1 号分区数据,分别由 1 号消费者或者 2 号消费者消费。
说明:0 号消费者挂掉后,消费者组需要按照超时时间 45s 来判断它是否退出,所以需要等待,时间到了 45s 后,判断它真的退出就会把任务分配给其他 broker 执行。
(2)再次重新发送消息观看结果(45s 以后).
1 号消费者:消费到 2、3、5 号分区数据。 // 注:分区的分配尽可能的与上次分配的保持相同。
2 号消费者:消费到 0、1、4、6 号分区数据。
// 所以1号消费者还是消费到2、5、3 号分区数据。2 号消费者还是消费到 4、6 号分区数据。
说明:消费者 0 已经被踢出消费者组,所以重新按照粘性方式分配。
从下图可以看出Sticky和RoundRobin的区别:
__consumer_offsets 主题里面采用key 和 value
的方式存储数据。key 是 group.id+topic+分区号
,value 就是当前 offset 的值
。每隔一段时间,kafka 内部会对这个 topic 进行合并(compact),也就是每个 group.id+topic+分区号就保留最新数据
。
1)消费 offset 案例
(0)思想:__consumer_offsets 为 Kafka 中的topic,那就可以通过消费者进行消费。
(1)在配置文件 config/consumer.properties 中添加配置 exclude.internal.topics=false,默认是 true,表示不能消费系统主题
。为了查看该系统主题数据,将该参数修改为 false。
[hyj@hadoop102 config]$ vim consumer.properties
#添加如下内容: exclude.internal.topics=false
# 改完之后分发
[hyj@hadoop102 config]$ xsync consumer.properties
(2)采用命令行方式,创建一个新的 topic。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --create --topic world --partitions 2 --replication-factor 2
(3)启动生产者往world topic 发送数据。
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-producer.sh --bootstrap-server hadoop102:9092 --topic world
>hello
>hello2
>hello3
(4)启动消费者消费 world topic 数据。
[hyj@hadoop104 kafka-3.0.0]$ bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic world --group test
hello
hello2
hello3
注意:指定消费者组名称,能够更好观察数据存储位置(key 是group.id+topic+分区号)。
(5)查看消费者消费主题__consumer_offsets。(--from-beginning
从头开始消费)
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic __consumer_offsets --consumer.config config/consumer.properties --formatter "kafka.coordinator.group.GroupMetadataManager\$OffsetsMessageFormatter" --from-beginning
[test,world,0]::OffsetAndMetadata(offset=3, leaderEpoch=Optional[0], metadata=, commitTimestamp=1651852455580, expireTimestamp=None)
[test,world,1]::OffsetAndMetadata(offset=3, leaderEpoch=Optional[0], metadata=, commitTimestamp=1651852455580, expireTimestamp=None)
为了使我们能够专注于自己的业务逻辑,Kafka提供了自动提交offset的功能。
自动提交offset的相关参数:
package com.hyj.kafka.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.time.Duration;
import java.util.Arrays;
import java.util.Properties;
public class CustomConsumerAutoOffset {
public static void main(String[] args) {
// 1. 创建 kafka 消费者配置对象
Properties properties = new Properties();
// 2. 添加配置参数
// 给消费者配置对象添加参数 连接集群
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");
// 配置反序列化 必须
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");
// 配置消费者组
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test");
// 是否自动提交 offset
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
// 提交 offset 的时间周期 1000ms,默认 5s
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 1000);
//3. 创建 kafka 消费者
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
//4. 设置消费主题 形参是列表
consumer.subscribe(Arrays.asList("first"));
//5. 消费数据
while (true) {
// 读取消息 (1s消费一批数据)
ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofSeconds(1));
// 输出消息
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord.value());
}
}
}
}
虽然自动提交offset十分简单便利,但由于其是基于时间提交的,开发人员难以把握offset提交的时机。因 此Kafka还提供了手动提交offset的API。
手动提交offset的方法有两种:分别是commitSync(同步提交)
和commitAsync(异步提交
)。两者的相同点是,都会将本次提交的一批数据最高的偏移量提交
;不同点是,同步提交阻塞当前线程
,一直到提交成功,并且会自动失败重试(由不可控因素导致,也会出现提交失败);而异步提交则没有失败重试机制
,故有可能提交失败。
• commitSync(同步提交):必须等待offset提交完毕,再去消费下一批数据。
• commitAsync(异步提交) :发送完提交offset请求后,就开始消费下一批数据了。
1)同步提交 offset
由于同步提交 offset 有失败重试机制,故更加可靠,但是由于一直等待提交结果,提交的效率比较低。以下为同步提交 offset 的示例。
package com.hyj.kafka.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.time.Duration;
import java.util.Arrays;
import java.util.Properties;
public class CustomConsumerByHandSync {
public static void main(String[] args) {
// 1. 创建 kafka 消费者配置类
Properties properties = new Properties();
// 2. 添加配置参数
// 添加连接
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");
// 配置反序列化 必须
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");
// 配置消费者组
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test");
// 是否自动提交 offset
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,false);
//3. 创建 kafka 消费者
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
//4. 设置消费主题 形参是列表
consumer.subscribe(Arrays.asList("first"));
//5. 消费数据
while (true){
// 读取消息 (1s消费一批数据)
ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofSeconds(1));
// 打印消费到的数据
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord.value());
}
// 同步提交 offset
consumer.commitSync();
}
}
}
2)异步提交 offset
虽然同步提交 offset 更可靠一些,但是由于其会阻塞当前线程,直到提交成功。因此吞吐量会受到很大的影响。因此更多的情况下,会选用异步提交 offset 的方式。
以下为异步提交 offset 的示例:
package com.hyj.kafka.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.time.Duration;
import java.util.Arrays;
import java.util.Properties;
public class CustomConsumerByHandAsync {
public static void main(String[] args) {
// 1. 创建 kafka 消费者配置类
Properties properties = new Properties();
// 2. 添加配置参数
// 添加连接
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");
// 配置反序列化 必须
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");
// 配置消费者组
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test");
// 是否自动提交 offset
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,false);
//3. 创建 kafka 消费者
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
//4. 设置消费主题 形参是列表
consumer.subscribe(Arrays.asList("first"));
//5. 消费数据
while (true){
// 读取消息 (1s消费一批数据)
ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofSeconds(1));
// 打印消费到的数据
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord.value());
}
// 异步提交 offset
consumer.commitAsync();
}
}
}
auto.offset.reset = earliest | latest | none
,默认是 latest。
当 Kafka 中没有初始偏移量(消费者组第一次消费)或服务器上不再存在当前偏移量时(例如该数据已被删除),该怎么办?
(1)earliest
:自动将偏移量重置为最早的偏移量,--from-beginning
。
(2)latest
(默认值):自动将偏移量重置为最新偏移量。
(3)none:如果未找到消费者组的先前偏移量,则向消费者抛出异常。
(4)任意指定 offset 位移开始消费
package com.hyj.kafka.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
import java.util.Set;
public class CustomConsumerSeek {
public static void main(String[] args) {
// 0 配置信息
Properties properties = new Properties();
// 连接
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");
// key value 反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test4");
// 1 创建一个消费者
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// int i=0;
// 2 订阅一个主题
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
// 获取消费者分区分配信息(有了分区分配信息才能开始消费)(如果还没有进行分配,或者分区正在重新分配的过程中,则可能为none)。
Set<TopicPartition> assignment= kafkaConsumer.assignment();
//保证分区分配方案已经制定完毕
while (assignment.size() == 0) {
//拉取数据,加快获取分区分配方案 (新消费者第一次调用Poll方法,负责发现GroupCoordinator. 通过GroupCoordinator加入消费者组,收到分配给它的分区.)
kafkaConsumer.poll(Duration.ofSeconds(1));
// i++;
//重新获取消费者分区分配信息
assignment = kafkaConsumer.assignment();
}
// System.out.println("\t i="+i); i=32
// 遍历所有分区,并指定 offset 从 2300 的位置开始消费
for (TopicPartition tp: assignment) {
kafkaConsumer.seek(tp, 2300);
}
// 3 消费该主题数据
while (true) {
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
注意:每次执行完,需要修改消费者组名;
需求:在生产环境中,会遇到最近消费的几个小时数据异常,想重新按照时间消费。
例如要求按照时间消费前一天的数据,怎么处理?
package com.hyj.kafka.consumer;
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.*;
public class CustomConsumerForTime {
public static void main(String[] args) {
Properties properties = new Properties();
// 连接
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");
// key value 反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test2");
// 1 创建一个消费者
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// 2 订阅一个主题
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
Set<TopicPartition> assignment = kafkaConsumer.assignment();
while (assignment.size() == 0) {
kafkaConsumer.poll(Duration.ofSeconds(1));
// 获取消费者分区分配信息(有了分区分配信息才能开始消费)
assignment = kafkaConsumer.assignment();
}
// 希望把时间转换为对应的offset
HashMap<TopicPartition, Long> topicPartitionLongHashMap = new HashMap<>();
// 封装对应的集合,每个分区对应一天前的数据
for (TopicPartition topicPartition : assignment) {
topicPartitionLongHashMap.put(topicPartition, System.currentTimeMillis() - 1 * 24 * 3600 * 1000);
}
// 获取从 1 天前开始消费的每个分区的 offset (按时间戳查找给定分区的偏移量)
Map<TopicPartition, OffsetAndTimestamp> offsets = kafkaConsumer.offsetsForTimes(topicPartitionLongHashMap);
// 遍历每个分区,对每个分区设置消费时间。
for (TopicPartition topicPartition : assignment) {
OffsetAndTimestamp offsetAndTimestamp = offsets.get(topicPartition);
// 根据时间指定开始消费的位置
if (offsetAndTimestamp != null){
kafkaConsumer.seek(topicPartition, offsetAndTimestamp.offset());
}
}
// 3 消费该主题数据
while (true) {
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
重复消费:已经消费了数据,但是 offset 没提交
。
漏消费:先提交 offset 后消费,有可能会造成数据的漏消费
。
重复消费的例子:
//5. 消费数据
while (true){
// 读取消息 (1s消费一批数据) 在这个指定的时间段里拉取,时间到了就立刻返回数据
ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofSeconds(1));
// 打印消费到的数据
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord.value());
}
// 延迟5秒,可以看到更好的效果
Thread.sleep(5000);
// 异步提交 offset
consumer.commitAsync();
}
(2)场景1:漏消费。设置offset为手动提交,当offset被提交时,数据还在内存中未落盘,此时刚好消费者线 程被kill掉,那么offset已经提交,但是数据未处理,导致这部分内存中的数据丢失.
漏消费的例子:
//5. 消费数据
while (true){
// 读取消息 (1s消费一批数据)
ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofSeconds(1));
// 异步提交 offset (先提交,再消费)
consumer.commitAsync();
// 延迟3秒,可以看到更好的效果
Thread.sleep(3000);
// 打印消费到的数据
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord.value());
}
// 异步提交 offset
consumer.commitAsync();
}
思考:怎么能做到既不漏消费也不重复消费呢?详看消费者事务。
如果想完成Consumer端的精准一次性消费,那么需要Kafka消费端将消费过程和提交offset过程做原子绑定
。此时我们需要将Kafka的offset保存到支持事务的自定义介质(比 如MySQL)。这部分知识会在后续项目部分涉及。
1)如果是Kafka消费能力不足,则可以考虑增 加Topic的分区数,并且同时提升消费组的消费者数量,消费者数 = 分区数。(两者缺一不可)
2)如果是下游的数据处理不及时:提高每批次拉取的数量。批次拉取数据过少(拉取数据/处理时间 < 生产速度),使处理的数据小于生产的数据,也会造成数据积压。
这两个参数是配套使用的:
单次 poll 最大等待时间,consumer.poll(Duration.ofSeconds(1)) 意味着在缓存中的数据被消费完之后,consumer 会到 broker 中拉取数据,最多等待 1秒。timeout 越大,单次 poll 返回的数据就越多。但前提是有足够多的数据可供消费,如果在 1秒内,consumer 已经拿完所有能拿的数据,就会立即返回。
Kafka-Eagle 框架可以监控 Kafka 集群的整体运行情况,在生产环境中经常使用。
Kafka-Eagle 的安装依赖于 MySQL,MySQL 主要用来存储可视化展示的数据。如果集群中之前安装过 MySQL 可以跨过该步。
1.安装包准备
(1)将安装包和JDBC驱动上传到/opt/software,共计6个
01_mysql-community-common-5.7.16-1.el7.x86_64.rpm
02_mysql-community-libs-5.7.16-1.el7.x86_64.rpm
03_mysql-community-libs-compat-5.7.16-1.el7.x86_64.rpm
04_mysql-community-client-5.7.16-1.el7.x86_64.rpm
05_mysql-community-server-5.7.16-1.el7.x86_64.rpm
mysql-connector-java-5.1.27-bin.jar
(2)卸载自带的Mysql-libs(如果之前安装过MySQL,要全部卸载掉,卸载mysql 或者mariadb数据库)
[hyj@hadoop102 software]$ rpm -qa | grep -i -E 'mysql|mariadb' | xargs -n1 sudo rpm -e --nodeps
-i:忽略字符的大小写
-E 选项可以用来扩展选项为正则表达式。 如果使用了grep 命令的选项-E,则应该使用|
来分割多个pattern,以此实现OR操作
。
grep -E 可以传递多个内容 ,使用 | 来分割多个pattern,以此实现OR操作
grep -E ‘pattern1|pattern2’ filename
grep -e 只能传递一个检索内容
grep -e pattern1 -e pattern2 filename
2.安装MySql
(1)安装MySQL依赖
[hyj@hadoop102 software]$ sudo rpm -ivh 01_mysql-community-common-5.7.16-1.el7.x86_64.rpm
[hyj@hadoop102 software]$ sudo rpm -ivh 02_mysql-community-libs-5.7.16-1.el7.x86_64.rpm
[hyj@hadoop102 software]$ sudo rpm -ivh 03_mysql-community-libs-compat-5.7.16-1.el7.x86_64.rpm
(2)安装mysql-client
[hyj@hadoop102 software]$ sudo rpm -ivh 04_mysql-community-client-5.7.16-1.el7.x86_64.rpm
(3)安装mysql-server
[hyj@hadoop102 software]$ sudo rpm -ivh 05_mysql-community-server-5.7.16-1.el7.x86_64.rpm
(4)启动MySQL
[hyj@hadoop102 software]$ sudo systemctl start mysqld
(5)查看MySQL密码
[hyj@hadoop102 software]$ sudo cat /var/log/mysqld.log | grep password
3.配置MySQL
配置只要是root用户+密码,在任何主机上都能登录MySQL数据库。
(1)用刚刚查到的密码进入mysql(如果报错,给密码加单引号)
mysql -uroot -p’password’
[hyj@hadoop102 software]$ mysql -uroot -p'Pga)w&5%8tdf'
(2)设置复杂密码(由于mysql密码策略,此密码必须足够复杂)
mysql> set password=password("Qs12=Ab34");
(3)更改mysql密码策略
mysql> set global validate_password_length=4;
mysql> set global validate_password_policy=0;
若是报以下错误:
ERROR 1193 (HY000): Unknown system variable 'validate_password_length'
ERROR 1193 (HY000): Unknown system variable 'validate_password_policy'
则执行以下:
mysql> install plugin validate_password soname 'validate_password.so';
mysql> set global validate_password_length=4;
mysql> set global validate_password_policy=0;
(4)设置简单好记的密码
mysql> set password=password(“0000”);
(5)进入msyql库
mysql> use mysql
(6)查询user表
mysql> select user,host from user;
+---------------+-----------+
| user | host |
+---------------+-----------+
| mysql.session | localhost |
| mysql.sys | localhost |
| root | localhost |
+---------------+-----------+
(7)修改user表,把Host表内容修改为%
mysql> update user set host="%" where user="root";
(8)刷新
mysql> flush privileges;
(9)退出
mysql> quit;
1)关闭 Kafka 集群
[hyj@hadoop105 kafka-3.0.0]$ bin/kafka-server-stop.sh
[hyj@hadoop102 ~]$ mykafka.sh stop
2)修改/opt/module/kafka-3.0.0/bin/kafka-server-start.sh 命令
[hyj@hadoop102 bin]$ vim kafka-server-start.sh
kafka的默认内存是1个G,因为我们需要使用Kafka-Eagle的监控功能,1个G内存可能不够,所以需要把它增加到2G.
修改如下参数值:
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" #监控kafka运行情况的端口号
#export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G"
fi
注意:修改之后在启动 Kafka 之前要分发之其他节点
[hyj@hadoop102 bin]$ xsync kafka-server-start.sh
3)启动kafka
[hyj@hadoop102 bin]$ mykafka.sh start
0)官网:https://www.kafka-eagle.org/
1)上传压缩包 kafka-eagle-bin-2.0.8.tar.gz 到集群/opt/software 目录下
2)解压到当前目录
[hyj@hadoop102 software]$ tar -zxvf kafka-eagle-bin-2.0.8.tar.gz
3)进入刚才解压的目录
[hyj@hadoop102 software]$ cd kafka-eagle-bin-2.0.8/
[hyj@hadoop102 kafka-eagle-bin-2.0.8]$ ls
efak-web-2.0.8-bin.tar.gz
4)将 efak-web-2.0.8-bin.tar.gz 解压至/opt/module
[hyj@hadoop102 kafka-eagle-bin-2.0.8]$ tar -zxvf efak-web-2.0.8-bin.tar.gz -C /opt/module/
5)修改名称
[hyj@hadoop102 module]$ mv efak-web-2.0.8/ efak
6)修改配置文件 /opt/module/efak/conf/system-config.properties
[hyj@hadoop102 conf]$ vim system-config.properties
efak.zk.cluster.alias=cluster1,cluster2
cluster1.zk.list=tdn1:2181,tdn2:2181,tdn3:2181
cluster2.zk.list=xdn10:2181,xdn11:2181,xdn12:2181
将其修改为:
efak.zk.cluster.alias=cluster1
cluster1.zk.list=hadoop102:2181,hadoop103:2181,hadoop104:2181/kafka
# 要想找到kafka相关信息就需要加上/kafka节点
cluster1.efak.offset.storage=kafka
cluster2.efak.offset.storage=zk
将其修改为:
# offset 保存在 kafka
cluster1.efak.offset.storage=kafka
efak.driver=org.sqlite.JDBC
efak.url=jdbc:sqlite:/hadoop/kafka-eagle/db/ke.db
efak.username=root
efak.password=www.kafka-eagle.org
将其修改为:
# 配置 mysql 连接
efak.driver=com.mysql.jdbc.Driver
efak.url=jdbc:mysql://hadoop102:3306/ke?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
efak.username=root
efak.password=0000
7)添加环境变量
[hyj@hadoop102 conf]$ sudo vim /etc/profile.d/my_env.sh
添加如下内容:
# kafkaEFAK
export KE_HOME=/opt/module/efak
export PATH=$PATH:$KE_HOME/bin
使环境变量生效
[hyj@hadoop102 conf]$ source /etc/profile
8)启动
(1)注意:启动之前需要先启动 ZK 以及 KAFKA。
(2)启动 efak
[hyj@hadoop102 efak]$ bin/ke.sh start
* EFAK Service has started success.
* Welcome, Now you can visit 'http://192.168.10.102:8048'
* Account:admin ,Password:123456
说明:如果停止 efak,执行命令。
[hyj@hadoop102 efak]$ bin/ke.sh stop
1)登录页面查看监控数据
http://192.168.10.102:8048/
左图为 Kafka 现有架构,元数据在 zookeeper 中,运行时动态选举 controller,由controller 进行 Kafka 集群管理。
右图为 kraft 模式架构(实验性),不再依赖 zookeeper 集群,而是用三台 controller 节点代替 zookeeper,元数据保存在 controller 中,由 controller 直接进行 Kafka 集群管理。
这样做的好处有以下几个:
1)停止efak,kafka和zookeeper
[hyj@hadoop102 efak]$ bin/ke.sh stop
[hyj@hadoop102 efak]$ mykafka.sh stop
[hyj@hadoop102 efak]$ zk.sh stop
2)再次解压一份 kafka 安装包
[hyj@hadoop102 software]$ tar -zxvf kafka_2.12-3.0.0.tgz -C /opt/module/
3)重命名为 kafka2
[hyj@hadoop102 module]$ mv kafka_2.12-3.0.0/ kafka2
4)在 hadoop102 上修改/opt/module/kafka2/config/kraft/server.properties 配置文件
[hyj@hadoop102 kraft]$ vim server.properties
#kafka 的角色(controller 相当于主机、broker 节点相当于从机,主机类似 zk 功 能)
process.roles=broker, controller
#节点 ID
node.id=2 #修改此处
#controller 服务协议别名
controller.listener.names=CONTROLLER
#全 Controller 列表
controller.quorum.voters=2@hadoop102:9093,3@hadoop103:9093,4@hadoop104:9093 # 修改此处
#不同服务器绑定的端口
listeners=PLAINTEXT://:9092,CONTROLLER://:9093
#broker 服务协议别名
inter.broker.listener.name=PLAINTEXT
#broker 对外暴露的地址
advertised.Listeners=PLAINTEXT://hadoop102:9092 #修改此处
#协议别名到安全协议的映射
listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLA
INTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL
#kafka 数据存储目录
log.dirs=/opt/module/kafka2/data #修改此处
5)分发 kafka2
[hyj@hadoop102 module]$ xsync kafka2/
node.id 做相应改变
, 值 需 要 和controller.quorum.voters 对应。advertised.Listeners 地址
。[hyj@hadoop103 kraft]$ vim server.properties
node.id=3
advertised.Listeners=PLAINTEXT://hadoop103:9092
[hyj@hadoop104 kraft]$ vim server.properties
node.id=4
advertised.Listeners=PLAINTEXT://hadoop104:9092
6)初始化集群数据目录
(1)首先生成存储目录唯一 ID。
[hyj@hadoop102 kafka2]$ bin/kafka-storage.sh random-uuid
U5XOyIg5TpW1VGF_GeW4pQ
(2)用该 ID 格式化 kafka 存储目录(三台节点)。
[hyj@hadoop102 kafka2]$ bin/kafka-storage.sh format -t U5XOyIg5TpW1VGF_GeW4pQ -c /opt/module/kafka2/config/kraft/server.properties
[hyj@hadoop103 kafka2]$ bin/kafka-storage.sh format -t U5XOyIg5TpW1VGF_GeW4pQ -c /opt/module/kafka2/config/kraft/server.properties
[hyj@hadoop104 kafka2]$ bin/kafka-storage.sh format -t U5XOyIg5TpW1VGF_GeW4pQ -c /opt/module/kafka2/config/kraft/server.properties
7)启动 kafka 集群
[hyj@hadoop102 kafka2]$ bin/kafka-server-start.sh -daemon config/kraft/server.properties
[hyj@hadoop103 kafka2]$ bin/kafka-server-start.sh -daemon config/kraft/server.properties
[hyj@hadoop104 kafka2]$ bin/kafka-server-start.sh -daemon config/kraft/server.properties
8)停止 kafka 集群
[hyj@hadoop102 kafka2]$ bin/kafka-server-stop.sh
[hyj@hadoop103 kafka2]$ bin/kafka-server-stop.sh
[hyj@hadoop104 kafka2]$ bin/kafka-server-stop.sh
1)在/home/hyj/bin 目录下创建 kf2.sh 脚本文件
[hyj@hadoop102 bin]$ vim kf2.sh
脚本如下:
#! /bin/bash
case $1 in
"start"){
for i in hadoop102 hadoop103 hadoop104
do
echo " --------启动 $i Kafka2-------"
ssh $i "/opt/module/kafka2/bin/kafka-server-start.sh -daemon /opt/module/kafka2/config/kraft/server.properties"
done
};;
"stop"){
for i in hadoop102 hadoop103 hadoop104
do
echo " --------停止 $i Kafka2-------"
ssh $i "/opt/module/kafka2/bin/kafka-server-stop.sh "
done
};;
esac
2)添加执行权限
[hyj@hadoop102 bin]$ chmod 777 kf2.sh
3)分发kf2.sh脚本
[hyj@hadoop102 bin]$ xsync kf2.sh
3)启动集群命令
[hyj@hadoop102 kafka2]$ kf2.sh start
4)停止集群命令
[hyj@hadoop102 kafka2]$ kf2.sh stop
5)创建first topic
[hyj@hadoop102 kafka2]$ kf2.sh start
[hyj@hadoop102 kafka2]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --create --topic first --partitions 3 --replication-factor 3
6)查看当前服务器中的所有 topic
[hyj@hadoop102 kafka2]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --list
first
7)发送消息
[hyj@hadoop102 kafka2]$ bin/kafka-console-producer.sh --bootstrap-server hadoop102:9092 --topic first
>hello
8)消费消息
[hyj@hadoop103 kafka2]$ bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic first
hello
Flume是一个在大数据开发中非常常用的组件。可以用于Kafka的生产者,也可以用于Kafka的消费者。
[hyj@hadoop103 kafka-3.0.0]$ zk.sh start
[hyj@hadoop103 kafka-3.0.0]$ mykafka.sh start
(2)启动kafka消费者 ,来消费first主题的数据
[hyj@hadoop103 kafka-3.0.0]$ bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic first
(3)在hadoop102节点的/opt/module/flume-1.9.0/jobs目录下创建file_to_kafka.conf
[hyj@hadoop102 flume]$ mkdir jobs
[hyj@hadoop102 flume-1.9.0]$ cd jobs/
[hyj@hadoop102 jobs]$ vim file_to_kafka.conf
配置文件内容如下:
# Name the components on this agent
#a1表示agent的名称
a1.sources = r1 # r1表示a1的source的名称
a1.sinks = k1 #k1表示a1的sink的名称
a1.channels = c1 #c1表示a1的channel的名称
# Describe/configure the source
#定义source的类型为taildir
a1.sources.r1.type = TAILDIR
#以空格分隔的文件组列表,每个文件组表示要尾处理的一组文件(下面只有一个文件组)
a1.sources.r1.filegroups = f1
a1.sources.r1.filegroups.f1 = /opt/module/applog/app.*
#JSON格式的文件,记录inode、绝对路径和 读取到的最新的位置
a1.sources.r1.positionFile = /opt/module/flume-1.9.0/taildir_position.json
# Use a channel which buffers events in memory
#表示a1的channel类型是memory内存型
a1.channels.c1.type = memory
#表示a1的channel总容量是1000个event
a1.channels.c1.capacity = 1000
#表示a1的channel从source接收或给sink的最大事件数
a1.channels.c1.transactionCapacity = 100
# Describe the sink
a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k1.kafka.bootstrap.servers = hadoop102:9092,hadoop103:9092,hadoop104:9092
a1.sinks.k1.kafka.topic = first
a1.sinks.k1.kafka.flumeBatchSize = 20
a1.sinks.k1.kafka.producer.acks = 1
a1.sinks.k1.kafka.producer.linger.ms = 1
# Bind the source and sink to the channel
#表示将r1和c1连接起来
a1.sources.r1.channels = c1
#表示将k1和c1连接起来
a1.sinks.k1.channel = c1
(4)启动Flume (&表示在后台运行)
[hyj@hadoop102 flume-1.9.0]$ bin/flume-ng agent -n a1 -c conf/ -f jobs/file_to_kafka.conf &
(5)向/opt/module/applog/app.log里追加数据,查看kafka消费者消费情况
[hyj@hadoop102 module]$ mkdir applog
[hyj@hadoop102 module]$ cd applog/
[hyj@hadoop102 applog]$ vim app.log
hello
[hyj@hadoop102 applog]$ echo hello >> app.log
(6)观察kafka消费者,能够看到消费的hello数据
(1)配置Flume
在hadoop102节点上的/opt/module/flume-1.9.0/jobs目录下创建kafka_to_file.conf配置文件
[hyj@hadoop102 jobs]$ vim kafka_to_file.conf
配置文件内容如下 :
# Name the components on this agent
#a1表示agent的名称
a1.sources = r1 # r1表示a1的source的名称
a1.sinks = k1 #k1表示a1的sink的名称
a1.channels = c1 #c1表示a1的channel的名称
# Describe/configure the source
a1.sources.r1.type = org.apache.flume.source.kafka.KafkaSource
#一次batch中最大可写入channel的消息数(当达到batchSize大小时写入到channel)
a1.sources.r1.batchSize = 50
#当batchSize没有达到最大值的时候,超过这个时间也会写入到 channel
a1.sources.r1.batchDurationMillis = 200
a1.sources.r1.kafka.bootstrap.servers = hadoop102:9092
a1.sources.r1.kafka.topics = first
a1.sources.r1.kafka.consumer.group.id = custom.g.id
# Use a channel which buffers events in memory
#表示a1的channel类型是memory内存型
a1.channels.c1.type = memory
#表示a1的channel总容量是1000个event
a1.channels.c1.capacity = 1000
#表示a1的channel从source接收或给sink的最大事件数
a1.channels.c1.transactionCapacity = 100
# Describe the sink
#表示a1的输出目的地是控制台 logger类型
a1.sinks.k1.type = logger
# Bind the source and sink to the channel
#表示将r1和c1连接起来
a1.sources.r1.channels = c1
#表示将k1和c1连接起来
a1.sinks.k1.channel = c1
(2)启动Flume
[hyj@hadoop102 flume-1.9.0]$ bin/flume-ng agent -n a1 -c conf/ -f jobs/kafka_to_file.conf -Dflume.root.logger=INFO,console
(3)启动kafka生产者
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-producer.sh --bootstrap-server hadoop102:9092 --topic first
>hello
(4)观察控制台输出的日志
正在更新中…
正在更新中…
正在更新中…
100万日活,每人每天100条日志,每天总共的日志条数是100万 * 100条 = 1亿条。
1亿/24小时/60分/60秒 = 1150条/每秒钟。
每条日志大小:0.5k - 2k(取1k)。
1150条/每秒钟 * 1k ≈ 1m/s 。
高峰期每秒钟:1150条 * 20倍 = 23000条。
每秒多少数据量:20MB/s。
服务器台数= 2 * (生产者峰值生产速率 * 副本 / 100) + 1
= 2 * (20m/s * 2 / 100) + 1 = 3台
建议3台服务器。
因为20*2/100=0.4是一个小数,所以往上取1.
kafka底层主要是顺序写
,固态硬盘和机械硬盘的顺序写速度差不多
。
建议选择普通的机械硬盘。
每天总数据量:1亿条 * 1k ≈ 100g
100g * 副本2 * 保存时间3天 / 0.7 ≈ 1T
建议三台服务器硬盘总大小,大于等于1T。
Kafka内存组成:堆内存 (kafka内部配置)+ 页缓存 (服务器内存)
if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then
export KAFKA_HEAP_OPTS="-Xmx10G -Xms10G"
fi
(1)查看Kafka进程号
[hyj@hadoop102 kafka-3.0.0]$ jps
3106 Kafka
3260 Jps
2669 QuorumPeerMain
(2)根据Kafka进程号,查看Kafka的GC情况
[hyj@hadoop102 kafka-3.0.0]$ jstat -gc 3106 1s 10
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
0.0 13312.0 0.0 13312.0 97280.0 4096.0 1986560.0 139624.0 46332.0 42667.6 5888.0 5431.5 4 0.679 0 0.000 0.679
0.0 13312.0 0.0 13312.0 97280.0 4096.0 1986560.0 139624.0 46332.0 42667.6 5888.0 5431.5 4 0.679 0 0.000 0.679
0.0 13312.0 0.0 13312.0 97280.0 4096.0 1986560.0 139624.0 46332.0 42667.6 5888.0 5431.5 4 0.679 0 0.000 0.679
0.0 13312.0 0.0 13312.0 97280.0 4096.0 1986560.0 139624.0 46332.0 42667.6 5888.0 5431.5 4 0.679 0 0.000 0.679
0.0 13312.0 0.0 13312.0 97280.0 5120.0 1986560.0 139624.0 46332.0 42667.6 5888.0 5431.5 4 0.679 0 0.000 0.679
0.0 13312.0 0.0 13312.0 97280.0 5120.0 1986560.0 139624.0 46332.0 42667.6 5888.0 5431.5 4 0.679 0 0.000 0.679
0.0 13312.0 0.0 13312.0 97280.0 5120.0 1986560.0 139624.0 46332.0 42667.6 5888.0 5431.5 4 0.679 0 0.000 0.679
0.0 13312.0 0.0 13312.0 97280.0 5120.0 1986560.0 139624.0 46332.0 42667.6 5888.0 5431.5 4 0.679 0 0.000 0.679
0.0 13312.0 0.0 13312.0 97280.0 6144.0 1986560.0 139624.0 46332.0 42667.6 5888.0 5431.5 4 0.679 0 0.000 0.679
0.0 13312.0 0.0 13312.0 97280.0 6144.0 1986560.0 139624.0 46332.0 42667.6 5888.0 5431.5 4 0.679 0 0.000 0.679
参数说明:
S0C:第一个幸存区的大小; S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小; S1U:第二个幸存区的使用大小
EC:伊甸园区的大小; EU:伊甸园区的使用大小
OC:老年代大小; OU:老年代使用大小
MC:方法区大小; MU:方法区使用大小
CCSC:压缩类空间大小; CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
; YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数; FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间;
(3)根据Kafka进程号,查看Kafka的堆内存
[hyj@hadoop102 kafka-3.0.0]$ jmap -heap 3106
Attaching to process ID 3106, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.212-b10
using thread-local object allocation.
Garbage-First (G1) GC with 8 thread(s)
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 2147483648 (2048.0MB)
NewSize = 1363144 (1.2999954223632812MB)
MaxNewSize = 1287651328 (1228.0MB)
OldSize = 5452592 (5.1999969482421875MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 1048576 (1.0MB)
Heap Usage:
G1 Heap:
regions = 2048
capacity = 2147483648 (2048.0MB)
used = 209035280 (199.35157775878906MB)
free = 1938448368 (1848.648422241211MB)
9.733963757753372% used
G1 Young Generation:
Eden Space:
regions = 50
capacity = 99614720 (95.0MB)
used = 52428800 (50.0MB)
free = 47185920 (45.0MB)
52.63157894736842% used
Survivor Space:
regions = 13
capacity = 13631488 (13.0MB)
used = 13631488 (13.0MB)
free = 0 (0.0MB)
100.0% used
G1 Old Generation:
regions = 138
capacity = 2034237440 (1940.0MB)
used = 142974992 (136.35157775878906MB)
free = 1891262448 (1803.648422241211MB)
7.028431843236549% used
12706 interned Strings occupying 1397424 bytes.
2)页缓存:页缓存是Linux系统服务器的内存。我们只需要保证1个segment(1g)中25%的数据在内存中就好。(每个分区有多个segment. 让最新的segment的25%缓存到页缓存中)
每个节点页缓存大小 =(Leader分区数 * 1g * 25%)/ 节点数
。例如集群中有10个分区,页缓存大小=(10 * 1g * 25%)/ 3 ≈ 1g
建议服务器内存大于等于11G(10+1g)。
num.io.threads = 8 负责写磁盘的线程数,整个参数值要占总核数的50%。 num.replica.fetchers = 1 副本拉取线程数,这个参数占总核数的50%的1/3。
num.network.threads = 3 数据传输线程数,这个参数占总核数的50%的2/3。
建议32个cpu core
。
如果有32个cpu core,分24+8,24给上面3个参数使用,整体服务器再预留8cpu core(8用于其他线程的使用),这样的话kafka跑起来就不会出现卡顿,效率会比较高.
num.io.threads占24的50%,即12.
num.replica.fetchers占24的50%的1/3,即4.
num.network.threads占24的50%的2/3,即8.
网络带宽 = 峰值吞吐量 ≈ 20MB/s 选择千兆网卡即可。
100Mbps单位是bit;10M/s单位是byte ; 1byte = 8bit,100Mbps/8 = 12.5M/s。
一般百兆的网卡(100Mbps )、千兆的网卡(1000Mbps)、万兆的网卡(10000Mbps)。
Updating Broker Configs
From Kafka version 1.1 onwards,
some of the broker configs can be updated without restarting the broker.
See the Dynamic Update Mode column in Broker Configs for the update mode of each broker config.
read-only: Requires a broker restart for update
per-broker: May be updated dynamically for each broker
cluster-wide: May be updated dynamically as a cluster-wide default.
May also be updated as a per-broker value for testing.
更新broker配置:
从Kafka 1.1版本开始,一些broker配置可以在不重启broker的情况下更新。有关每个broker配置的更新模式,请参阅代理配置中的动态更新模式列。
read-only: 需要重新启动broker进行更新
per-broker: 可以为每个broker动态更新(不需要重启集群,针对的是单个broker节点)
cluster-wide: 可以作为集群范围的默认值动态更新(针对集群)。
也可以更新为每个broker的值以用于测试。
至少一次(At Least Once)= ACK级别设置为-1 + 分区副本大于等于2 + ISR里应答的最小副本数量大于等于2
// 1初始化事务
void initTransactions();
// 2开启事务
void beginTransaction() throws ProducerFencedException;
// 3在事务内提交已经消费的偏移量(主要用于消费者)
void sendOffsetsToTransaction(Map<TopicPartition, OffsetAndMetadata> offsets, String consumerGroupId) throws ProducerFencedException;
// 4提交事务
void commitTransaction() throws ProducerFencedException;
// 5放弃事务(类似于回滚事务的操作)
void abortTransaction() throws ProducerFencedException;
单分区内,有序(有条件的,不能乱序);多分区,分区与分区间无序;
如果broker端配置参数auto.create.topics.enable设置为true(默认值是true),那么当生产者向一个未创建的主题发送消息时,会自动创建一个分区数为num.partitions(默认值为1)、副本因子为default.replication.factor(默认值为1)的主题
。除此之外,当一个消费者开始从未知主题中读取消息时,或者当任意一个客户端向未知主题发送元数据请求时,都会自动创建一个相应主题。这种创建主题的方式是非预期的,增加了主题管理和维护的难度。生产环境建议将该参数设置为false
1)向一个没有提前创建five主题发送数据
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-console-producer.sh --bootstrap-server hadoop102:9092 --topic five
>hello world
2)查看five主题的详情
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --describe --topic five
增加分区数:
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --alter --topic first --partitions 3
如何提升吞吐量?
在生产环境中,如果某个Kafka节点挂掉。 正常处理办法:
用Kafka官方自带的脚本,对Kafka进行压测。
(1)创建一个test topic,设置为3个分区3个副本
[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --create --topic test --partitions 3 --replication-factor 3
(2)在/opt/module/kafka-3.0.0/bin目录下面有这两个文件。我们来测试一下
[hyj@hadoop105 kafka-3.0.0]$ 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
输出结果如下:9.76 MB/sec
49887 records sent, 9977.4 records/sec (9.74 MB/sec), 153.0 ms avg latency, 697.0 ms max latency.
49747 records sent, 9876.3 records/sec (9.64 MB/sec), 91.5 ms avg latency, 625.0 ms max latency.
50615 records sent, 10123.0 records/sec (9.89 MB/sec), 53.7 ms avg latency, 294.0 ms max latency.
49980 records sent, 9996.0 records/sec (9.76 MB/sec), 41.3 ms avg latency, 267.0 ms max latency.
49748 records sent, 9949.6 records/sec (9.72 MB/sec), 12.8 ms avg latency, 90.0 ms max latency.
50281 records sent, 10056.2 records/sec (9.82 MB/sec), 53.2 ms avg latency, 253.0 ms max latency.
46491 records sent, 9298.2 records/sec (9.08 MB/sec), 629.1 ms avg latency, 1582.0 ms max latency.
53503 records sent, 10696.3 records/sec (10.45 MB/sec), 105.9 ms avg latency, 813.0 ms max latency.
50025 records sent, 10001.0 records/sec (9.77 MB/sec), 22.6 ms avg latency, 228.0 ms max latency.
50065 records sent, 10009.0 records/sec (9.77 MB/sec), 22.4 ms avg latency, 184.0 ms max latency.
39645 records sent, 7842.7 records/sec (7.66 MB/sec), 10.2 ms avg latency, 1092.0 ms max latency.
59897 records sent, 11756.0 records/sec (11.48 MB/sec), 324.2 ms avg latency, 1095.0 ms max latency.
51322 records sent, 10264.4 records/sec (10.02 MB/sec), 303.3 ms avg latency, 1051.0 ms max latency.
45296 records sent, 8355.7 records/sec (8.16 MB/sec), 29.4 ms avg latency, 961.0 ms max latency.
33135 records sent, 6595.3 records/sec (6.44 MB/sec), 1518.0 ms avg latency, 2702.0 ms max latency.
60480 records sent, 12062.2 records/sec (11.78 MB/sec), 2279.2 ms avg latency, 3482.0 ms max latency.
53065 records sent, 10610.9 records/sec (10.36 MB/sec), 851.0 ms avg latency, 1995.0 ms max latency.
63273 records sent, 12654.6 records/sec (12.36 MB/sec), 420.9 ms avg latency, 1491.0 ms max latency.
49983 records sent, 9996.6 records/sec (9.76 MB/sec), 17.0 ms avg latency, 155.0 ms max latency.
1000000 records sent, 9995.102400 records/sec (9.76 MB/sec), 355.55 ms avg latency, 3482.00 ms max latency, 24 ms 50th, 1853 ms 95th, 2931 ms 99th, 3390 ms 99.9th.
参数说明:
(3)调整batch.size大小
①batch.size默认值是16k。本次实验batch.size设置为32k。
[hyj@hadoop105 kafka-3.0.0]$ 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=32768 linger.ms=0
输出结果如下:9.76 MB/sec
49954 records sent, 9990.8 records/sec (9.76 MB/sec), 133.3 ms avg latency, 913.0 ms max latency.
49516 records sent, 9838.3 records/sec (9.61 MB/sec), 10.0 ms avg latency, 97.0 ms max latency.
50768 records sent, 10151.6 records/sec (9.91 MB/sec), 10.9 ms avg latency, 92.0 ms max latency.
50068 records sent, 10013.6 records/sec (9.78 MB/sec), 10.8 ms avg latency, 100.0 ms max latency.
49998 records sent, 9999.6 records/sec (9.77 MB/sec), 11.3 ms avg latency, 77.0 ms max latency.
49976 records sent, 9991.2 records/sec (9.76 MB/sec), 132.7 ms avg latency, 943.0 ms max latency.
50037 records sent, 10007.4 records/sec (9.77 MB/sec), 10.7 ms avg latency, 73.0 ms max latency.
50037 records sent, 10007.4 records/sec (9.77 MB/sec), 14.3 ms avg latency, 176.0 ms max latency.
49968 records sent, 9993.6 records/sec (9.76 MB/sec), 10.1 ms avg latency, 58.0 ms max latency.
50037 records sent, 10007.4 records/sec (9.77 MB/sec), 10.5 ms avg latency, 58.0 ms max latency.
49987 records sent, 9997.4 records/sec (9.76 MB/sec), 8.9 ms avg latency, 71.0 ms max latency.
49926 records sent, 9983.2 records/sec (9.75 MB/sec), 9.9 ms avg latency, 82.0 ms max latency.
50055 records sent, 10011.0 records/sec (9.78 MB/sec), 7.4 ms avg latency, 57.0 ms max latency.
50047 records sent, 10009.4 records/sec (9.77 MB/sec), 8.3 ms avg latency, 57.0 ms max latency.
49981 records sent, 9948.4 records/sec (9.72 MB/sec), 7.3 ms avg latency, 65.0 ms max latency.
50213 records sent, 10042.6 records/sec (9.81 MB/sec), 9.3 ms avg latency, 87.0 ms max latency.
49273 records sent, 9850.7 records/sec (9.62 MB/sec), 11.3 ms avg latency, 87.0 ms max latency.
50828 records sent, 10165.6 records/sec (9.93 MB/sec), 77.3 ms avg latency, 657.0 ms max latency.
49991 records sent, 9998.2 records/sec (9.76 MB/sec), 10.6 ms avg latency, 91.0 ms max latency.
1000000 records sent, 9995.701848 records/sec (9.76 MB/sec), 25.73 ms avg latency, 943.00 ms max latency, 7 ms 50th, 62 ms 95th, 573 ms 99th, 887 ms 99.9th.
②batch.size默认值是16k。本次实验batch.size设置为4k。
[hyj@hadoop105 kafka-3.0.0]$ 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=4096 linger.ms=0
结果为:4.45 MB/sec
(4)调整linger.ms时间
linger.ms默认是0ms。本次实验linger.ms设置为50ms。
[hyj@hadoop105 kafka-3.0.0]$ 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=4096 linger.ms=50
结果为:5.04 MB/sec
(5)调整压缩方式
①默认的压缩方式是none。本次实验compression.type设置为snappy。
[hyj@hadoop105 kafka-3.0.0]$ 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=4096 linger.ms=50 compression.type=snappy
结果为:5.11 MB/sec
②默认的压缩方式是none。本次实验compression.type设置为zstd。
[hyj@hadoop105 kafka-3.0.0]$ 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=4096 linger.ms=50 compression.type=zstd
结果为:6.70 MB/sec
③默认的压缩方式是none。本次实验compression.type设置为gzip。
[hyj@hadoop105 kafka-3.0.0]$ 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=4096 linger.ms=50 compression.type=gzip
结果为:7.64 MB/sec
④默认的压缩方式是none。本次实验compression.type设置为lz4。
[hyj@hadoop105 kafka-3.0.0]$ 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=4096 linger.ms=50 compression.type=lz4
结果为:4.58 MB/sec
(6)调整缓存大小
默认生产者端缓存大小32m。本次实验buffer.memory设置为64m。
[hyj@hadoop105 kafka-3.0.0]$ 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=4096 linger.ms=50 buffer.memory=67108864
结果为:4.80 MB/sec
(1)修改/opt/module/kafka-3.0.0/config/consumer.properties文件中的一次拉取条数为500
max.poll.records=500
(2)消费100万条日志进行压测
[hyj@hadoop105 kafka-3.0.0]$ bin/kafka-consumer-perf-test.sh --bootstrap-server hadoop102:9092,hadoop103:9092,hadoop104:9092 --topic test --messages 1000000 --consumer.config config/consumer.properties
结果为:207.3445 MB.sec
参数说明:
(3)一次拉取条数为2000
①修改/opt/module/kafka-3.0.0/config/consumer.properties文件中的一次拉取条数为2000
max.poll.records=2000
②再次执行
[hyj@hadoop105 kafka-3.0.0]$ bin/kafka-consumer-perf-test.sh --bootstrap-server hadoop102:9092,hadoop103:9092,hadoop104:9092 --topic test --messages 1000000 --consumer.config config/consumer.properties
结果为: 225.3271 MB.sec
(4)调整fetch.max.bytes大小为100m
①修改/opt/module/kafka-3.0.0/config/consumer.properties文件中的拉取一批数据大小为100m
fetch.max.bytes=104857600
②再次执行
[hyj@hadoop105 kafka-3.0.0]$ bin/kafka-consumer-perf-test.sh --bootstrap-server hadoop102:9092,hadoop103:9092,hadoop104:9092 --topic test --messages 1000000 --consumer.config config/consumer.properties
结果为: 267.8677 MB.sec