Kafka 基础概念详解
Kafka 是什么?有什么作用?
Apache Kafka 是一个开源的分布式事件流平台,最初由 LinkedIn 开发并开源。它基于发布 - 订阅模式,设计目标是处理高吞吐量、实时数据流。Kafka 的核心价值在于:
-
消息系统:
- 实现系统间松耦合通信
- 支持多生产者和多消费者
- 提供消息持久化存储
-
数据管道:
- 高效收集和传输大量数据
- 支持实时数据流处理
- 作为微服务架构中的事件总线
-
数据存储:
- 基于磁盘的持久化存储
- 可配置的数据保留策略
- 支持数据重放和回溯消费
-
流处理平台:
- 内置 Kafka Streams API
- 与 Flink、Spark 等流处理框架集成
- 支持实时数据转换和分析
Kafka 的架构详解
Kafka 架构主要由以下组件构成:
-
Producer(生产者):
- 负责创建和发送消息到 Kafka 集群
- 支持同步和异步发送模式
- 可以自定义分区策略
- 实现批量发送以提高吞吐量
-
Consumer(消费者):
- 从 Kafka 集群读取消息
- 维护消费偏移量(offset)
- 支持水平扩展(通过消费者组)
- 可以手动控制消费位置
-
Consumer Group(消费者组):
- 一组协同工作的消费者实例
- 实现负载均衡消费
- 确保每个分区只被组内一个消费者消费
- 支持故障自动转移
-
Broker(代理):
- Kafka 集群中的单个服务器节点
- 存储消息数据
- 处理读写请求
- 维护分区的副本
-
Topic(主题):
- 消息的逻辑分类
- 类似于数据库中的表
- 每个主题可以有多个分区
- 支持多租户隔离
-
Partition(分区):
- 主题的物理分片
- 消息在分区内有序存储
- 每个分区可以有多个副本
- 提高并发处理能力
-
Replica(副本):
- 分区的冗余备份
- 包括一个 Leader 和多个 Follower
- Leader 处理所有读写请求
- Follower 被动复制 Leader 数据
-
Controller:
- 特殊的 Broker 节点
- 负责集群管理
- 处理分区 Leader 选举
- 监控 Broker 状态
-
Zookeeper(2.8.0 之前):
- 存储集群元数据
- 协调 Broker 之间的工作
- 记录分区和副本分配
- 监控 Broker 状态
Zookeeper 对于 Kafka 的作用
在 Kafka 2.8.0 版本之前,Zookeeper 扮演了关键角色:
-
元数据管理:
- 存储 Broker 注册信息
- 维护 Topic 和分区信息
- 记录副本分配情况
-
集群协调:
- 选举 Controller 节点
- 处理 Broker 加入和退出
- 管理分区重分配
-
配置管理:
- 存储动态配置参数
- 支持配置变更通知
-
消费者协调:
- 管理消费者组
- 存储消费者偏移量(旧版)
- 协调消费者分区分配
在 2.8.0 版本后,Kafka 引入了 KRaft 模式(Kafka Raft Metadata),不再依赖 Zookeeper:
- 使用 Raft 协议管理元数据
- 减少系统复杂度
- 提高集群稳定性
- 简化部署和运维
生产者相关详解
生产者的发送消息的分区策略
Kafka 提供了多种分区策略:
-
轮询策略(Round Robin):
- 默认分区策略
- 按顺序将消息发送到各个分区
- 确保消息均匀分布
- 适合不需要特定顺序的场景
-
随机策略(Random):
- 随机选择分区
- 实现简单但分布可能不均匀
- 实际使用较少
-
按 key 哈希策略(Keyed):
- 根据消息的 key 计算哈希值
- 相同 key 的消息总是发送到同一分区
- 保证相关消息的顺序性
- 适合需要保持特定顺序的场景
-
粘性分区策略(Sticky Partitioning):
- 2.4 版本引入的优化策略
- 临时固定使用一个分区,攒够一批消息后再换
- 减少元数据请求次数
- 提高吞吐量
-
自定义分区策略:
- 实现 Partitioner 接口
- 根据业务需求定制分区逻辑
- 例如根据消息内容或业务规则选择分区
Kafka 的 ack 的三种机制详解
生产者通过 acks 参数控制消息确认机制:
-
acks=0:
- 生产者发送消息后立即返回
- 不等待任何服务器确认
- 吞吐量最高,但可靠性最低
- 可能丢失消息(如网络故障)
-
acks=1:
- 生产者等待 Leader 副本接收消息
- Leader 写入本地日志后返回确认
- 部分可靠性保障
- 如果 Leader 崩溃且未复制到 Follower,消息可能丢失
-
acks=all(或 -1):
- 生产者等待所有 ISR 副本确认
- 只有当所有 ISR 副本都接收到消息才返回
- 最高可靠性保障
- 吞吐量最低,延迟最高
- 可配置
min.insync.replicas确保至少有指定数量的副本同步
生产者如何保证消息的可靠性
要确保消息不丢失,需要综合配置以下参数:
-
acks=all:
- 确保所有 ISR 副本都收到消息
-
retries > 1:
- 发送失败时自动重试
- 可配置重试间隔和最大重试次数
-
retry.backoff.ms:
- 重试间隔,避免频繁重试导致网络拥塞
-
delivery.timeout.ms:
- 消息发送超时时间
-
max.in.flight.requests.per.connection > 1:
- 控制并发请求数
- 设置为 1 可保证消息顺序性
-
enable.idempotence=true:
- 启用幂等性生产者
- 自动处理重试导致的重复消息
- 仅在同一个会话内有效
-
transactional.id:
- 配置事务 ID
- 实现跨分区、跨主题的原子性操作
- 确保消息的 "恰好一次" 语义
-
错误处理机制:
- 捕获发送异常
- 记录失败消息
- 实现补偿机制
消费者相关详解
consumer 是推还是拉?有什么优劣势?
Kafka 采用 pull(拉)模式从 Broker 获取数据:
优势:
- 消费者控制消费速率,避免被快速生产者压垮
- 支持批量消费,提高吞吐量
- 易于实现水平扩展
- 消费者可以根据需要回溯消费历史数据
劣势:
- 如果没有数据,消费者会空轮询,浪费资源
- 可能导致消息处理延迟(需合理配置参数)
优化措施:
fetch.min.bytes:设置最小拉取数据量,避免频繁空请求fetch.max.wait.ms:设置最大等待时间,避免长时间阻塞max.poll.records:控制一次拉取的最大消息数enable.auto.commit:控制是否自动提交偏移量
消费者如何不自动提交偏移量,由应用提交?
手动提交偏移量的步骤:
-
配置消费者:
enable.auto.commit=false -
处理消息后手动提交:
// 同步提交 consumer.commitSync(); // 异步提交 consumer.commitAsync(); // 带回调的异步提交 consumer.commitAsync((offsets, exception) -> { if (exception != null) { // 处理提交失败的情况 } }); // 提交特定偏移量 Map<TopicPartition, OffsetAndMetadata> offsets = new HashMap<>(); offsets.put(new TopicPartition(topic, partition), new OffsetAndMetadata(nextOffset)); consumer.commitSync(offsets); -
提交时机选择:
- 处理完一批消息后提交
- 处理完特定业务逻辑后提交
- 定时提交
消费者故障,出现活锁问题如何解决?
活锁是指消费者不断重新平衡但无法处理消息的状态,通常由以下原因导致:
-
处理时间过长:
- 消息处理逻辑复杂
- 外部资源依赖(如数据库、API)响应慢
-
配置不合理:
max.poll.records设置过大session.timeout.ms设置过小max.poll.interval.ms设置过小
解决方案:
- 优化消息处理逻辑,减少处理时间
- 增加消费者实例,分担负载
- 调整配置参数:
- 增大
max.poll.interval.ms - 减小
max.poll.records - 增大
session.timeout.ms
- 增大
- 使用异步处理机制,将耗时操作放入线程池
- 实现消息批处理,减少处理频率
如何控制消费的位置?
消费者可以通过以下方式控制消费位置:
-
从特定偏移量开始消费:
// 从指定偏移量开始消费 consumer.seek(topicPartition, offset); // 从最早的消息开始消费 consumer.seekToBeginning(Collections.singleton(topicPartition)); // 从最新的消息开始消费 consumer.seekToEnd(Collections.singleton(topicPartition)); -
根据时间戳定位:
// 构建时间戳到分区的映射 Map<TopicPartition, Long> timestampsToSearch = new HashMap<>(); timestampsToSearch.put(topicPartition, targetTimestamp); // 获取符合时间戳的偏移量 Map<TopicPartition, OffsetAndTimestamp> offsetsForTimes = consumer.offsetsForTimes(timestampsToSearch); // 定位到该偏移量 OffsetAndTimestamp offsetAndTimestamp = offsetsForTimes.get(topicPartition); if (offsetAndTimestamp != null) { consumer.seek(topicPartition, offsetAndTimestamp.offset()); } -
消费者组重置策略:
# 当没有初始偏移量或当前偏移量无效时的策略 auto.offset.reset=earliest # 从最早的消息开始 auto.offset.reset=latest # 从最新的消息开始 auto.offset.reset=none # 抛出异常
分区与副本相关详解
Kafka Partition 副本 leader 是怎么选举的?
当 Leader 副本所在 Broker 发生故障时,会触发 Leader 选举:
-
Controller 检测故障:
- Controller 监控所有 Broker 状态
- 当发现 Leader 所在 Broker 不可用时,触发选举流程
-
从 ISR 中选择新 Leader:
- 优先选择 ISR 集合中的副本
- 通常选择 LEO(Log End Offset)最大的副本
- 可以通过
unclean.leader.election.enable参数控制是否允许非 ISR 副本成为 Leader
-
更新元数据:
- Controller 更新 Zookeeper/KRaft 中的分区 Leader 信息
- 通知所有相关 Broker 新的 Leader 分配
-
通知消费者:
- 消费者通过元数据请求获取新的 Leader 信息
- 重新连接到新的 Leader
分区数越多越好吗?吞吐量就会越高吗?
分区数与吞吐量的关系较为复杂:
正面影响:
- 增加并行度,允许多个消费者同时处理
- 提高磁盘 I/O 利用率
- 支持更高的写入吞吐量
负面影响:
- 增加 Broker 管理开销
- 增加客户端连接数
- 增加内存使用(每个分区需要维护状态)
- 增加 Leader 选举时间
- 过多分区可能导致数据倾斜
- 降低单分区的吞吐量(由于资源竞争)
合理设置分区数:
- 考虑生产者写入能力
- 考虑消费者处理能力
- 考虑集群 Broker 数量
- 考虑磁盘 I/O 能力
- 通常建议每个 Broker 管理 1000-2000 个分区
- 初始设置后可通过分区重分配调整
Kafka 如何保证分布式情况下消息的顺序消费?
Kafka 保证消息顺序性的机制:
-
单分区内的顺序性:
- Kafka 保证消息在单个分区内严格有序
- 生产者发送到同一分区的消息按发送顺序存储
-
跨分区的顺序性:
- 通过将相关消息发送到同一分区保证
- 使用相同 key 的消息会被路由到同一分区
- 消费者组中只有一个消费者处理该分区
-
实现方式:
- 生产者使用按 key 哈希分区策略
- 消费者组中消费者数量不超过分区数
- 每个分区只由一个消费者处理
- 可通过增加分区数提高并发度,但仍受限于分区数量
-
性能权衡:
- 严格顺序消费会降低系统吞吐量
- 分布式系统中强顺序性与高可用性难以兼得
- 实际应用中需根据业务需求平衡顺序性和性能
其他重要机制详解
Kafka 的高可用机制是什么?
Kafka 通过多副本机制实现高可用性:
-
副本分配:
- 每个分区有多个副本(通常 2-3 个)
- 副本分布在不同 Broker 上
- 通过
replication.factor参数控制副本数
-
ISR 机制:
- In-Sync Replicas 集合
- 包含所有与 Leader 保持同步的副本
- 通过
replica.lag.time.max.ms参数定义同步标准 - 只有 ISR 中的副本才有资格成为 Leader
-
Leader 选举:
- Controller 负责 Leader 选举
- 从 ISR 中选择新 Leader
- 确保数据一致性和可用性
-
数据同步:
- Follower 定期从 Leader 拉取数据
- 使用 HW(High Watermark)和 LEO 机制保证数据一致性
- 只有被所有 ISR 副本确认的消息才对消费者可见
-
Broker 故障恢复:
- Controller 检测到 Broker 故障
- 触发相关分区的 Leader 选举
- 新 Leader 继续提供服务
- 故障 Broker 恢复后作为 Follower 重新加入
Kafka 如何减少数据丢失?
从多个层面保证数据不丢失:
-
生产者层面:
acks=all:确保所有 ISR 副本都收到消息- 合理设置
retries和retry.backoff.ms - 启用幂等性和事务
min.insync.replicas > 1:确保至少有指定数量的副本同步
-
Broker 层面:
- 合理设置
unclean.leader.election.enable=false:禁止非 ISR 副本成为 Leader - 配置合理的刷盘策略:
log.flush.interval.messages:多少条消息刷一次盘log.flush.interval.ms:多长时间刷一次盘
- 监控 ISR 状态,及时处理异常
- 合理设置
-
消费者层面:
- 手动提交偏移量
- 先处理消息再提交偏移量
- 处理消息时实现幂等性
- 合理设置
session.timeout.ms和heartbeat.interval.ms
-
集群层面:
- 多副本机制
- 合理的副本分布策略
- 定期进行数据校验
- 监控磁盘使用情况
Kafka 如何不消费重复数据?比如扣款,不能重复扣。
解决重复消费问题的方法:
-
幂等性生产者:
- 启用
enable.idempotence=true - Kafka 自动处理重试导致的重复消息
- 局限:仅在单个会话内有效,无法跨会话保证
- 启用
-
事务机制:
- 设置
transactional.id - 使用
beginTransaction()、send()、commitTransaction()方法 - 保证跨分区、跨主题的原子性操作
- 实现 "恰好一次" 语义
- 设置
-
消费端幂等性设计:
- 为每条消息生成唯一标识(UUID、业务 ID 等)
- 消费前检查消息是否已处理
- 可使用数据库、缓存(Redis)等存储已处理消息的标识
-
状态检查:
- 对于扣款等操作,消费前检查账户状态
- 基于业务状态决定是否执行操作
-
乐观锁:
- 在数据库表中添加版本号字段
- 更新时检查版本号是否匹配
- 不匹配则表示已被处理过
Kafka 与传统 MQ 消息系统之间有哪些关键区别?
Kafka 与传统 MQ(如 RabbitMQ、ActiveMQ)的主要区别:
-
架构设计:
- Kafka:分布式、分区、多副本
- 传统 MQ:中心化或主从架构
-
性能特性:
- Kafka:高吞吐量、低延迟、支持大规模数据
- 传统 MQ:中等吞吐量、较高延迟、适合小规模消息
-
消息持久化:
- Kafka:基于磁盘的持久化存储
- 传统 MQ:通常内存存储,可选持久化
-
顺序性保证:
- Kafka:分区内严格有序,跨分区需特殊处理
- 传统 MQ:全局有序,但可能影响性能
-
扩展性:
- Kafka:水平扩展能力强
- 传统 MQ:扩展相对复杂
-
消息回溯:
- Kafka:支持任意位置回溯消费
- 传统 MQ:通常不支持或有限支持
-
使用场景:
- Kafka:日志收集、实时数据流处理、事件驱动架构
- 传统 MQ:企业集成、异步通信、事务消息
-
消息处理模型:
- Kafka:基于主题的发布 - 订阅模式,支持消费者组
- 传统 MQ:支持队列模式、发布 - 订阅模式
-
消息确认机制:
- Kafka:灵活的 acks 配置
- 传统 MQ:更复杂的事务和确认机制
-
社区生态:
- Kafka:丰富的流处理生态(Kafka Streams、Connect、REST Proxy)
- 传统 MQ:成熟的企业集成工具支持
2131

被折叠的 条评论
为什么被折叠?



