kafka

一、kafka概述

kafka如何实现高性能

  • 增加消费者,提升消息的处理速度。相应的可以增加生产者的数量。
  • 将消息队列分类,每一类为一个topic,生产者将消息按照topic将不同的消息投递到消息队列中,消费者消费对应topic的消息
  • 分区并行消费:如果一个topic中的消息过多,可以将消息拆分为几段,每段为一个partition分区,一个消费者负责一个partition

kafka如何实现高扩展性

  • Topic 多分区:单台机器的资源有限,可以将partition分散在不同机器上,每台机器代表一个broker
  • Broker 集群扩容:可以通过增加broker来缓解CPU和内存占用过高的问题
  • 消费能力不足时,增加消费者组内的消费者数量(最多不超过分区数),即可线性提升消费速度。

kafka如何实现高可用

  • 可以给partition增加几个副本,分为leader副本和follower副本,leader负责处理生产者和消费者之间的读写请求,follower负责异步同步leader的内容
  • 将leader和follower分散到不同的borker上,可以避免leader挂掉后消息不可用
  • ISR(同步副本集合)机制:只有与 Leader 保持同步的副本才能被选举为新 Leader,保证数据不丢失、不不一致。
  • ACK 可靠性策略:生产者可配置 acks=all,确保所有 ISR 副本都落盘后才认为发送成功,保证消息不丢失。

二、consummer group的作用

1、实现消息的并行消费

  • 单个消费者的处理能力有限,无法高效处理高吞吐量的消息流。
  • 消费者组允许多个消费者协同消费同一个 Topic 的分区,每个分区仅由组内的一个消费者处理,从而实现水平扩展

2、负载均衡

  • 分区数量固定时,消费者组自动平衡分区分配(通过内置策略如 Range、RoundRobin、Sticky)。

3、容错与高可用

  • 消费者组通过心跳机制检测故障,触发 Rebalance 将分区重新分配给存活的消费者。避免单个消费者崩溃时,造成消息积压或丢失

4、消息消费进度的统一管理

  • 消费者组维护统一的 Offset:
  • 每个消费者组的消费进度(Offset)存储在 Kafka 内部 Topic __consumer_offsets 中。
  • 即使消费者重启或替换,也能从上次提交的 Offset 恢复,避免重复消费或丢失消息

三、zookeeper的作用

  • 组件太多了,需要统一维护组件的状态信息。zookeeper会定期和borker通信获取kafka集群的状态

四、kafka中的重要概念

主题(topic)

  • Kafka中的消息以主题为单位进行归类,生产者负责将消息发送到特定的主题(发送到Kafka集群中的每一条消息都要指定一个主题),而消费者负责订阅主题并进行消费。
  • 主题是一个逻辑上的概念,可以包含多个分区。
  • 同一主题下的不同分区包含的消息是不同的。

分区(partition)

  • 分区在存储层面可以看作成一个可追加的日志(Log)文件,消息在被追加到分区日志文件的时候都会分配一个特定的偏移量(offset)
  • offset是消息在分区中的唯一标识,Kafka通过它来保证消息在分区内的顺序性,不过offset并不跨越分区

副本的集合AR(Assigned Replicas)、ISR(In-Sync Replicas)、OSR(Out-of-Sync Replicas)

  • 分区中的所有副本统称为AR。
  • 所有与leader副本保持一定程度同步的副本(包括leader副本)组成ISR,ISR集合是AR集合中的一个子集。
  • 与leader副本同步滞后过多的副本(不包括leader副本)组成OSR。由此可见,AR=ISR+OSR。
  • 消息会先发送到leader副本,然后follower副本才能从leader副本中拉取消息进行同步,同步期间内follower副本相对于leader副本而言会有一定程度的滞后。该时间阈值由replica.lag.time.max.ms参数设定,默认30s

LEO和HW

  • LEO是Log End Offset的缩写,它标识当前日志文件中下一条待写入消息的offset,即LEO的大小相当于当前日志分区中最后一条消息的offset值加1。
  • HW是High Watermark的缩写,俗称高水位,它标识了一个特定的消息偏移量(offset),消费者只能拉取到这个offset之前的消息。
    • ISR集合中最小的LEO即为分区的HW,对消费者而言只能消费HW之前的消息。
  • 图解LEO和HW
    image

分区策略

  • kafka默认分区器DefaultPartitioner
  • 规则1:指明partition的情况下,直接将指明的值作为partition值
    • 例如partition=0,所有数据写入分区0
  • 规则2:没有指明partition值但有key的情况下,将key的hash值与topic的partition数进行取余得到partition值;
    • 例如:key1的hash值=5,key2的hash值=6,topic的partition数=2,那么key1对应的value1写入1号分区,key2对应的value2写入0号分区。
  • 规则3:既没有partition值又没有key值的情况下,Kafka采用Sticky Partition(黏性分区器),会随机选择一个分区,并尽可能一直使用该分区,待该分区的batch已满或者已完成,Kafka再随机一个分区进行使用(和上一次的分区不同)
  • 自定义分区,研发人员可以根据企业需求,自己重新实现分区器

kafka消费发送流程

  • 1、生产者的main线程调用send方法创建ProducerRecord对象来封装消息,包含主题、key、value。
  • 2、拦截器对消息预处理(如添加审计信息),序列化器将key/value转换为字节数组,分区器确定消息的目标分区
    • 若指定key,则按key哈希分配(相同key到同一分区)
    • 若无key,则轮询或粘性分区(Sticky Partitioning
  • 3、消息按照topic-partition分组存入内存缓冲区,等待批量发送,
    • 缓冲区默认32m,由参数buffer.memory控制
    • 每个分区对应一个Deque< ProducerBatch >。Deque(双端队列),支持从队列头部或尾部快速插入/删除数据。ProducerBatch是一个包含多条消息的批次,是Kafka网络传输的最小单位。
    • 参数batch.size,控制单个ProducerBatch的最大容量(默认16KB)
    • linger.ms,决定批次在Deque中的等待时间
  • 4、Sender线程(独立I/O线程)轮询缓冲区中的消息异步发送到broker
    • 满足以下任一条件即发送:批次满(batch.size)、等待超时(linger.ms)、缓冲区满(buffer.memory)
    • 按topic-partition选择目标Broker(通过元数据缓存定位Leader副本)
  • 5、Leader将消息写入页缓存(PageCache)(非直接落盘,依赖OS刷盘机制),leader将消息同步给副本,leader根据ACK应答级别给sender线程发送确认。
    • acks=1(默认):仅Leader写入成功即响应
    • acks=all:所有ISR副本同步完成才响应
    • acks=0:不等待响应(可能丢失消息)
  • 6、sender收到确认后将缓冲区中的数据清除。main线程通过回调函数(callback)异步获取消息发送结果,避免阻塞
    image

生产者提高吞吐量的设置参数

  • batch.size:批次大小,默认16k
  • linger.ms:等待时间,修改为5-100ms
  • compression.type:传输启用压缩,方式为snappy
  • buffer.memory:缓冲区大小,默认32m,修改为64m

kafka的数据可靠性

kafka的ACK应答级别

  • acks=0:producer发出消息即代表完成发送,不需要等待来自borker的落盘应答。这种情况下数据传输效率最高,但是数据可靠性确是最低的
  • acks=1:producer发出消息,等待 Leader 副本写入成功,但不等待 Follower同步。
  • acks=-1(a11):生产者发送过来的数据,Leader和isr队列中的节点都落盘后才表示写入成功

数据完全可靠条件

  • ACK级别设置为-1
  • 分区副本大于等于2
  • ISR里应答的最小副本数量大于等于2

kafka数据传递语义

  • Kafka 的数据传递语义(Message Delivery Semantics)定义了消息在生产者、Broker 和消费者之间的可靠性保证,主要分为以下三种级别

最多一次(AtMost Once)

  • 语义:消息可能丢失,但绝不会重复消费
  • 实现条件
    • 生产者的ACK级别设置为0

至少一次(AtLeast Once)

  • 语义:消息绝不会丢失,但可能重复消费
  • 实现条件
    • ACK级别设置为-1
    • 分区副本数大于等于2
    • ISR里应答的最小副本数量大于等于2

精确一次(ExactlyOnce)

  • 语义:消息既不丢失也不重复
  • 实现条件
    • 幂等性
    • ACK级别设置为-1
    • 分区副本数大于等于2
    • ISR里应答的最小副本数量大于等于2

消息重复场景

  • 生产者重试:
    • 当生产者发送消息后,如果leader副本写入成功但未收到所有ISR(in-sync replicas)副本的确认。(ack=1 或 ack=-1)
    • 生产者会重试发送,可能导致消息被重复写入
  • Leader切换:
    • 如果leader在发送ack后但在将消息传播给所有follower之前崩溃,新leader可能不包含该消息,(ack=1 或 ack=-1),导致生产者重试
  • 消费者提交偏移量失败:
    • 消费者处理完消息后,提交offset前崩溃,下次启动时会从最后提交的offset重新消费

漏消费场景

  • 自动提交的间隔设置较短,消息尚未处理完成,就被自动提交
# 危险配置
enable.auto.commit=true
auto.commit.interval.ms=1000  # 自动提交间隔过短
  • 消息处理逻辑异常,比如逻辑设置先提交后处理,但是消息处理过程失败了
  • 日志清理策略导致,log.retention.ms设置过短,消息被删除后消费者才来消费
kafka-configs --describe --topic my-topic --all | grep retention
  • 消费者seek跳过消息,手动定位offset时跳过了某些消息

kafka幂等性

  • Kafka 的幂等性是一种保证消息不重复的机制,它能确保生产者发送的消息即使因网络问题或重试导致多次发送,Broker 也只会持久化一次。

如何启用幂等性

  • 配置生产者参数 enable.idempotence 为 true
  • 启用后,Kafka 会自动设置以下参数:
    • acks=all(必须确保消息被完整接收)
    • retries=Integer.MAX_VALUE(无限重试)
    • max.in.flight.requests.per.connection=1(同一时间只能有一个未确认的请求)

幂等性原理

  • Kafka 通过以下关键标识符实现幂等性:

    • PID(Producer ID):每个生产者实例的唯一标识。Kafka每次重启都会分配一个新的
    • Sequence Number:每条消息的序列号(针对每个分区单调递增)
  • Broker 会检查这组标识符:

    • 如果收到相同 PID + 分区 + Sequence Number 的消息,会识别为重复消息并丢弃
    • 如果 Sequence Number <= 已存储的序列号,则判定为重复消息,直接丢弃。
    • 如果 Sequence Number == 已存储的序列号 + 1,则接受消息并更新存储的序列号。
    • 如果 Sequence Number > 已存储的序列号 + 1(乱序),则拒绝消息(可能发生数据丢失)。

幂等性的限制

  • 仅保证单分区单会话内的幂等:
    • 不能跨分区保证幂等
    • 不能跨生产者会话保证幂等(重启后PID会变)
  • 不保证跨分区的原子性:
    • 如果需要跨分区/跨Topic的原子写入,需要使用事务(Transactions)

kafka事务

  • Kafka事务(Transactions)提供了跨分区和跨Producer会话的原子性写入能力,能够确保消息"要么全部成功,要么全部失败",是构建可靠流处理系统的关键特性。
    image

事务关键配置

  • 生产者配置
    • 必须配置 transactional.id(唯一标识一个事务型 Producer)
    • 必须启用幂等性(enable.idempotence=true)
    • ack级别设置为-1(all)
  • 消费者配置
    • 只读取已提交消息(isolation.level=read_committed)

事务实现原理

  • Kafka Broker 中有一个 事务协调器(Transaction Coordinator),负责管理事务的提交/回滚
  • 每个 transactional.id 会被哈希到某个 Broker,由其 Transaction Coordinator 管理
  • Kafka 内部有一个特殊的 Topic __transaction_state,用于持久化事务元数据(如事务状态、PID 映射等)

使用场景

  • 精确一次处理(Exactly-once)
  • 流处理应用:如Kafka Streams的状态更新
  • 跨系统事务:与数据库事务配合(通过Kafka Connect)
  • 关键业务操作:如金融交易、订单处理

最佳实践

  • 为每个逻辑Producer分配唯一transactional.id
  • 事务应尽量短小,避免长时间运行
  • 合理设置transaction.timeout.ms(默认60秒)
  • 监控事务相关指标:提交/中止比率、平均持续时间等
  • 与幂等性配合使用(enable.idempotence=true)

kafka数据有序性

  • kafka保证在单个分区内,消息严格按照发送顺序存储和消费
  • 实现机制:
    • 生产者按顺序发送消息到分区
    • 分区在Broker上是追加写入的日志(Log)
    • 消费者按偏移量(Offset)顺序读取
  • 不同分区之间的消息没有顺序保证

影响有序性的关键因素

  • 生产者消息发送的重试机制:
    • 默认配置下重试可能破坏顺序
    • 解决方案:设置 max.in.flight.requests.per.connection=1-
  • 生产者消息的批量发送:
    • 批量发送可能影响消息实际到达顺序
    • 解决方案:调整 linger.ms 和 batch.size
  • Leader切换:
    • 在 unclean leader election 情况下可能丢失顺序
    • 解决方案:设置 unclean.leader.election.enable=false
  • 消费者的并行消费:
    • 单个消费者多线程消费会破坏顺序
    • 解决方案:每个分区使用单线程消费

保证有序性的配置方案

# 生产者配置
# 保证分区内有序的最小配置
acks=all
max.in.flight.requests.per.connection=1
retries=Integer.MAX_VALUE
enable.idempotence=true


# 消费者配置
# 保证顺序消费
max.poll.records=1  # 每次只拉取一条(极端情况)
fetch.min.bytes=1   # 避免等待批量拉取

kafka命令行工具

  • Kafka 的命令行工具在 Kafka 包的/bin目录下,主要包括服务和集群管理脚本,配置脚本,信息查看脚本,Topic 脚本,客户端脚本等。
  • kafka-configs.sh:配置管理脚本
  • kafka-console-consumer.sh:kafka 消费者控制台
  • kafka-console-producer.sh:kafka 生产者控制台
  • kafka-consumer-groups.sh:kafka 消费者组相关信息
  • kafka-delete-records.sh:删除低水位的日志文件
  • kafka-log-dirs.sh:kafka 消息日志目录信息
  • kafka-mirror-maker.sh:不同数据中心 kafka 集群复制工具
  • kafka-preferred-replica-election.sh:触发 preferred replica 选举
  • kafka-producer-perf-test.sh:kafka 生产者性能测试脚本
  • kafka-reassign-partitions.sh:分区重分配脚本
  • kafka-replica-verification.sh:复制进度验证脚本
  • kafka-server-start.sh:启动 kafka 服务
  • kafka-server-stop.sh:停止 kafka 服务
  • kafka-topics.sh:topic 管理脚本
  • kafka-verifiable-consumer.sh:可检验的 kafka 消费者
  • kafka-verifiable-producer.sh:可检验的 kafka 生产者
  • zookeeper-server-start.sh:启动 zk 服务
  • zookeeper-server-stop.sh:停止 zk 服务
  • zookeeper-shell.sh:zk 客户端

创建topic

bin/kafka-topics.sh --bootstrap-server localhost:9092 \
--create \
--topic my_topic_name \
--partitions 20 \
--replication-factor 3 \
--config x=y

修改主题

  • Kafka 目前不支持减少主题的分区数
# 修改主题的分区数量为40个
bin/kafka-topics.sh --bootstrap-server localhost:9092 \
--alter \
--topic my_topic_name \
--partitions 40

添加配置

bin/kafka-configs.sh --bootstrap-server localhost:9092 \
--entity-type topics \
--entity-name my_topic_name \
--alter \
--add-config x=y

删除配置

bin/kafka-configs.sh --bootstrap-server localhost:9092 \
--entity-type topics \
--entity-name my_topic_name \
--alter \
--delete-config x

删除主题

bin/kafka-topics.sh --bootstrap-server localhost:9092 \
--delete \
--topic my_topic_name

启动生产者

./kafka-console-producer.sh --bootstrap-server KAFKA_IP:9092 --topic TOPIC_NAME

启动消费者

./kafka-console-consumer.sh --bootstrap-server KAFKA_IP:9092 --consumer-property group.id=GROUP_NAME --topic TOPIC_NAME

生产者将消息发送给broker,broker会将消息保存在本地的日志文件中
/usr/local/kafka/data/kafka-logs/主题-分区/00000000.log
消息的保存是有序的,通过offset偏移量来描述消息的有序性

单播消息
如果多个消费者在同一个消费组,只有一个消费者可以收到订阅的topic中的消息
多播消息
不同的消费组订阅同一个topic,那么不同的消费组中只有一个消费者能收到消息

查看消费者详细信息
./kafka-consumer-group.sh --bootstrap-server KAFKA_IP:9092 --describe --group GROUP_NAME

查看所有消费组列表

./kafka-consumer-groups.sh --bootstrap-server localhost:9092 --list

查看指定消费组的详细信息

# 输出中的LAG字段表示滞后消息数(LOG-END-OFFSET 减去 CURRENT-OFFSET)
./kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group my-group --describe

# 如果只关心 Lag(积压消息数),可以结合 awk 筛选
# 只看GROUP 、TOPIC 、PARTITION 、LAG这四个字段
./kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group my-group --describe | awk '{print $1,$2,$3,$6}'

查看消费组状态

# Stable:消费组正常运行,所有分区已分配。
# Empty:消费组存在,但没有活跃的消费者。
# Dead:消费组已无消费者,且所有 offset 已过期(通常 __consumer_offsets 已清理)
./kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group my-group --state

查看消费者成员信息

# #PARTITIONS:该消费者负责的分区数。
# ASSIGNMENT:具体分配的分区列表
./kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group my-group --members --verbose

重置消费组 Offset

  • 如果需要手动调整消费位置(如重新消费或跳过积压数据)
  • 可选重置策略:
    • --to-earliest:将偏移量重置为最早偏移量。
    • --to-latest:将偏移量重置为最新偏移量(丢弃积压数据)。
    • --to-current :将偏移量重置为当前偏移量
    • --to-datetime 2023-01-01T00:00:00.000:按时间重置。
    • --shift-by -100:向后移动 100 条消息(负数表示回退)。
./kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
--group my-group \
--reset-offsets \
--to-earliest \
--topic my-topic \
--execute

zookeeper

image

  • [zk: localhost:2181(CONNECTED) 2] ls /kafka
  • 这个命令是用于查看 ZooKeeper中 /kafka 路径下的子节点,通常用于Kafka 集群管理。
  • Kafka 依赖 ZooKeeper 存储集群元数据,/kafka 是 Kafka 的根路径

Controller(控制器)

  • Controller 是从 Broker 中选举出来的,负责分区 Leader 和 Follower 的管理。
  • 当某个分区的 leader 副本发生故障时,由 Controller 负责为该分区选举新的 leader 副本,当检测到某个分区的 ISR(In-Sync Replica)集合发生变化时,由Controller负责通知所有 broker 更新其元数据信息。
  • 当使用kafka-topics.sh脚本为某个 topic 增加分区数量时,同样还是由控制器负责分区的重新分配。
  • Kafka 中 Contorller 的选举的工作依赖于 Zookeeper,成功竞选为控制器的 broker 会在 Zookeeper 中创建/controller这个临时(EPHEMERAL)节点。

image
image

broker节点服役和退役

服役新节点的过程

启动新节点

bin/kafka-server-start.sh -daemon ./config/server.properties

指定需要重新分配分区的主题列表

# 情况1,迁移指定主题
cat topics-to-move.json
{
  "topics": [
    {"topic": "topic1"},
    {"topic": "topic2"}
  ],
  "version": 1
}

# 情况2,迁移主题中的特定分区
{
  "partitions": [
    {"topic": "topic1", "partition": 0},
    {"topic": "topic1", "partition": 1}
  ],
  "version": 1
}

# 情况3,迁移所有主题
# 先获取所有主题
kafka-topics.sh --list --bootstrap-server localhost:9092 > all_topics.txt

# 转换为JSON格式
awk '{print "{\"topic\": \""$1"\"},"}' all_topics.txt > temp.json
echo '{ "topics": [' > topics.json
cat temp.json >> topics.json
echo '], "version": 1 }' >> topics.json

# JSON 格式必须严格正确(可使用 jq 验证)
jq . topics.json

生成分区重新分配计划

# 生成分区重新分配计划(包含新节点)
kafka-reassign-partitions.sh --bootstrap-server kafka101:9092\
  --broker-list "0,1,2,3" \
  --topics-to-move-json-file topics.json \
  --generate \
  > reassign.json

执行重新分配

kafka-reassign-partitions.sh --bootstrap-server kafka101:9092 \
  --reassignment-json-file reassign.json \
  --execute \

验证分配状态

kafka-reassign-partitions.sh --bootstrap-server kafka101:9092 \
  --reassignment-json-file reassign.json \
  --verify \

退役旧节点

1、指定需要重新分配分区的主题列表
2、生成分区重新分配计划(不包含退役节点)
3、执行重新分配
4、验证进度
5、停止kafka服务

kafka的Leader选举流程

Kafka 集群中有一个 broker 的 Controller 会被选举为 Controller Leader,负责管理集群broker 的上下线,所有 topic 的分区副本分配和 Leader 选举等工作。
Controller 的信息同步工作是依赖于 Zookeeper 的
image

leader、follower故障处理细节

Follower故障

(1)Followrer发生故障后会被临时踢出ISR
(2)这个期间Leader和Follower继续接收数据(3)待该Fo1lower恢复后,Fo1lower会读取本地磁盘记录的上次的HW,并将1og文件高于HW的部分截取掉,从HW开始向Leader进行同步。
(4)等该Follower的LEO大于等于该Partition的HW,即Fo1lower追上Leader之后,就可以重新加入ISR了。

Leader故障

(1)Leader发生故障之后,会从ISR中选出一个新的Leader(2)为保证多个副本之间的数据一致性,其余的Follower会先将各自的1og文件高于HW的部分截掉,然后从新的Leader同步数据。
注意:这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。

手动调整分区副本数量

  • 在生产环境中, 每台服务器的配置和性能不一致, 但是Kafka只会根据自己的代码规则创建对应的分区副本, 就会导致个别服务器存储压力较大。 所以需要手动调整分区副本的存储
点击查看代码
# (1)创建一个新的 topic, 名称为 three。
bin/kafka-topics.sh --bootstrap-server hadoop102:9092 \
--create --partitions 4 \
--replication-factor 2 --topic three
#(2)查看分区副本存储情况。
bin/kafka-topics.sh --bootstrap-server hadoop102:9092 \
--describe --topic three
#(3)创建副本存储计划(所有副本都指定存储在broker0、1 中)。
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)执行副本存储计划
bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --reassignment-json-file
increase-replication-factor.json --execute
#(5)验证副本存储计划。
[atguigu@hadoop102 kafka]$ bin/kafka-reassign-partitions.sh --
bootstrap-server hadoop102:9092 --reassignment-json-file
increase-replication-factor.json --verify
#(6) 查看分区副本存储情况。
bin/kafka-topics.sh --bootstrap-server
hadoop102:9092 --describe --topic three

leader partition负载平衡

通常auto.leader.rebalance.enable在生产中设置为false,即不开启
image

增加副本因子

在生产环境当中,由于某个主题的重要等级需要提升,我们考虑增加副本。副本数的增加需要先制定计划,然后根据计划执行

点击查看代码
#1)创建 topic(目前副本数为1)
bin/kafka-topics.sh --bootstrap-server hadoop102:9092 \
--create --partitions 3 --replication-factor 1 --topic four
#2)手动增加副本存储
# 创建副本存储计划(所有副本都指定存储在 broker0、 broker1、 broker2 中)。
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]}]}
#执行副本存储计划。
bin/kafka-reassign-partitions.sh --
bootstrap-server hadoop102:9092 --reassignment-json-file
increase-replication-factor.json --execute

kafka文件存储

partition存储目录结构

  • Topic是逻辑上的概念,而partition是物理上的概念,每个partition对应于一个物理文件夹,文件夹中存储的就是实际的消息数据。消息是以日志分段(segment)的形式存储的。文件夹的命名规则为: topic名称+分区序号, 例如: first-0。
  • 一个 Topic 可划分为多个 Partition(如 名为orders的topic 分为 orders-0、orders-1)
  • Partition 是 Kafka 并行化的最小单元,不同 Partition 可分布在不同 Broker 上。
/kafka-logs/
    ├── orders-0/                           # Topic: orders, Partition: 0
    │   ├── 00000000000000000000.index      # 偏移量索引文件
    │   ├── 00000000000000000000.log        # 消息数据文件(核心)
    │   ├── 00000000000000000000.timeindex  # 时间戳索引文件
    │   └── 00000000000000012345.log        # 新Segment(达到1GB或7天时创建)
    └── orders-1/                           # Partition: 1
# index和log文件均以当前segment的第一条消息的offset命名

核心文件类型

  • 日志文件(.log)

    • 存储实际消息,文件名以当前分段的起始偏移量命名(如 00000000000000000000.log)。
    • 写入方式:仅追加(Append-Only),保证顺序写性能。
    • 分段策略:
    • 大小阈值:log.segment.bytes=1GB(默认)
    • 时间阈值:log.roll.hours=168(7天,默认)
  • 索引文件(.index)

    • 稀疏索引:每写入 log.index.interval.bytes=4KB 数据,记录一条 <Offset, 物理位置> 映射
    • 作用:快速定位消息,避免全量扫描。
  • 时间索引文件(.timeindex)

    • 按时间戳建立索引,支持按时间范围查询(如 offsetsForTimes API)。

kafka消息定位流程

  • 1、确定目标 Partition 和 Offset
    • 消费者指定要读取的 Topic、Partition 和 Offset(如 orders-0 分区的 Offset=220)
  • 2、查找对应的 Segment 文件
    • Segment 文件命名是以该segment中第一条索引的名字命名的
    • 选择文件名 ≤ 目标 Offset 的最大 Segment。
  • 3、二分查找索引文件(.index)
    • 索引条目格式:<Offset, 物理位置>(如 200:1024 表示 Offset=200 的消息在 .log 文件的 1024 字节处)。
    • 读取 .index 文件,找到 ≤ 目标 Offset 的最大索引条目(如 Offset=200)。
    • 根据索引中的物理位置(如 1024),从 .log 文件的对应位置开始顺序扫描,直到找到目标 Offset=220 的消息
  1. 返回消息内容
    解析 .log 文件中的消息头(Header)和负载(Payload),返回给消费者。

文件清理策略

删除策略(delete)

  • log.cleanup.policy = delete 启用删除策略
  • 基于时间。Kafka 中默认的日志保存时间为 7 天,可以通过调整如下参数修改保存时间。超时的日志会被删除
    • log.retention.hours, 最低优先级小时,默认 7 天。
    • log.retention.minutes, 分钟。
    • log.retention.ms, 最高优先级毫秒。
    • log.retention.check.interval.ms,负责设置检查周期,默认5分钟。
  • 基于大小。超过设置的所有日志总大小,删除最早的 segment。
    • log.retention.bytes = -1,(默认不限制)

压缩策略(compact)

  • log.cleanup.policy = compact 启用压缩策略
    针对Key:保留同一Key的最新值,适用于用户配置更新的情况,例如用户的年龄变更

运维调试,查看日志

# 监控清理状态
# 查看清理线程状态
kafka-topics.sh --describe --topic user-profile \
  --bootstrap-server localhost:9092 | grep "LogCleaner"

# JMX 指标(如 LogCleaner 吞吐量)
kafka-run-class kafka.tools.JmxTool \
  --object-name kafka.log:type=LogCleaner


运维命令示例
(1)查看 Topic 的磁盘占用
kafka-log-dirs.sh --bootstrap-server localhost:9092 \
  --describe --topic-list orders
(2)手动触发 Segment 滚动
kafka-topics.sh --bootstrap-server localhost:9092 \
  --alter --topic orders --config segment.bytes=1073741824
(3)解析 Segment 文件内容
kafka-dump-log.sh --files /data/kafka-logs/orders-0/00000000000000000000.log
 (4) 检查特定 Topic 的分区分布
kafka-log-dirs.sh --bootstrap-server localhost:9092 \
  --describe --topic-list test
 (5) 手动清理过期日志
kafka-log-dirs.sh --bootstrap-server localhost:9092 \
  --alter --topic test --partition 0 \
  --log-dir /data/kafka-logs \
  --config retention.ms=3600000  # 保留1小时

kafka高效读写数据的原因

  • 1、 kafka采用分布式分区架构(并行度高)

    • Topic 分为多个 Partition:每个 Partition 是一个有序的、不可变的消息队列。
    • Producer可同时向不同 Partition 并行写入。
    • Consumer Group中不同 Consumer 可并行消费不同 Partition。
  • 2、 稀疏索引(快速定位数据)
    索引文件(.index):存储消息 Offset 到物理位置的映射(每4KB数据建一条索引)。
    日志文件(.log):存储实际消息,文件名以当前 Segment(日志分段) 的起始 Offset 命名。
    根据目标 Offset 二分查找 最近的索引条目。
    从索引指向的物理位置开始顺序扫描日志文件。

  • 3、 顺序写磁盘(写入高性能)
    Kafka 的 producer 生产数据,要写入到 log 文件中,写的过程是一直追加到文件末端,为顺序写。省去了大量磁头寻址的时间

  • 4、 页缓存 + 零拷贝,实现读取高性能

    • 零拷贝
    • 传统的文件传输数据需要经过,磁盘 → 内核缓冲区 → 用户缓冲区 → Socket缓冲区 → 网卡。历经(4次拷贝 + 2次CPU上下文切换)
    • kafka broker不处理数据,数据的处理是在生产者和消费者端处理。零拷贝kafka传输数据经过,磁盘 → 内核缓冲区 → 网卡(2次拷贝 + 0次CPU切换)
    • 页缓存
    • PageCache页缓存:Kafa直接利用操作系统提供的PageCache功能。当上层有写操作时,操作系统只是将数据写入PageCache。当读操作发生时,先从PageCache中查找,如果找不到,再去磁盘中读取。实际上PageCache是把尽可能多的空闲内存都当做了磁盘缓存来使用。

image

kafka消费者

kafka消费方式

  • consumer采用从broker中主动拉取数据
  • Kafka没有采用push这种方式,因为由broker决定消息发送速率, 很难适应所有消费者的消费速率。
  • pull模式不足之处是,如果Kafka没有数据,消费者可能会陷入循环中, 一直返回空数据。

消费者组

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

Coordinator(协调器)

  • Coordinator 是一个 Broker,而不是消费者。每个 Kafka Broker 都可以担任某些消费者组的 Coordinator。
  • 在 Kafka 中,Coordinator(协调器) 负责管理消费者组(Consumer Group)的元数据。也就是系统队列__consumer_offsets中Topic的对应分区数据
  • 负责处理消费者的心跳、触发再平衡(Rebalance)等关键操作。

如何确定协调器

  • groupid的hashcode值 % 50(__consumer_offsets的分区个数)
  • 如果得到的值为1,那么__consumer_offsets主题的1号分区所在的broker作为该groupid的coordinator

Kafka 消费者组的初始化流程

  • 1、消费者启动并订阅 Topic
  • 2、确定 Group Coordinator
    • 消费者通过 groupid的hashcode值 % 50(__consumer_offsets的分区个数)得到一个分区
    • 向集群任意 Broker 发送 FindCoordinator 请求,获取该分区 Leader 所在的 Broker(即 Coordinator)
  • 3、消费者向 Coordinator 发送 JoinGroup 请求
    • 首次加入组的话,Coordinator 选举第一个注册的消费者作为 Leader 消费者。Leader 消费者负责计算分区分配方案(如 Range、RoundRobin)。
    • 非首次加入的情况,直接同步现有分配方案。
  • 4、分区分配(SyncGroup)
    • Leader 消费者计算分配策略后,通过 SyncGroup 请求提交给 Coordinator。Coordinator 将分配结果同步给所有消费者。
  • 5、Offset 初始化
    • 消费者根据auto.offset.reset的配置决定从何处开始消费
  • 6、开始消费并维持心跳
    • 消费者启动独立线程,按 heartbeat.interval.ms(默认3秒)向 Coordinator 发送心跳。
    • 若 Coordinator 在 session.timeout.ms(默认45秒)内未收到心跳,触发再平衡。

image

消费者的重要参数

# 向 Kafka集群建立初始连接用到的 host/port 列表。
bootstrap.servers

# 指定接收消息的 key 和 value 的反序列化类型。一定要写全类名。
key.deserializer 和 value.deserializer

# 指定接收消息的 key 和 value 的反序列化类型。一定要写全类名。
group.id

# 默认值为 true,表示消费者会自动周期性地向服务器提交偏移量。
enable.auto.commit

# 则该值定义了消费者偏移量向 Kafka自动提交的频率
auto.commit.interval.ms

# 当 Katka 首次启动或当前偏移量在服务器中不存在时偏移量的设置策略
# earliest:自动重置偏移量到最早的偏移量。
# latest:默认,自动重置偏移量为最新的偏移量。
# none:如果消费组原来的(previous)偏移量不存在,则向消费者抛异常。
# anything:向消费者抛异常。
auto.offset.reset

# 控制Kafka内部主题__consumer_offsets的分区数量
offsets.topic.num.partitions

# Kafka 消费者和组协调器(Group Coordinator)之间的心跳时间,默认 3s。该条目的值必须小于 session.timeout.ms ,也不应该高于session.timeout.ms 的1/3。超过session.timeout.ms时间无应答被踢出消费者组
heartbeat.interval.ms

# 消费者处理消息的最大时长,默认是5分钟。超过该值,消费者会从消费者组中被移除,消费者组执行再平衡。
max.poll.interval.ms

# 默认1个字节。消费者获取服务器端一批消息最小的字节数。
fetch.min.bytes

# 默认 500ms。如果没有从服务器端获取到一批数据的最小字节数。该时间到,仍然会返回数据。
fetch.max.wait.ms

# 一次 poll 拉取数据返回消息的最大条数,默认是 500 条。
max.poll.records

image

消费者组的分区分配策略

  • Kafka 消费者组的分区分配策略(Partition Assignment Strategy)决定了 Topic 分区如何分配给组内的消费者,直接影响消费的 负载均衡 和 故障恢复。Kafka 提供了多种内置策略,并支持自定义分配逻辑

Range 分配策略

  • Range是对每个topic而言的,首先对同一个 topic 里面的分区按照字典顺序(简单理解为序号顺序)进行排序,并对消费者按照字典(字母)顺序进行排序。
  • 假如有7个分区(p0,p1,p2,p3,p4,p5,p6),3个消费者(C0,C1,C2)
    • C0分配p0、p1、p2
    • C1分配p3、p4
    • C2分配p5、p6
  • 如果有N多个topic,消费者C0可能都会多消费1个分区,容易产生数据倾斜!

RoundRobin 分配策略

  • RoundRobin 针对集群中所有Topic而言。
  • 将所有分区和消费者按哈希排序,然后轮询分配。
  • 例如:3 个分区 (p0, p1, p2),2 个消费者 (c1, c2):
    • c1 分配 p0, p2
    • c2 分配 p1
  • 优点:分配更均衡(尤其适用于消费者订阅相同 Topic 列表)。
  • 缺点:如果消费者订阅的 Topic 不同,可能导致分配不均。

Sticky (粘性)分配策略

  • 首次分配时尽量均衡(类似 RoundRobin)。
  • 消费者变动时(如重启),尽量保留原有分配,仅调整必要的分区。
  • 优点:减少分区迁移(降低 Rebalance 开销)。避免重复消费或数据倾斜。
  • 适用场景:消费者组频繁扩缩容(如云环境动态伸缩)。
  • 理解“尽量均衡”,7个分区,3个消费者,三个消费者消费的分区数量是3,2,2。和range策略不同的是,range会先将分区排序,sticky则不会

分配策略的选择建议

  • 分区数固定,消费者稳定。Range 或 RoundRobin
  • 消费者动态变化(如 Kubernetes Pod 扩缩容)。Sticky
  • 需要最小化 Rebalance 影响。Sticky
  • 自定义分区逻辑(如按业务键分配)。自定义策略

消费者offset的默认维护位置

  • kafka消费者offset维护在系统主题__consumer_offsets中,主题里面采用 key 和 value 的方式存储数据。
  • key 是 group.id+topic+分区号, value 就是当前 offset 的值。 每隔一段时间, kafka 内部会对这个 topic 进行compact(紧凑),也就是每个 group.id+topic+分区号就保留最新数据。

消费者自动提交offset

  • enable.auto.commit 默认值为 true,消费者会自动周期性地向服务器提交偏移量。
  • auto.commit.interval.ms定义了消费者偏移量向 Kafka 提交的频率,默认5s。

消费者手动提交offset

  • 虽然自动提交offset十分简单便利, 但由于其是基于时间提交的, 开发人员难以把握offset提交的时机。 因此Kafka还提供了手动提交offset的API。
  • 提交早了有可能提前消费
  • 手动提交offset的方法有两种:
    • commitSync(同步提交):必须等待offset提交完毕,再去消费下一批数据。
    • commitAsync(异步提交):发送完提交offset请求后,就开始消费下一批数据了。
  • 相同点是, 都会将本次poll() 拉取的一批数据中最高的偏移量提交
  • 不同点是,同步提交阻塞当前线程,一直到提交成功,并且会自动失败重试;而异步提交则没有失败重试机制,故有可能提交失败。

指定 Offset 消费

  • auto.offset.reset 是 Kafka 消费者的一个重要配置参数,它决定了当消费者组初次启动或要消费的偏移量在Broker上不存在时(例如偏移量已被删除),消费者的行为策略
  • auto.offset.reset = earliest | latest | none 默认是 latest。
    • earliest:自动将偏移量重置为最早的偏移量, --from-beginning。
    • latest(默认值):自动将偏移量重置为最新偏移量
    • none:如果未找到消费者组的先前偏移量,则向消费者抛出异常。

指定时间消费

需求:在生产环境中,会遇到最近消费的几个小时数据异常,想重新按照时间消费。例如要求按照时间消费前一天的数据,怎么处理?
offsetsForTimes() 方法,研发写代码实现

数据积压(消费者如何提高吞吐量)

1)如果是Katka消费能力不足,则可以考虑增加Topic的分区数,并且同时提升消费组的消费者数量,消费者数=分区数。(两者缺一不可)
2)如果是下游的数据处理不及时:提高每批次拉取的数量。批次拉取数据过少(拉取数据/处理时间<生产速度),使处理的数据小于生产的数据,也会造成数据积压。

posted @ 2026-03-27 11:31  立勋  阅读(60)  评论(0)    收藏  举报