【All】Kafka从抬脚到入门
一、Kafka简介
1.1、定义
- 旧定义
Kafka 是一个分布式的基于发布/订阅模式的消息队列。 - 新定义
Kafka 是一个开源的分布式事件流平台,用于数据管道、流分析、数据集成和关键任务的应用。
1.2、使用场景
主要用于大数据实时处理领域。
- 缓冲: 有助于控制和优化数据流经过系统的速度。
- 消峰: 在访问量剧增的情况下,保证应用不会因突发的超负荷请求而崩溃。
- 解耦: 保证程序的可扩展性。
- 异步通信:
1.3、使用模式
- 点对点: 消费者主动拉取消息,消息收到后清除消息。
- 发布/订阅模式: 可以有多个topic主题;消息被消费者消费后不会删除;每个消费者相互独立,都可以消费到数据。
1.4、基础架构及概念
最简单的流程:生产者 -> 消息主题 -> 消费者。
考虑到一台主机可能存不下 海量的消息 数据,引入分区,将topic进行 分区;
考虑到分区数据的高可用,引入副本,可为每个分区创建副本;
考虑到一个消费者消费信息的瓶颈,引入 消费者组。
- Producer: 消息生产者,向 Kafka broker 发送消息。
- Consumer: 消息消费者,从 Kafka broker 消费消息。
- Consumer Group(CG):消费者组,由多个Consumer组成。
消费者组与消费者组之间对消息的消费互不影响;
消费者组内的消费者之间,不能消费同一个Topic的同一分区数据,即一个Topic的一个分区只能由同一消费者组内的一个消费者消费。 - Broker: 一台Kafka服务器。一个broker可容纳多个topic。一个集群由多个broker组成。
- Topic: 消息主题,Kafka将一组消息抽象归纳为一个主题。主题就是对消息的分类,生产者将消息发送到特定的主题,消费者订阅主题或是主题的某些分区进行消费。
- Parition: 一个topic可分为多个Parition,每个partition是一个有序队列。
- Replica: 分区副本。每个Parition可设置若干副本。 每个分区的所有副本有且只有一个Leader,其他为Follower。
- Leader: 分区Leader副本,生产者发送数据的对象,以及消费者消费数据的对象都是Leader。
- Follower:分区Leader副本,实时从Leader中同步数据,当Leader故障时,某个follower将成为新的Leader。
- Message:消息。消息是Kafka通信的基本单位,由固定长度的消息头和可变长度的消息体构成。在java重新实现的客户端中,Message被称之为Record。
二、Kafka安装
2.1、准备环境
- Linux操作系统
- Java运行环境(1.8或以上)
- zookeeper 集群环境,可参照 Zookeeper集群部署 。
- 服务器列表:
2.2、准备安装介质
分别登录server1、server2、server3执行,操作、配置相同:
##更新或安装wget命令
yum -y install wget
##创建安装目录
mkdir -p /usr/local/services/kafka
##获取安装包kafka_2.12-3.0.0.tgz
wget http://mirrors.tuna.tsinghua.edu.cn/apache/kafka/3.0.0/kafka_2.12-3.0.0.tgz
##解压缩kafka_2.12-3.0.0.tgz
tar -zxvf kafka_2.12-3.0.0.tgz
2.3、修改环境配置
- 分别登录server1、server2、server3添加主机名,操作、配置相同:
vi /etc/hosts
##添加如下内容
168.5.7.75 server1
168.5.7.76 server2
168.5.7.77 server3
:wq
- 分别登录server1、server2、server3添加kafka环境变量,操作、配置相同:
vi ~/.bash_profile
##添加如下内容
export KAFKA_HOME=/usr/local/services/kafka/kafka_2.12-3.0.0
export PATH=$PATH:$KAFKA_HOME/bin
:wq
source /etc/profile
说明:再任一路径下输入 kafka 按 Tab 键后会补全 Kafka 相关脚本.sh,即Kafka 环境变量配置成功。 因 Kafka 脚本运行时会加载 /config 路径下的相关配置文件,故当不在 Kafka 安装目录 bin 下执行相关脚本时, 需要指定配置文件绝对路径。
2.4、修改Kafka配置
登录server1执行操作:
cd $KAFKA_HOME/config
cp server.properties server.properties.$(date +%Y%m%d)
vi server.properties
## 修改文件内配置如下
## broker 的全局唯一编号,不能重复,只能是数字
broker.id=1
## kafka 运行日志(数据)存放的路径,路径不需要提前创建,kafka 自动帮你创建,可以配置多个磁盘路径,路径与路径之间可以用","分隔
log.dirs=/opt/data/kafka-logs
port=9093
## 配置连接 Zookeeper 集群地址(在 zk 根目录下创建/kafka,方便管理)
zookeeper.connect=server1:2181,server2:2181,server3:2181/kafka
:wq
登录server2执行操作:
cd $KAFKA_HOME/config
cp server.properties server.properties.$(date +%Y%m%d)
vi server.properties
## 修改文件内配置如下
## broker 的全局唯一编号,不能重复,只能是数字
broker.id=2
## kafka 运行日志(数据)存放的路径,路径不需要提前创建,kafka 自动帮你创建,可以配置多个磁盘路径,路径与路径之间可以用","分隔
log.dirs=/opt/data/kafka-logs
port=9093
## 配置连接 Zookeeper 集群地址(在 zk 根目录下创建/kafka,方便管理)
zookeeper.connect=server1:2181,server2:2181,server3:2181/kafka
:wq
登录server3执行操作:
cd $KAFKA_HOME/config
cp server.properties server.properties.$(date +%Y%m%d)
vi server.properties
## 修改文件内配置如下
## broker 的全局唯一编号,不能重复,只能是数字
broker.id=3
## kafka 运行日志(数据)存放的路径,路径不需要提前创建,kafka 自动帮你创建,可以配置多个磁盘路径,路径与路径之间可以用","分隔
log.dirs=/opt/data/kafka-logs
port=9093
## 配置连接 Zookeeper 集群地址(在 zk 根目录下创建/kafka,方便管理)
zookeeper.connect=server1:2181,server2:2181,server3:2181/kafka
:wq
2.5、Kafka服务启停
## 默认 zookeeper 集群启动成功,查看命令:./zkServer.sh status
## 启动命令
sh ${KAFKA_HOME}/bin/kafka-server-start.sh -daemon ${KAFKA_HOME}/config/server.properties
## 停止命令
sh ${KAFKA_HOME}/bin/kafka-server-stop.sh
2.6、Kafka集群启停脚本
说明:本脚本基于SSH服务器免密登录,如集群未配置SSH,参照:《SSH安装配置》 。
- 启动脚本:start-kafka-cluster.sh
#!/bin/bash
brokers="server1 server2 server3"
KAFKA_HOME="/usr/local/services/kafka/kafka_2.11-2.3.0"
KAFKA_NAME="kafka_2.11-2.3.0"
echo "INFO : Begin to start kafka cluster ..."
for broker in $brokers
do
echo "INFO : Starting ${KAFKA_NAME} on ${broker} ..."
ssh ${broker} -C "source /etc/profile; sh ${KAFKA_HOME}/bin/kafka-server-start.sh -daemon ${KAFKA_HOME}/config/server.properties"
if [[ $? -eq 0 ]]; then
echo "INFO:[${broker}] Start successfully"
fi
done
echo "INFO:Kafka cluster starts successfully !"
为脚本添加执行权限:
chmod a+x start-kafka-cluster.sh
- 停止脚本:stop-kafka-cluster.sh
#!/bin/bash
brokers="server1 server2 server3"
KAFKA_HOME="/usr/local/services/kafka/kafka_2.11-2.3.0"
KAFKA_NAME="kafka_2.11-2.3.0"
echo "INFO : Begin to stop kafka cluster ..."
for broker in $brokers
do
echo "INFO : Shut down ${KAFKA_NAME} on ${broker} ..."
ssh ${broker} "source /etc/profile;bash ${KAFKA_HOME}/bin/kafka-server-stop.sh"
if [[ $? -ne 0 ]]; then
echo "INFO : Shut down ${KAFKA_NAME} on ${broker} is down"
fi
done
echo "INFO : kafka cluster shut down completed!"
为脚本添加执行权限:
chmod a+x stop-kafka-cluster.sh
三、Kafka-Shell API
3.1、主题相关
${KAFKA_HOME}/bin/kafka-topics.sh
## 参数详解
## --bootstrap-server <io:port> 指定连接 kafka-broker 节点
## --topic <topic_name> 指定操作的 主题
## --create 指定对主题的操作:新增
## --delete 指定对主题的操作:删除
## --alter 指定对主题的操作:编辑
## --list 指定对主题的操作:查看列表
## --describe 指定对主题的操作:查看详情
## --partitions <num> 设置主题分区数。
## -- replication-factor <num> 设置分区副本数。
## --config <key-value> 更改系统默认设置
## 案例:查看集群中所有 topic
kafka-topics.sh --bootstrap-server 168.5.7.75:9092 --list
## 案例:创建一个名为 hello 的主题,其分区数为:1,副本数为:3。
kafka-topics.sh --bootstrap-server 168.5.7.75:9092 --create --partitions 1 --replication-factor 3 --topic hello
## 案例:查看主题 hello 详情
kafka-topics.sh --bootstrap-server 168.5.7.75:9092 --describe --topic hello
## 案例:修改主题 hello 分区数(注意:通过alter命令只能增加分区数,不能减少)
kafka-topics.sh --bootstrap-server 168.5.7.75:9092 --alter --topic hello --partitions 3
## 案例:删除主题 hello
kafka-topics.sh --bootstrap-server 168.5.7.75:9092 --delete --topic hello
3.2、生产者相关
${KAFKA_HOME}/bin/kafka-console-producer.sh
## 参数详解
## --bootstrap-server <io:port> 指定连接 kafka-broker 节点
## --topic <topic_name> 指定操作的 主题
## 案例:生产者连接 broker 生产消息
kafka-console-producer.sh --bootstrap-server 168.5.7.75:9092 --topic hello
## 进入命令行后发送消息即可
>
3.3、消费者相关
${KAFKA_HOME}/bin/kafka-console-consumer.sh
## 参数详解
## --bootstrap-server <io:port> 指定连接 kafka-broker 节点
## --topic <topic_name> 指定操作的 主题
## –from-beginning 从头开始消费。
## –group <group_id> 指定消费者组名称。
## 案例:消费者连接 broker 消费消息,默认只消费连接之后生产的新消息
kafka-console-consumer.sh --bootstrap-server 168.5.7.75:9092 --topic hello
## 案例:消费者连接 broker 消费消息,指定从主题中的第一条消息开始消费
kafka-console-consumer.sh --bootstrap-server 168.5.7.75:9092 --from-beginning --topic hello
四、Kafka-Java API
4.1、添加依赖
<dependencies>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
4.2、生产者生产消息
4.2.1、同步发送
public class CustomProducerSync {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1. 给 kafka 配置对象添加配置信息:bootstrap.servers
Properties properties = new Properties();
//服务信息
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"47.106.86.64:9092");
//配置序列化
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 2. 创建 kafka 生产者的配置对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String,String>(properties);
// 3. 创建 kafka 生产者对象
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord("first", "one" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if (e == null) {
System.out.println( "分区 : " + recordMetadata.partition() + " 主题: " + recordMetadata.topic() );
}
}
// .get() 表示同步发送,先把DQueue发送至Broker并ack确认后,在接受新的消息
}).get();
}
kafkaProducer.close();
}
}
4.2.2、普通异步发送
public class CustomProducer {
public static void main(String[] args) {
// 1. 给 kafka 配置对象添加配置信息:bootstrap.servers
Properties properties = new Properties();
//服务信息
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"47.106.86.64:9092");
//配置序列化
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 2. 创建 kafka 生产者的配置对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String,String>(properties);
// 3. 创建 kafka 生产者对象
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord("first", "one" + i));
}
kafkaProducer.close();
}
}
4.2.3、带回调函数的异步发送
public class CustomProducer {
public static void main(String[] args) {
// 1. 给 kafka 配置对象添加配置信息:bootstrap.servers
Properties properties = new Properties();
//服务信息
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"47.106.86.64:9092");
//配置序列化
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 2. 创建 kafka 生产者的配置对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String,String>(properties);
// 3. 创建 kafka 生产者对象
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord("first", "one" + i), new Callback() {
// 回调函数会在 producer 收到 ack 时调用,为异步调用。
// 消息发送失败会自动重试,不需要我们在回调函数中手动重试。
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if (e == null) {
System.out.println( "分区 : " + recordMetadata.partition() + " 主题: " + recordMetadata.topic() );
}
}
});
}
kafkaProducer.close();
}
}
五、Kafka详解
5.1、生产者
5.1.1、生产者 生产消息 流程图
-
主要模块
-
main线程
负责将消息发送至 RecordAccumulator。 -
RecordAccumulator(消息累加器)
消息累加器中的双端队列(DQueue)个数和主题的分区数一一对应; -
sender线程:负责将消息从 RecordAccumulator 发送至 Broker。
sender与Broker通信默认缓存五个请求( max.in.flight.requests.per.connection );
-
-
主要流程
- 生产者调用 send() 方法向 Broker 发送一条消息 msg;
- 消息 msg 被 main线程 处理,主要经过:
-> 过滤器(ProducerInterceptor):
-> 序列化器:
-> 分区器: - 经过 main线程 处理后,消息被存放至 消息累加器(多个双端队列);
- 队列中数据达到 batch.size 或超过 linger.ms 设置的等待时长,触发sender发送;
- sender将DQueue中的数据发送到Broker,并等待Broker的ack,ack超时会触发(retries次)重试;
- Broker返回ack成功后,删除累加器中对应的消息批次将被删除。
5.1.2、拦截器
- 拦截器接口说明
public interface ProducerInterceptor<K, V> extends Configurable {
// 在消息分区前,修改消息内容、消息主题,或将消息放置延时队列等。
ProducerRecord<K, V> onSend(ProducerRecord<K, V> record);
// sender 收到 broker ack 后的回调方法,执行优先级大于 sender 自己的回调。
void onAcknowledgement(RecordMetadata metadata, Exception exception);
// 关闭拦截器时,进行资源释放
void close();
}
- 添加拦截器流程
// 1. 实现 ProducerInterceptor 接口,如:MyProducerInterceptor
// 2. 添加至 kafka 配置对象
Properties properties = new Properties();
properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,MyInterceptor.getClass.getName());
5.1.3、序列化器
5.1.4、分区器
5.1.4.1、分区器作用
5.1.4.2、分区器类别
- DefaultPartitioner - 默认分区器
// 该分区器支持三种分区策略
// 1. 创建 ProducerRecord 时指定分区: partition
public ProducerRecord(String topicName, Integer partition, K key, V value);
// 2. 当创建 ProducerRecord 时指定key,使用 key 的 hash 对分区数取余 作为: partition
public ProducerRecord(String topicName, K key, V value);
// 3. 当创建 ProducerRecord 时, partition 和 key 均未指定,使用随机黏性分区
public ProducerRecord(String topicName, V value);
- RoundRobinPartitioner - 轮询策略
- UniformStickyPartitioner - 随机黏性分区
随机选择一个分区,并尽可能一直使用该分区,直到该 批次数据达到 batch.size 获取 linger.ms 被sender 发送后,再随机使用除此分区的其他分区。
5.1.4.3、自定义分区器
// 1. 实现分区器 Partitioner 接口,编写 partition() 分区逻辑。例:MyPartitioner
// 2. 在生产者的配置中添加分区器参数
Properties properties = new Properties();
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,MyPartitioner.class.getName());
5.1.5、消息累加器(RecordAccumulator)
- 当linger.ms = 0时,sender会在消息累加器接收到一条消息时就将其发送到broker;
- 当linger.ms > 0时,sender会在到达linger.ms时间或者数据量达到batch.size时将消息批量发送到broker。
通过这种批量机制,减少网络IO。提高了生产者的吞吐量。
累加器的存储形式为ConcurrentMap<TopicPartition, Deque<ProducerBatch>>
。
其中一个分区对应一个双端队列,队列中存储的是ProducerBatch
为batch.size
配置,数据量达到batch.size
会创建新的ProducerBatch
,并触发sender
线程进行发送。
通过 buffer.memory
设置消息累加器的缓冲容量(默认32m
)。如果生产者的发送速率大于sender发送的速率,消息就会堆满累加器。生产者就会阻塞或报错。报错取决于重试次数(retries
,默认:int最大值)和重试间隔(retry.backoff.ms
,默认:100ms)。
为例减少高流量时JVM对ProducerBatch的GC回收造成的性能损耗,Kafka引入 BufferPool
内存池来管理 ProducerBatch 的创建和回收。申请一个新的ProducerBatch空间时,调用 free.allocate(size, maxTimeToBlock)
找内存池申请空间。
当单条消息大于batch.size
时,会为其生成一个更大的ProducerBatch
,发送完后由GC回收该内存空间,不再复用内存池。
5.1.6、消息发送线程(Sender)
Sender线程发送数据失败后,会根据retry.backoff.ms
和retries
的配置进行失败重试。最多允许max.in.flight.requests.per.connection
条无ack得消息存在,开启幂等性时,其取值范围为1-5
。
Sender 线程发送消息及接收消息都是基于java NIO的Selector
。
5.1.6、生产者性能调优
5.1.7、生产者性能调优
// 1. 调整批次大小 batch.size。默认 16K,可设置为 32K。
properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
// 2. 调整批次等待时间 linger.ms。默认 0 不等待,batch.size 参数将不生效,可设置为:5-100ms
properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);
// 3.调整缓冲区RecordAccumulator大小 buffer.memory。默认 32M,可设置为 64M。
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 67554432);
// 4.开启消息压缩 compression.type 。默认 none,可配置值 gzip、snappy、lz4 和 zstd
properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
// 以上为调优的方向,具体的参数设置需要结合实际的业务场景、消息种类进行压测来设置,才能达到好的效果。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架