kafka学习笔记

kafka学习笔记

    • kafka介绍
    • 传统消息队列的应用场景
    • 消息队列的两种模式
    • kafka基础架构
    • Kafka 快速入门
      • 安装部署
    • Kafka 命令行操作
    • 生产者命令行操作
    • 消费者命令行操作
    • Kafka 生产者
      • 生产者消息发送流程
    • 异步发送 API
      • 普通异步发送
      • 带回调函数的异步发送
      • 同步发送 API
    • 生产者分区
    • 自定义分区器
    • 生产经验——生产者如何提高吞吐量
    • 生产经验——数据可靠性
    • 生产经验——数据去重
      • 数据传递语义
      • 幂等性
      • 生产者事务
    • 生产经验——数据有序
    • 生产经验——数据乱序
    • Kafka Broker 工作流程
      • Zookeeper 存储的 Kafka 信息
      • Kafka Broker 总体工作流程
      • Broker 重要参数
    • 生产经验——节点服役和退役
      • 服役新节点
      • 退役旧节点
    • Kafka 副本
      • 副本基本信息
      • Leader 选举流程
      • Leader 和 Follower 故障处理细节
      • 分区副本分配
    • 生产经验——手动调整分区副本存储
    • 生产经验——Leader Partition 负载平衡
    • 生产经验——增加副本因子
    • 文件存储
      • 文件存储机制
      • 文件清理策略
    • 高效读写数据
    • Kafka 消费者
      • Kafka 消费方式
      • 消费者总体工作流程
      • 消费者组原理
      • 消费者组初始化流程
      • 消费者组详细消费流程
      • 消费者重要参数
    • 消费者 API
      • 独立消费者案例(订阅主题)
      • 独立消费者案例(订阅分区)
      • 消费者组案例
    • 生产经验——分区的分配以及再平衡
      • Range 以及再平衡
      • RoundRobin 以及再平衡
      • Sticky 以及再平衡
    • offset 位移
      • offset 的默认维护位置
      • 自动提交 offset
      • 手动提交 offset
      • 指定 Offset 消费
      • 指定时间消费
    • 漏消费和重复消费
    • 生产经验——消费者事务
    • 生产经验——数据积压(消费者如何提高吞吐量)
    • Kafka-Eagle 监控
      • MySQL 环境准备
        • MySQL安装
      • Kafka 环境准备
    • Kafka-Eagle 安装
      • Kafka-Eagle 页面操作
    • Kafka-Kraft 模式
      • Kafka-Kraft 架构
      • Kafka-Kraft 集群部署
      • Kafka-Kraft 集群启动停止脚本
    • 集成Flume
      • Flume生产者
      • Flume消费者
    • 集成Flink
    • 集成SpringBoot
    • 集成Spark
    • Kafka硬件配置选择
      • 场景说明
      • 服务器台数选择
      • 磁盘选择
      • 内存选择
      • CPU选择
      • 网络选择
    • kafka生产者
    • Kafka生产者核心参数配置
    • 生产者如何提高吞吐量
    • 数据可靠性
    • 数据去重
    • 数据有序
    • 数据乱序
    • Kafka Broker
    • Leader Partition负载平衡
    • 自动创建主题
    • Kafka消费者
      • Kafka消费者核心参数配置
    • 消费者再平衡
    • 消费者如何提高吞吐量
    • Kafka总体
      • 如何提升吞吐量
      • 数据精准一次
      • 合理设置分区数
      • 单条日志大于1m
      • 服务器挂了
    • 集群压力测试
      • Kafka压测
      • Kafka Producer压力测试
      • Kafka Consumer压力测试

基于尚硅谷做的笔记…
另外也参考了几篇博客…

kafka介绍

Kafka是最初由Linkedin公司开发,是⼀个分布式⽀持分区的(partition)、多副本的(replica),基于zookeeper协调分布式消息系统,它的最⼤的特性就是可以实时的处理⼤量数据以满⾜各种需求场景:⽐如基于hadoop的批处理系统、低延迟的实时系统、Storm/Spark流式处理引擎,web/nginx⽇志、访问⽇志,消息服务等等,⽤scala语⾔编写,Linkedin于2010年贡献给了Apache基⾦会并成为顶级开源 项⽬。

Kafka传统定义:Kafka是一个分布式的基于发布/订阅模式的消息队列(MessageQueue),主要应用于大数据实时处理领域.
发布/订阅:消息的发布者不会将消息直接发送给特定的订阅者,而是将发布的消息分为不同的类别,订阅者只接收感兴趣的消息
Kafka最新定义 : Kafka是 一个开源的分布式事件流平台 (Event Streaming Platform),被数千家公司用于高性能数据管道、流分析、数据集成和关键任务应用。

传统消息队列的应用场景

传统的消息队列的主要应用场景包括:缓存/消峰、解耦和异步通信.

缓冲/消峰:有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。
解耦:允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
kafka学习笔记_第1张图片
异步通信:允许用户把一个消息放入队列,但并不立即处理它,然后在需要的时候再去处理它们。
kafka学习笔记_第2张图片

消息队列的两种模式

kafka学习笔记_第3张图片

  1. 点对点模式:
    a.只有一个topic主题的数据
    b.消费完数据之后就删除数据
    c.只有一个消费者
  2. 发布/订阅模式:
    a.可以有多个topic主题
    b.消费完数据之后不删除数据
    c.可以有多个消费者

kafka基础架构

kafka学习笔记_第4张图片
(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周

Kafka 快速入门

安装部署

  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  #修改此处

5)分发安装包
在这里插入图片描述
xsync脚本的内容:

#!/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 进程了。

Kafka 命令行操作

kafka学习笔记_第5张图片
1)启动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

Kafka 生产者

生产者消息发送流程

1.发送原理
在消息发送的过程中,涉及到了两个线程——main 线程和 Sender 线程。在 main 线程中创建了一个双端队列 RecordAccumulator。main 线程将消息发送给 RecordAccumulator,Sender 线程不断从 RecordAccumulator 中拉取消息发送到 Kafka Broker。
2.发送流程
kafka学习笔记_第6张图片
• producer通过⽹络发送消息到Kafka集群,然后consumer来进⾏消费,服务端(brokers)和客户端(producer、consumer)之间通信通过TCP协议来完成。所以需要Serializer序列化器.
• Partitioner分区器决定着数据发往哪个分区.
当Partitioner分区器向双端队列 RecordAccumulator发送数据时,会创建批次大小,从内存池中取出内存,当这些数据发送到kafka集群之后,就会把内存释放到内存池当中.

  1. Sender线程:
    异步从RecordAccumulator(消息累加器)中获取缓存的消息,然后将其转为指定格式的ProducerRequest对象,将ProducerRequest对象请求保存到InFlightRequests中,最后将请求发往各个broker。InFlightRequests的作用是缓存已经发送出去但还没有收到服务端响应的请求。
    InFlightRequests相关的配置参数:
    max.in.flight.requests.per.connection参数表示每个连接(客户端与broker 节点之间的网络连接)的最多缓存请求数,默认值为5个。当超过该数值之后,客户端便不能再向这个连接发送更多的请求了。另外也得注意,当该参数配置大于1时,由于因为失败重试原因,可能会存在消息乱序的风险。
    参考链接: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

异步发送 API

普通异步发送

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,说明消息发送失败.
kafka学习笔记_第7张图片

注意:消息发送失败会自动重试,不需要我们在回调函数中手动重试。
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 控制台观察回调信息

同步发送 API

kafka学习笔记_第8张图片
• replica.lag.time.max.ms:如果一个follower在这时间内没有发送任何通信请求,或者至少在这个时间内没有消耗完leader日志结束偏移量(同步完所有数据),leader将从isr中移除follower.
• 每个partition(分区)之中的每条消息都会被赋予一个叫做offset(偏移量)的顺序id编号,用于标识它在当前分区日志中的位置。
request.timeout.ms:配置控制客户端等待请求响应的最大时间。如果在超时之前没有收到响应,则客户端将在必要时重新发送请求,或在重试耗尽时使请求失败。这个值应该大于replica.lag.time.max.ms(broker配置),以减少由于不必要的生产者重试而导致的消息重复的可能性。

  1. 同步发送:一定是逐条发送的,第一条响应到达后,才会发送第二条(发送一条消息,所有的后续工作完成以后才能继续下一条消息的发送).
    如果需要使用同步发送,可以在每次发送之后使用get方法,因为producer.send方法返回一个Future类型的结果,Future的get方法会一直阻塞直到该线程的任务得到返回值,也就是broker返回发送成功。
    因为batch.size默认是16k,如果一条消息的大小<16k,那么此时就会等linger.ms(默认0ms,无延迟)设置的时间到sender才会发送消息.

  2. 异步发送:可以发送一条,也可以批量发送多条,特性是不需等第一次(注意这里单位是次,因为单次可以是单条,也可以是批量数据)响应,就立即发送第二次消息.

当用户调用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

生产者分区

  1. 分区好处
    kafka学习笔记_第9张图片
    2.生产者发送消息的分区策略
    kafka学习笔记_第10张图片
    3.案例一
    将数据发往指定 partition 的情况下,例如,将所有数据发往分区 1 中。
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个参数:

  1. batch.size:批次大小,默认16k (也就是一个batch满了16kb就发送出去).如果 batch 太小,会导致频繁网络请求,吞吐量下降;
    如果batch太大,会导致一条消息需要等待很久才能被发送出去,而且会让内存缓冲区有很大压力,过多数据缓冲在内存里.一般在实际生产环境,这个batch的值可以增大一些来提升吞吐量。
  2. linger.ms:等待时间,默认值是0ms(意思就是消息立即被发送,不延迟,来一条发送一条),但是这是不对的。可以将其修改为5-100ms.假如linger.ms设置为为50ms,消息被发送出去后会进入一个batch,如果50ms内,这个batch满了16kb就会被发送出去。但是如果50ms时间到,batch没满,那么也必须把消息发送出去了,不能让消息的发送延迟时间太长,也避免给内存造成过大的一个压力。
  3. compression.type:默认是none,不压缩,可以使用lz4,snappy等压缩,压缩之后可以减小数据量,提升吞吐量,但是会加大producer端的cpu开销。
  4. 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 应答原理
kafka学习笔记_第11张图片
思考: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)):

  1. 分区副本设置为1个
  2. ISR里应答的最小副本数量(min.insync.replicas,默认为1)设置为1.设置acks=all,且副本数为3,极端情况下,如果ISR中只有leader一个副本时,此时producer发送的数据只要leader同步成功就会返回响应.

因此:
数据完全可靠条件 = ACK级别设置为-1 + 分区副本大于等于2 + ISR里应答的最小副本数量大于等于2

可靠性总结:

  1. acks=0,生产者发送过来数据就不管了,可靠性差,效率高;
  2. acks=1,生产者发送过来数据Leader应答,可靠性中等,效率中等;
  3. acks=-1,生产者发送过来数据Leader和ISR队列里面所有Follwer应答,可靠性高,效率低;
  4. 在生产环境中,acks=0很少使用;acks=1,一般用于传输普通日志,允许丢个别数据;acks=-1,一般用于传输和钱相关的数据,对可靠性要求比较高的场景。
    kafka学习笔记_第12张图片
    代码编写:
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();
    }
}

生产经验——数据去重

数据传递语义

数据传递语义:

  1. 至少一次(At Least Once)= ACK级别设置为-1 + 分区副本大于等于2 + ISR里应答的最小副本数量大于等于2
  2. 最多一次(At Most Once)= ACK级别设置为0
  3. 总结:
    • At Least Once可以保证数据不丢失,但是不能保证数据不重复;
    • At Most Once可以保证数据不重复,但是不能保证数据不丢失。
    • 精确一次(Exactly Once):对于一些非常重要的信息,比如和钱相关的数据,要求数据既不能重复也不丢失。Kafka 0.11版本以后,引入了一项重大特性:幂等性和事务。

幂等性

幂等性原理:

  1. 幂等性就是指Producer不论向Broker发送多少次重复数据,Broker端都只会持久化一条,保证了不重复。
  2. 精确一次(Exactly Once) = 幂等性 + 至少一次( ack=-1 + 分区副本数>=2 + ISR最小副本数量>=2) 。
  3. 重复数据的判断标准:具有相同主键的消息提交时,Broker只会持久化一条。
    • PID是Kafka每次重启都会分配一个新的(每个 Producer 在初始化时,都会分配一个唯一的 PID,这个 PID 对用户来说是不可见的)
    • Partition 表示分区号
    • Sequence Number是单调自增的,针对每个生产者(对应 PID )发送到指定主题分区的消息都对应一个从 0 开始递增的 Sequence Number.

所以幂等性只能保证的是在单分区单会话内不重复

如何使用幂等性:
开启参数 enable.idempotence 默认为 true,false 表示关闭。

生产者事务

1)Kafka 事务原理
kafka学习笔记_第13张图片
● 生产者发送多条消息可以封装在一个事务中,形成一个原子操作。多条消息要么都发送成功,要么都发送失败。

●不同的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,分别表示事务已经成功提交或者被成功中止。
流程:

  1. Producer 会向 Broker (随机选择一台 broker,一般选择本地连接最少的broker)发送FindCoordinatorRequest请求(根据事务id去broker端寻找这个id对应的事务协调器),FindCoordinatorRequest中的coordinator_type由原来的0变成1,由此来表示与事务相关联。Kafka 在收到 FindCoorinatorRequest 请求之后,会根据 coordinator_key (也就是transactionalId)查找对应的TransactionCoordinator节点。如果找到,则会返回其相对应的node_id、host和port信息.
  2. Producer向TransactionCoordinator发送InitProducerIdRequest请求(请求获取PID),注意:如果未开启事务特性而只开启幂等性,那么会随机选择一台 broker 发送InitProducerIdRequest请求.
  3. 1)TransactionCoordinator为当前 Producer 分配一个 PID并返回给Producer。分两种情况:
     ●不带transactionID(幂等性)
      直接生成一个新的PID,返回给Producer
     ●带transactionID(事务)
      这种情况下,TransactionCoordinator根据transactionalId获取对应的PID,这个对应关系是保存在事务日志中。这样可以确保相同的TransactionId返回相同的PID,用于恢复或者终止之前未完成的事务。当TransactionCoordinator第一次收到包含该transactionalId的InitProducerIdRequest请求时,它会把transactionalId和对应的PID以消息(“事务日志消息”)的形式保存到主题__transaction_state中.
    2)除了返回PID,InitProducerIdRequest还会触发执行以下任务
     ●增加该 PID 对应的 producer_epoch。
     ●恢复(Commit)或中止(Abort)之前的生产者未完成的事务。
  4. 开启事务
    通过KafkaProducer的beginTransaction()方法可以开启一个事务,调用该方法后,生产者本地会标记已经开启了一个新的事务,只有在生产者发送第一条消息之后 TransactionCoordinator才会认为该事务已经开启。
  5. read-process-write流程
    一旦Producer开始发送消息,TransactionCoordinator会将该存于Transaction Log内,并将其状态置为BEGIN。另外,如果该为该事务中第一个,Transaction Coordinator还会启动对该事务的计时(每个事务都有自己的超时时间)。
    在注册到Transaction Log后,生产者发送数据,虽然没有还没有执行commit或者abort,但是此时消息已经保存到Broker上了。即使后面执行abort,消息也不会删除,只是更改状态字段标识消息为abort状态。
  6. 事务提交或终结 commitTransaction/abortTransaction
    在Producer执行commitTransaction/abortTransaction时,Transaction Coordinator会执行①②提交:
    ①将Transaction Log内的该事务状态设置为PREPARE_COMMIT或PREPARE_ABORT
    ②将Transaction Marker写入该事务涉及到的所有消息(即将消息标记为committed或aborted)。这一步骤Transaction Coordinator会发送给当前事务涉及到的每个的Leader发送WriteTxnMarkersRequest请求,Broker收到该请求后,会将对应的Transaction Marker控制信息写入日志文件。它和普通消息一样存储在日志文件中。
    一旦Transaction Marker写入完成,Transaction Coordinator会将最终的COMPLETE_COMMIT或COMPLETE_ABORT状态写入Transaction Log中以标明该事务结束。
    参考链接:Kafka事务特性详解;kafka之事务

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();
        }
    }
}

生产经验——数据有序

单分区内,有序;
多分区,分区与分区间无序;

生产经验——数据乱序

  1. kafka在1.x版本之前保证数据单分区有序,条件如下:
    max.in.flight.requests.per.connection=1(不需要考虑是否开启幂等性):在阻塞之前,客户端将在单个连接上发送的未确认请求的最大数量。
  2. kafka在1.x及以后版本保证数据单分区有序,条件如下:
    (1)未开启幂等性
    max.in.flight.requests.per.connection需要设置为1。
    (2)开启幂等性
    max.in.flight.requests.per.connection需要设置小于等于5。
    原因说明:因为在kafka1.x以后,启用幂等性后,kafka服务端会缓存producer发来的最近5个request的元数据
    故无论如何,都可以保证最近5个request的数据都是有序的。
    kafka学习笔记_第14张图片
    假设Request1,Request2,Request3,Request4,Request5的SeqNumber分别为1,2,3,4,5。此时Request1和Request2发送到kafka集群会先落盘,若是Request3发送失败,需要重试,在重试的这段时间,Request4和Request5(或后来的Request6和Request7)先被发送到kafka集群,因为Request4和Request5的SeqNumber不是3,所以Request4和Request5不会被落盘,它们会被缓存起来;等到Request3被发送到kafka集群的时候,会现在内存里先排好序之后才会落盘。

Kafka Broker 工作流程

Zookeeper 存储的 Kafka 信息

(1)启动 Zookeeper 客户端。

[hyj@hadoop102 zookeeper-3.5.7]$ bin/zkCli.sh 

(2)通过 ls 命令可以查看 kafka 相关信息。

[zk: localhost:2181(CONNECTED) 0] ls /kafka

kafka学习笔记_第15张图片

  1. /admin:
    主要保存kafka当中的核心的重要信息,包括类似于已经删除的topic就会保存在这个路径下面
  2. /brokers:
    主要用于保存kafka集群当中的broker信息,以及没被删除的topic信息
  3. /cluster:
    主要用于保存kafka集群的唯一id信息,每个kafka集群都会分配给唯一id,以及对应的版本号
  4. /config:
    集群配置信息
  5. /controller:
    kafka集群当中的控制器信息.(控制器组件(Controller),是 Apache Kafka 的核心组件。它的主要作用是在 Apache ZooKeeper 的帮助下管理和协调整个 Kafka 集群。)
  6. /controller_epoch:
    主要用于保存记录controller的选举的次数
  7. /isr_change_notification:
    isr列表发生变更时候的通知,在kafka当中由于存在ISR列表变更的情况发生,为了保证ISR列表更新的及时性,定义了isr_change_notification 这个节点,主要用于通知Controller来及时将ISR列表进行变更
  8. /latest_producer_id_block:
    使用/latest_producer_id_block节点来保存PID块,主要用于能够保证生产者的任意写入请求都能够得到响应。
    发现了一篇写得很好的博客:Producer PID 申请, PID 段
  9. /log_dir_event_notification:
    主要用于保存当broker当中某些LogDir出现异常时候,例如磁盘损坏,文件读写失败等异常的时候,向ZK当中增加一个通知序号,controller监听到这个节点的变化之后,就会做出对应的处理操作。

参考链接:kafka的内置共识机制与raft算法

Kafka Broker 总体工作流程

kafka学习笔记_第16张图片
不懂AR和ISR的区别可以看这篇:AR和ISR

  1. broker启动后在zk中注册
    kafka的每个broker在启动时,都会向Zookeeper发送注册请求,Zookeeper会在/kafka/brokers/ids下创建这个broker节点,如/brokers/ids/[0…N],并保存broker的IP地址和端口。
    这个节点是临时节点,一旦broker宕机,这个临时节点就会被自动删除。
  2. controller谁先注册,谁说了算
    ● kafka 集群中有1个或多个 broker,但只有一个会被选举为控制器(Controller)。
    ● broker 在启动时,会尝试去 ZooKeeper 中创建 /controller 临时节点。
    ● Kafka 当前选举控制器(Controller)的规则是:第一个成功创建 /controller 临时节点的 broker 会被指定为控制器(Controller)。
    ● 控制器(Controller)负责管理整个集群中所有分区和副本的状态。
    ● 当某个分区的leader副本出现故障时,由控制器负责为该分区选举新的leader副本.
    ● 集群Broker是不会与Zookeeper直接交互去获取元数据的。相反,它们总是与Controller进行通信,获取和更新最新的集群数据。
    ● 当检测到某个分区的ISR集合发生变化时,由控制器负责通知所有broker更新其元数据信息.
    ● 当使用kafka-topics.sh脚本为某个topic增加分区数量时,同样还是由控制器负责分区的重新分配。
    ● Controller Broker (KafkaController ) 是一个 Kafka 服务,它运行在 Kafka 集群中的每个 Broker 上,但在任何时间点只有一个可以处于活动状态(选举)。
    ● 每个broker都会在内存中保存当前控制器的brokerid值,这个值可以标识为activeControllerId。
    ● 控制器在选举成功之后会读取Zookeeper中各个节点的数据来初始化上下文信息(ControllerContext),并且也需要管理这些上下文信息,比如为某个topic增加了若干个分区,控制器在负责创建这些分区的同时也要更新上下文信息,并且也需要将这些变更信息同步到其他普通的broker节点中。

参考链接(必看好文):《直击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"}

Broker 重要参数

kafka学习笔记_第17张图片
kafka学习笔记_第18张图片

生产经验——节点服役和退役

服役新节点

(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 副本

副本基本信息

  1. Kafka 副本作用:提高数据可靠性。
  2. Kafka 默认副本 1 个,生产环境一般配置为 2 个,保证数据可靠性;太多副本会增加磁盘存储空间,增加网络上数据传输,降低效率。
  3. Kafka 中副本分为:Leader 和 Follower。Kafka 生产者只会把数据发往 Leader,然后 Follower 找 Leader 进行同步数据。
  4. Kafka 分区中的所有副本统称为 AR(Assigned Repllicas)。
    AR = ISR + OSR
    ISR,表示和 Leader 保持同步的 Follower 集合。如果 Follower 长时间未向 Leader 发送通信请求或同步数据,则该 Follower 将被踢出 ISR,进入OSR。该时间阈值由 replica.lag.time.max.ms参数设定,默认值为30s。Leader 发生故障之后,就会从 ISR 中选举新的 Leader。
    OSR,表示 Follower 与 Leader 副本同步时,延迟过多的副本.
    在这里插入图片描述

Leader 选举流程

Kafka 集群中有一个 broker 的 Controller 会被选举为 Controller Leader,负责管理集群broker 的上下线,所有 topic 的分区副本分配和 Leader 选举等工作。
Controller 的信息同步工作是依赖于 Zookeeper 的。
kafka学习笔记_第19张图片
(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

Leader 和 Follower 故障处理细节

LEO(Log End Offset):每个副本的最后一个offset,LEO其实就是最新的offset + 1
HW(High Watermark):所有副本中最小的LEO
kafka学习笔记_第20张图片

  1. 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了。
    kafka学习笔记_第21张图片

  2. 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

kafka学习笔记_第22张图片
(3)创建一个新的 topic,名称为 four。

[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

kafka学习笔记_第23张图片

生产经验——手动调整分区副本存储

需求:创建一个新的topic,4个分区,两个副本,名称为three。将 该topic的所有副本都存储到broker0和broker1两台服务器上。
kafka学习笔记_第24张图片

手动调整分区副本存储的步骤如下:
(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

生产经验——Leader Partition 负载平衡

正常情况下,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命名。
kafka学习笔记_第25张图片
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学习笔记_第26张图片
说明:日志存储参数配置
kafka学习笔记_第27张图片

文件清理策略

Kafka 中默认的日志保存时间为 7 天,可以通过调整如下参数修改保存时间。

  1. log.retention.hours,最低优先级小时,默认 7 天。
  2. log.retention.minutes,分钟。
  3. log.retention.ms,最高优先级毫秒。
  4. log.retention.check.interval.ms,负责设置检查周期,默认 5 分钟。

那么日志一旦超过了设置的时间,怎么处理呢?
Kafka 中提供的日志清理策略有 delete 和 compact 两种。

  1. delete 日志删除:将过期数据删除
    log.cleanup.policy = delete 所有数据启用删除策略
    (1)基于时间:默认打开。以 segment 中所有记录中的最大时间戳作为该文件时间戳
    (2)基于大小:默认关闭。超过设置的所有日志总大小,删除最早的 segment。log.retention.bytes,默认等于-1,表示无穷大。
    思考:如果一个 segment 中有一部分数据过期,一部分没有过期,怎么处理?看(1)
    kafka学习笔记_第28张图片
  2. compact 日志压缩
    compact日志压缩:对于相同key的不同value值,只保留最后一个版本。
    log.cleanup.policy = compact 所有数据启用压缩策略
    kafka学习笔记_第29张图片
    压缩后的offset可能是不连续的,比如上图中没有6,当从这些offset消费消息时,将会拿到比这个offset大 的offset对应的消息,实际上会拿到offset为7的消息,并从这个位置开始消费。
    这种策略只适合特殊场景,比如消息的key是用户ID,value是用户的资料,通过这种压缩策略,整个消息集里就保存了所有用户最新的资料。

高效读写数据

  1. Kafka 本身是分布式集群,可以采用分区技术,并行度高
  2. 读数据采用稀疏索引,可以快速定位要消费的数据
  3. 顺序写磁盘
    Kafka 的 producer 生产数据,要写入到 log 文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到 600M/s,而随机写只有 100K/s。这与磁盘的结构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间。
  4. 页缓存 + 零拷贝技术
    零拷贝:Kafka的数据加工处理操作交由Kafka生产者和Kafka消费者处理。Kafka Broker应用层不关心存储的数据,所以就不用走应用层,传输效率高。
    PageCache页缓存:Kafka重度依赖底层操作系统提供的PageCache功 能。当上层有写操作时,操作系统只是将数据写入PageCache当读操作发生时,先从PageCache中查找,如果找不到,就会从磁盘读取文件写入 Page Cache 再读取。实际上PageCache是把尽可能多的空闲内存都当做了磁盘缓存来使用。
    如果生产者与消费者的速度相差不大,消费者会直接读取之前生产者写入PageCache的数据,大家在内存里完成接力,根本没有磁盘访问。
    即使 Kafka 重启了,PageCache 还依然在(因为页缓存不需要GC).kafka学习笔记_第30张图片
    kafka学习笔记_第31张图片

Kafka 消费者

Kafka 消费方式

  1. pull(拉)模 式:
    consumer采用从broker中主动拉取数据。Kafka采用这种方式。
  2. push(推)模式:
    Kafka没有采用这种方式,因为由broker决定消息发送速率,很难适应所有消费者的消费速率。例如推送的速度是50m/s,Consumer1、Consumer2就来不及处理消息。
    kafka学习笔记_第32张图片

pull模式不足之处是,如 果Kafka没有数据,消费者可能会陷入循环中,一直返回空数据。

消费者总体工作流程

kafka学习笔记_第33张图片

消费者组原理

Consumer Group(CG):消费者组,由多个consumer组成。形成一个消费者组的条件,是所有消费者的groupid相同。
• 消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费。
• 消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
• 如果向消费组中添加更多的消费者,超过主题分区数量,则有一部分消费者就会闲置,不会接收任何消息。

消费者组初始化流程

kafka学习笔记_第34张图片

消费者组详细消费流程

kafka学习笔记_第35张图片

消费者重要参数

kafka学习笔记_第36张图片
kafka学习笔记_第37张图片

消费者 API

独立消费者案例(订阅主题)

1)需求:
创建一个独立消费者,消费 first 主题中数据。
kafka学习笔记_第38张图片
注意:在消费者 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 号分区的数据
kafka学习笔记_第39张图片
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)需求:测试同一个主题的分区数据,只能由一个消费者组中的一个消费.
kafka学习笔记_第40张图片
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,可以看到只能有一个消费者消费到数据。

生产经验——分区的分配以及再平衡

  1. 一个consumer group中有多个consumer组成,一个 topic有多个partition组成,现在的问题是,到底由哪个consumer来消费哪个partition的数据。
  2. Kafka有四种主流的分区分配策略: Range、RoundRobin、Sticky、CooperativeSticky。
    可以通过配置参数partition.assignment.strategy,修改分区的分配策略。默认策略是Range + CooperativeSticky。Kafka可以同时使用多个分区分配策略。
    在这里插入图片描述
    在这里插入图片描述
    3)将分区的所有权从一个消费者移到另一个消费者称为再平衡.如何rebalance也涉及到分区分配策略.

Range 以及再平衡

1)Range 分区策略原理
kafka学习笔记_第41张图片

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. 停止掉 0 号消费者,快速重新发送消息观看结果(45s 以内,越快越好)。
    1 号消费者:消费到 3、4 号分区数据。
    2 号消费者:消费到 5、6 号分区数据。
    0 号消费者的任务会整体被分配到 1 号消费者或者 2 号消费者。
    说明:0 号消费者挂掉后,消费者组需要按照超时时间 45s 来判断它是否退出,所以需要等待,时间到了 45s 后,判断它真的退出就会把任务分配给其他 broker 执行。
  2. 再次重新发送消息观看结果(45s 以后)。
    1 号消费者:消费到 0、1、2、3 号分区数据。
    2 号消费者:消费到 4、5、6 号分区数据。
    说明:消费者 0 已经被踢出消费者组,所以重新按照 range 方式分配。

RoundRobin 以及再平衡

1)RoundRobin 分区策略原理
RoundRobin 针对集群中所有Topic而言。
RoundRobin 轮询分区策略,是把所有的 partition 和所有的consumer 都列出来,然后按照 hashcode 进行排序,最后通过轮询算法来分配 partition 给到各个消费者。
kafka学习笔记_第42张图片
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 方式分配。

Sticky 以及再平衡

粘性分区定义:可以理解为分配的结果带有“粘性的”。即在执行一次新的分配之前,考虑上一次分配的结果,尽量少的调整分配的变动,可以节省大量的开销。
粘性分区是 Kafka 从 0.11.x 版本开始引入这种分配策略,首先会尽量均衡的放置分区到消费者上面,在出现同一消费者组内消费者出现问题的时候,会尽量保持原有分配的分区不变化。
Sticky分配策略的原理比较复杂,它的设计主要实现了两个目的:

  1. 分区的分配要尽可能的均匀(又随机);
  2. 分区的分配尽可能的与上次分配的保持相同。

如果这两个目的发生了冲突,优先实现第一个目的。

粘性分区分配 : 假如现在有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的区别:
kafka学习笔记_第43张图片

kafka学习笔记_第44张图片

offset 位移

offset 的默认维护位置

kafka学习笔记_第45张图片
__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)

自动提交 offset

为了使我们能够专注于自己的业务逻辑,Kafka提供了自动提交offset的功能。
自动提交offset的相关参数:

  1. enable.auto.commit:是否开启自动提交offset功能,默认是true.消费者会自动周期性地向服务器提交偏移量。
  2. auto.commit.interval.ms:自动提交offset的时间间隔,默认是5s.如果设置了 enable.auto.commit 的值为 true, 则该值定义了消费者偏移量向 Kafka 提交的频率,默认 5s。
    kafka学习笔记_第46张图片
    1)消费者自动提交 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十分简单便利,但由于其是基于时间提交的,开发人员难以把握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();
        }
    }
}

指定 Offset 消费

auto.offset.reset = earliest | latest | none ,默认是 latest。
当 Kafka 中没有初始偏移量(消费者组第一次消费)或服务器上不再存在当前偏移量时(例如该数据已被删除),该怎么办?
(1)earliest:自动将偏移量重置为最早的偏移量,--from-beginning
(2)latest(默认值):自动将偏移量重置为最新偏移量。
(3)none:如果未找到消费者组的先前偏移量,则向消费者抛出异常。
kafka学习笔记_第47张图片
(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 后消费,有可能会造成数据的漏消费

(1)场景1:重复消费。自动提交offset引起。
kafka学习笔记_第48张图片

重复消费的例子:

       //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已经提交,但是数据未处理,导致这部分内存中的数据丢失.
kafka学习笔记_第49张图片

漏消费的例子:

        //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)。这部分知识会在后续项目部分涉及。
kafka学习笔记_第50张图片

生产经验——数据积压(消费者如何提高吞吐量)

1)如果是Kafka消费能力不足,则可以考虑增 加Topic的分区数,并且同时提升消费组的消费者数量,消费者数 = 分区数。(两者缺一不可)
2)如果是下游的数据处理不及时:提高每批次拉取的数量。批次拉取数据过少(拉取数据/处理时间 < 生产速度),使处理的数据小于生产的数据,也会造成数据积压。
kafka学习笔记_第51张图片
这两个参数是配套使用的:
kafka学习笔记_第52张图片
单次 poll 最大等待时间,consumer.poll(Duration.ofSeconds(1)) 意味着在缓存中的数据被消费完之后,consumer 会到 broker 中拉取数据,最多等待 1秒。timeout 越大,单次 poll 返回的数据就越多。但前提是有足够多的数据可供消费,如果在 1秒内,consumer 已经拿完所有能拿的数据,就会立即返回。

Kafka-Eagle 监控

Kafka-Eagle 框架可以监控 Kafka 集群的整体运行情况,在生产环境中经常使用。

MySQL 环境准备

Kafka-Eagle 的安装依赖于 MySQL,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;

Kafka 环境准备

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

Kafka-Eagle 安装

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 
  1. 因为我们只需要监控一个集群,所以删掉一个集群(cluster2)
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节点
  1. 往下走:
cluster1.efak.offset.storage=kafka
cluster2.efak.offset.storage=zk

将其修改为:

# offset 保存在 kafka
cluster1.efak.offset.storage=kafka
  1. 配置数据存储在MySQL数据库里
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

Kafka-Eagle 页面操作

1)登录页面查看监控数据
http://192.168.10.102:8048/
kafka学习笔记_第53张图片

Kafka-Kraft 模式

Kafka-Kraft 架构

kafka学习笔记_第54张图片
左图为 Kafka 现有架构,元数据在 zookeeper 中,运行时动态选举 controller,由controller 进行 Kafka 集群管理。
右图为 kraft 模式架构(实验性),不再依赖 zookeeper 集群,而是用三台 controller 节点代替 zookeeper,元数据保存在 controller 中,由 controller 直接进行 Kafka 集群管理。

这样做的好处有以下几个:

  1. Kafka 不再依赖外部框架,而是能够独立运行;
  2. controller 管理集群时,不再需要从 zookeeper 中先读取数据,集群性能上升;
  3. 由于不依赖 zookeeper,集群扩展时不再受到 zookeeper 读写能力限制;
  4. controller 不再动态选举,而是由配置文件规定。这样我们可以有针对性的加强controller 节点的配置,而不是像以前一样对随机 controller 节点的高负载束手无策。

Kafka-Kraft 集群部署

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/
  1. 在 hadoop103 和 hadoop104 上 需 要 对 node.id 做相应改变 , 值 需 要 和controller.quorum.voters 对应。
  2. 在 hadoop103 和 hadoop104 上需要 根据各自的主机名称,修改相应的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

Kafka-Kraft 集群启动停止脚本

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

Flume是一个在大数据开发中非常常用的组件。可以用于Kafka的生产者,也可以用于Kafka的消费者。
kafka学习笔记_第55张图片

Flume生产者

kafka学习笔记_第56张图片
(1)启动zookeeper和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数据

Flume消费者

kafka学习笔记_第57张图片
(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)观察控制台输出的日志

集成Flink

正在更新中…

集成SpringBoot

正在更新中…

集成Spark

正在更新中…

Kafka硬件配置选择

场景说明

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内部配置)+ 页缓存 (服务器内存)

  1. Kafka堆内存建议每个节点:10g ~ 15g
    在kafka-server-start.sh中修改
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)。

CPU选择

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)。

kafka生产者

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的值以用于测试。

Kafka生产者核心参数配置

kafka学习笔记_第58张图片

kafka学习笔记_第59张图片
kafka学习笔记_第60张图片
在这里插入图片描述

生产者如何提高吞吐量

kafka学习笔记_第61张图片

数据可靠性

kafka学习笔记_第62张图片
至少一次(At Least Once)= ACK级别设置为-1 + 分区副本大于等于2 + ISR里应答的最小副本数量大于等于2

数据去重

1)配置参数
在这里插入图片描述
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; 

数据有序

单分区内,有序(有条件的,不能乱序);多分区,分区与分区间无序;

数据乱序

kafka学习笔记_第63张图片

Kafka Broker

Kafka Broker总体工作流程:
kafka学习笔记_第64张图片
kafka学习笔记_第65张图片
kafka学习笔记_第66张图片

Leader Partition负载平衡

kafka学习笔记_第67张图片

自动创建主题

如果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 

Kafka消费者

Kafka消费者核心参数配置

消费者组初始化流程:
kafka学习笔记_第68张图片

kafka学习笔记_第69张图片
在这里插入图片描述
kafka学习笔记_第70张图片
kafka学习笔记_第71张图片

消费者再平衡

kafka学习笔记_第72张图片

消费者如何提高吞吐量

增加分区数:

[hyj@hadoop102 kafka-3.0.0]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --alter --topic first --partitions 3 

kafka学习笔记_第73张图片

Kafka总体

如何提升吞吐量

如何提升吞吐量?

  1. 提升生产吞吐量
    (1)buffer.memory:发送消息的缓冲区大小,默认值是32m,可以增加到64m。
    (2)batch.size:默认是16k。如果batch设置太小,会导致频繁网络请求,吞吐量下降;如果batch太大,会导致一条消息需要等待很久才能被发送出去,增加网络延时。
    (3)linger.ms,这个值默认是0,意思就是消息必须立即被发送。一般设置一个5-100毫秒。如果linger.ms设置的太小,会导致频繁网络请求,吞吐量下降;如果linger.ms太长,会导致一条消息需要等待很久才能被发送出去,增加网络延时。
    (4)compression.type:默认是none,不压缩,但是也可以使用lz4压缩,效率还是不错的,压缩之后可以减小数据量,提升吞吐量,但是会加大producer端的CPU开销。
  2. 增加分区
  3. 消费者提高吞吐量
    (1)调整fetch.max.bytes大小,默认是50m。
    (2)调整max.poll.records大小,默认是500条。
  4. 增加下游消费者处理能力

数据精准一次

  1. 生产者角度
    (1) acks设置为-1 (acks=-1)。
    (2)幂等性(enable.idempotence = true) + 事务 。
  2. broker服务端角度
    (1)分区副本大于等于2 (–replication-factor 2)。
    (2) ISR里应答的最小副本数量大于等于2 (min.insync.replicas = 2)。
  3. 消费者
    (1) 事务 + 手动提交offset (enable.auto.commit = false)。
    (2) 消费者输出的目的地必须支持事务(MySQL、Kafka)。

合理设置分区数

  1. 创建一个只有1个分区的topic。
  2. 测试这个topic的producer吞吐量和consumer吞吐量。
  3. 假设他们的值分别是Tp和Tc,单位可以是MB/s。
  4. 然后假设总的目标吞吐量是Tt,那么分区数 = Tt / min(Tp,Tc)。 例如:producer吞吐量 = 20m/s;consumer吞吐量 = 50m/s,期望吞吐量100m/s; 分区数 = 100 / 20 = 5分区 分区数一般设置为:3-10个 分区数不是越多越好,也不是越少越好,需要搭建完集群,进行压测,再灵活调整分区个数

单条日志大于1m

kafka学习笔记_第74张图片

服务器挂了

在生产环境中,如果某个Kafka节点挂掉。 正常处理办法:

  1. 先尝试重新启动一下,如果能启动正常,那直接解决。
  2. 如果重启不行,考虑增加内存、增加CPU、网络带宽。
  3. 如果将kafka整个节点误删除,如果副本数大于等于2,可以按照服役新节点的方式重新服役一个新节点,并执行负载均衡。

集群压力测试

Kafka压测

用Kafka官方自带的脚本,对Kafka进行压测。

  1. 生产者压测:kafka-producer-perf-test.sh
  2. 消费者压测:kafka-consumer-perf-test.sh

Kafka Producer压力测试

(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.

参数说明:

  1. record-size是一条信息有多大,单位是字节,本次测试设置为1k。
  2. num-records是总共发送多少条信息,本次测试设置为100万条。
  3. throughput 是每秒多少条信息,设成-1,表示不限流,尽可能快的生产数据,可测出生产者最大吞吐量。本次实验设置为每秒钟1万条。
  4. producer-props 后面可以配置生产者相关参数,batch.size配置为16k。

(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

Kafka Consumer压力测试

(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
参数说明:

  1. –bootstrap-server 指定Kafka集群地址
  2. –topic 指定topic的名称
  3. –messages 总共要消费的消息个数。本次实验100万条。

(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

你可能感兴趣的:(大数据,kafka)