【西瓜带你学Kafka | 第八期】 Kafka的主从同步、消息可靠性、流处理与顺序消费(文含图解)

文章目录

    • 前言
    • 一、Kafka 中如何进行主从同步
      • ISR 机制
      • 同步复制
      • 异步复制
      • 异步复制的利与弊
    • 二、Kafka 中什么情况下会出现消息丢失/不一致的问题
      • 消息发送时丢失
      • 消息消费时丢失
    • 三、Kafka 作为流处理平台的特点
      • 什么是流处理
      • Kafka 作为流处理平台的五大特性
      • 与其他流处理框架的定位差异
    • 四、消费者故障,出现活锁问题如何解决
      • 什么是活锁
      • 解决方案
      • 相关参数调优建议
    • 五、Kafka 中如何保证顺序消费
      • 顺序性的边界
      • 解决方案:指定 MessageKey
      • 需要注意的问题
    • 六、Kafka 是否支持多租户隔离
      • 什么是多租户
      • Kafka 的多租户方案
    • 总结

前言

用 Kafka 做基础消息收发并不难,但真正上生产之后,一连串棘手的问题就来了:消息怎么就丢了?消费者明明活着却不干活?多个业务共用一个集群怎么互不影响?消息顺序怎么就乱了?

这些问题的答案散落在 Kafka 的各个机制里------ISR 同步决定了副本一致性,ACK 配置决定了丢消息的概率,活锁检测决定了消费者的健康判定,分区模型决定了顺序性的边界。

本期西瓜带你学Kafka把这六个机制串起来,从同步复制讲到消息丢失,从流处理特性讲到顺序消费,帮助建立一个完整的 Kafka 可靠性认知框架。


一、Kafka 中如何进行主从同步

主从同步是 Kafka 高可用的基石。理解了这个机制,后面讲消息丢失时就能准确判断"丢在了哪个环节"。

ISR 机制

Kafka 动态维护了一个同步状态的副本的集合(a set of In-Sync Replicas),简称 ISR。在这个集合中的节点都是和 Leader 保持高度一致的。

核心规则:任何一条消息只有被 ISR 集合中的每个节点读取并追加到日志中,才会向外部通知"这个消息已经被提交"。

ISR 不是固定的,它是动态维护的------如果某个 Follower 落后太多,会被踢出 ISR;追上来之后又会被重新加入。

同步复制

Kafka 通过配置 producer.type 来确定是异步还是同步,默认是同步。

同步复制的完整流程:

  1. Producer 会先通过 Zookeeper 识别到 Leader
  2. 然后向 Leader 发送消息
  3. Leader 收到消息后写入到本地 log 文件
  4. 这个时候 Follower 再向 Leader Pull 消息
  5. Pull 回来的消息会写入本地 log 中
  6. 写入完成后会向 Leader 发送 Ack 回执
  7. 等到 Leader 收到所有 Follower 的回执之后,才会向 Producer 回传 Ack

异步复制

Kafka 中 Producer 异步发送消息是基于同步发送消息的接口来实现的,实现方式如下:

  1. 客户端消息发送过来以后,会先放入一个 BlockingQueue 队列中然后就返回了
  2. Producer 再开启一个线程 ProducerSendThread 不断从队列中取出消息
  3. 然后调用同步发送消息的接口将消息发送给 Broker

异步复制的利与弊

优势 :Producer 在内存缓存消息,当累计达到阈值时批量发送请求。小数据 I/O 太多会拖慢整体的网络延迟,批量延迟发送事实上提升了网络效率。

风险 :如果在达到阈值前,Producer 不可用了,缓存的数据将会丢失。


二、Kafka 中什么情况下会出现消息丢失/不一致的问题

理解了主从同步机制之后,消息丢失的场景就很容易推导出来了。丢失可能发生在两个阶段:生产端 和消费端。

消息发送时丢失

消息发送有两种方式:同步(sync) 和 异步(async) 。默认是同步的方式,可以通过 producer.type 属性进行配置。

Kafka 也可以通过配置 acks 属性来确认消息的生产:

acks 值 含义 丢失风险
0 不进行消息接收是否成功的确认 网络异常、缓冲区满即丢失
1 当 Leader 接收成功时确认 Leader 宕机且 Follower 未同步时丢失
-1 Leader 和 Follower 都接收成功时确认 几乎不丢失

acks=0 的丢失场景

不和 Kafka 进行消息接收确认,可能会因为网络异常、缓冲区满的问题,导致消息丢失。Producer 以为发成功了,实际上 Broker 根本没收到。

acks=1 的丢失场景

只有 Leader 同步成功而 Follower 尚未完成同步,如果 Leader 挂了,就会造成数据丢失。新选举出来的 Leader(原来的某个 Follower)并没有这条消息。

消息消费时丢失

Kafka 有两个消息消费的 Consumer 接口:

1. low-level 接口

消费者自己维护 offset 等值,可以实现对 Kafka 的完全控制。由于 offset 完全由消费者管理,可以精确控制消费进度,不容易丢消息。

2. high-level 接口

封装了对 partition 和 offset 的管理,使用简单。

high-level 接口的丢失场景

如果使用高级接口,可能存在这样的情况:一个消费者提取了一个消息后便提交了 offset ,那么还没来得及消费就已经挂了。下次消费时的数据就是 offset+1 的位置,那么原先 offset 的数据就丢失了。

复制代码
时间线:
1. Consumer 拉取 offset=100 的消息
2. Consumer 提交 offset=100(标记为已消费)  ← 先提交了
3. Consumer 开始处理消息...
4. Consumer 宕机!消息还没处理完           ← 但还没消费完
5. Consumer 重启,从 offset=101 开始消费    ← offset=100 的消息丢了

解决思路:先消费再提交 offset,或者使用手动提交 offset 的方式,确保消息处理完成后再提交。


三、Kafka 作为流处理平台的特点

前面讲的都是 Kafka 作为"消息队列"的机制,但 Kafka 的定位远不止于此------它是一个分布式流处理平台。

什么是流处理

流处理就是连续、实时、并发和以逐条记录的方式处理数据的意思。与批处理不同,流处理强调的是数据到达即处理,而不是攒一批再处理。

Kafka 作为流处理平台的五大特性

Kafka 的高吞吐量、低延时、高可靠性、容错性、高可扩展性都使得 Kafka 非常适合作为流式平台。具体来说:

1. 轻量级 Java 类库

它是一个简单的、轻量级的 Java 类库,能够被集成到任何 Java 应用中。不需要独立部署流处理集群,直接在应用内引入即可。

2. 零外部依赖

除了 Kafka 之外没有任何其他的依赖。利用 Kafka 的分区模型支持水平扩容 和保证顺序性。不像 Spark Streaming 或 Flink 需要额外的计算集群。

3. 本地状态容错

支持本地状态容错,可以执行非常快速有效的有状态操作。比如窗口聚合、Join 等操作的中间状态可以存储在本地,故障时通过 changelog topic 恢复。

4. Exactly-Once 语义

支持 exactly-once 语义,保证每条消息恰好被处理一次,不多不少。这对金融、交易等场景至关重要。

5. 毫秒级延迟

支持一次处理一条记录,实现 ms 级的延迟。真正的逐条处理,而不是微批(micro-batch)。

与其他流处理框架的定位差异

特性 Kafka Streams Spark Streaming Flink
部署方式 嵌入应用 独立集群 独立集群
外部依赖 仅 Kafka Spark + 存储 Flink 集群
处理模型 逐条记录 微批 逐条记录
延迟 ms 级 秒级 ms 级
状态管理 本地 + changelog 外部存储 本地 + checkpoint

四、消费者故障,出现活锁问题如何解决

什么是活锁

活锁和死锁不同。死锁是线程卡住不动了,而活锁是:消费者持续地维持心跳,但没有进行消息处理。

从 Broker 的角度看,这个 Consumer 是"活着的"(心跳正常),所以不会触发 Rebalance 把分区分配给其他 Consumer。但实际上这个 Consumer 什么都没干,它持有的分区就这样被"空占"了。

解决方案

为了预防消费者在这种情况下一直持有分区,通常会利用 max.poll.interval.ms 活跃检测机制。

工作原理:

  • Kafka 不仅检查心跳,还检查 Consumer 调用 poll() 的频率

  • 如果调用 poll 的频率大于最大间隔(即两次 poll 之间的时间超过了 max.poll.interval.ms),那么消费者将会主动离开消费组

  • 分区会被重新分配给其他消费者接管

    正常情况:
    poll() → 处理消息 → poll() → 处理消息 → poll()
    |<--- 间隔在 max.poll.interval.ms 内 --->|

    活锁情况:
    poll() → 心跳正常但消息处理卡住/不处理 → 超过 max.poll.interval.ms
    |<--- 超时!Consumer 被踢出消费组 --->|

相关参数调优建议

参数 作用 建议
max.poll.interval.ms 两次 poll 的最大间隔 根据消息处理耗时设置,默认 300000ms(5 分钟)
max.poll.records 单次 poll 返回的最大消息数 如果处理慢,可以减小这个值,确保在间隔内处理完
session.timeout.ms 心跳超时时间 检测 Consumer 是否真正宕机

max.poll.interval.ms 和 session.timeout.ms 的区别:前者检测"活着但不干活"(活锁),后者检测"真的挂了"(宕机)。


五、Kafka 中如何保证顺序消费

顺序性的边界

Kafka 的消费单元是 Partition 。同一个 Partition 使用 offset 作为唯一标识保证顺序性。

但这里有一个关键限制:这只是保证了在 Partition 内部的顺序性,而不是 Topic 中的顺序。

一个 Topic 通常有多个 Partition,消息被分散到不同 Partition 后,跨 Partition 之间是没有顺序保证的。

解决方案:指定 MessageKey

如果需要保证某一类消息的顺序消费,可以在发送的时候指定 MessageKey。同一个 Key 的消息会发到同一个 Partition 中。

复制代码
Topic: order-topic (3 个 Partition)

消息: orderId=1001, action=创建  → Key=1001 → Partition-1
消息: orderId=1001, action=支付  → Key=1001 → Partition-1  顺序保证
消息: orderId=1001, action=发货  → Key=1001 → Partition-1  顺序保证

消息: orderId=1002, action=创建  → Key=1002 → Partition-2
消息: orderId=1002, action=支付  → Key=1002 → Partition-2  顺序保证

同一个订单的所有操作都落在同一个 Partition,消费时自然就是有序的。

需要注意的问题

1. 全局顺序 vs 分区顺序

如果业务要求全局严格有序 (所有消息按发送顺序消费),那只能设置 Topic 只有 1 个 Partition。但这会牺牲并行度和吞吐量。

大多数场景其实只需要同一业务实体内有序(比如同一个订单、同一个用户),用 MessageKey 就够了。

2. Key 的选择要均匀

如果所有消息的 Key 都一样,那所有消息都会落到同一个 Partition,等于退化成了单分区,失去了并行消费的优势。Key 的选择应该保证消息能均匀分布到各个 Partition。


六、Kafka 是否支持多租户隔离

什么是多租户

多租户技术(multi-tenancy technology)是一种软件架构技术,它是实现如何在多用户的环境下共用相同的系统或程序组件 ,并且仍可确保各用户间数据的隔离性。

简单来说:多个业务团队共用一个 Kafka 集群,但彼此之间互不影响、数据互不可见。

Kafka 的多租户方案

Kafka 支持多租户,解决方案主要通过两个层面实现:

1. 主题级别的访问控制

通过配置哪个主题可以生产或消费数据来启用多租户。不同租户只能访问自己被授权的 Topic,无法读写其他租户的数据。

如何实现

依靠 SASL 做身份认证 + ACL 做资源授权 ,给每个租户独立账号,通过Topic前缀+ACL规则限制只能访问自己的主题,实现多租户数据隔离。

    1. 服务端开启认证授权(server.properties 核心配置)
properties 复制代码
# 开启SASL端口
listeners=SASL_PLAINTEXT://:9092
# 启用SCRAM账号密码认证
sasl.enabled.mechanisms=SCRAM-SHA-256
# 开启ACL授权器
authorizer.class.name=org.apache.kafka.metadata.authorizer.StandardAuthorizer
    1. 创建租户账号
bash 复制代码
# 创建租户用户 tenant-a,设置密码
kafka-configs.sh --bootstrap-server 127.0.0.1:9092 \
--alter --entity-type users --entity-name tenant-a \
--add-config SCRAM-SHA-256=[password=123456]
    1. 配置ACL权限(核心:前缀匹配隔离)
bash 复制代码
# 允许租户 tenant-a 读写前缀为 tenant-a- 的所有Topic
kafka-acls.sh --bootstrap-server 127.0.0.1:9092 --add \
--allow-principal User:tenant-a \
--operation Read --operation Write --operation Describe \
--resource-pattern-type prefixed \
--topic tenant-a-
    1. 客户端配置指定账号(生产者/消费者)
properties 复制代码
security.protocol=SASL_PLAINTEXT
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
username="tenant-a" password="123456";
sasl.mechanism=SCRAM-SHA-256

2. 配额(Quota)机制

Kafka 也有对配额的操作支持。管理员可以对请求定义和强制配额,以控制客户端使用的 Broker 资源。

配额可以从以下维度进行限制:

配额维度 说明
生产速率 限制某个客户端每秒可以发送的数据量(bytes/sec)
消费速率 限制某个客户端每秒可以拉取的数据量(bytes/sec)
请求处理时间占比 限制某个客户端请求占用 Broker 处理能力的百分比

通过配额机制,即使某个租户的流量突然暴增,也不会把整个集群的资源吃光,其他租户的正常使用不受影响。


总结

  1. 主从同步:ISR 机制动态维护同步副本集合,同步复制等所有 Follower 确认,异步复制先入队再批量发送,各有利弊
  2. 消息丢失:生产端因 acks 配置不当丢失(acks=0 网络异常丢、acks=1 Leader 宕机丢),消费端因先提交 offset 后处理导致丢失
  3. 流处理平台:轻量级 Java 类库、零外部依赖、本地状态容错、exactly-once 语义、ms 级延迟,五大特性使 Kafka 不只是消息队列
  4. 活锁问题 :通过 max.poll.interval.ms 检测"活着但不干活"的消费者,超时自动踢出消费组
  5. 顺序消费:Partition 内有序但跨 Partition 无序,通过指定 MessageKey 将同类消息路由到同一 Partition
  6. 多租户隔离:通过主题级访问控制和配额机制,实现多业务共享集群且互不影响

这六个机制构成了 Kafka 可靠性和隔离性的完整图景。从副本同步保证数据不丢,到 ACK 和 offset 管理堵住丢消息的口子,再到活锁检测和顺序消费保证消费端的正确性,最后通过多租户隔离让多个业务安全共存。

相关推荐
Han.miracle2 分钟前
MySQL InnoDB:MVCC、记录锁、间隙锁、二级索引MVCC底层原理
java·开发语言
不停喝水28 分钟前
【前端转全栈java速通课】 项目实战④7-11节 操作数据库-登录-注册-修改密码-注销-mubatis-plus简化crud-
java·前端·数据库
y1su33 分钟前
Leetcode 二分模板
java·数据结构·算法·leetcode·排序算法
霸道流氓气质1 小时前
多Agent通信机制与协议设计完全指南:从FIPA-ACL到A2A/MCP的Java生产级实战
java·开发语言
007张三丰1 小时前
C/C++ 内存管理详解:从内存分布到 new/delete 底层原理
java·c语言·c++·内存管理
骇客野人1 小时前
Java BIO / NIO / AIO 完整详解 + 编程技巧
java·开发语言·nio
霸道流氓气质1 小时前
OpenTelemetry 入门与实战:Java Agent、Spring Boot Starter与LLM调用追踪示例
java·开发语言·spring boot
m0_587383001 小时前
24 小时自助健身房系统软件开发实战指南与案例解析
java·spring boot·spring·系统架构·需求分析
栖凤2 小时前
Java 的 try-catch 在 Agent 里失效了,我用了 5 个模式才兜住
java·前端·python