文章目录
- 前言
- 一、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来确定是异步还是同步,默认是同步。
同步复制的完整流程:
- Producer 会先通过Zookeeper 识别到 Leader
- 然后向 Leader 发送消息
- Leader 收到消息后写入到本地 log 文件
- 这个时候 Follower 再向 LeaderPull 消息
- Pull 回来的消息会写入本地 log中
- 写入完成后会向 Leader发送 Ack 回执
- 等到 Leader收到所有 Follower 的回执之后,才会向 Producer 回传 Ack
异步复制
Kafka 中 Producer 异步发送消息是基于同步发送消息的接口来实现的,实现方式如下:
- 客户端消息发送过来以后,会先放入一个BlockingQueue 队列中然后就返回了
- Producer 再开启一个线程ProducerSendThread不断从队列中取出消息
- 然后调用同步发送消息的接口将消息发送给 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规则限制只能访问自己的主题,实现多租户数据隔离。
- 服务端开启认证授权(server.properties 核心配置)
# 开启SASL端口 listeners=SASL_PLAINTEXT://:9092 # 启用SCRAM账号密码认证 sasl.enabled.mechanisms=SCRAM-SHA-256 # 开启ACL授权器 authorizer.class.name=org.apache.kafka.metadata.authorizer.StandardAuthorizer- 创建租户账号
# 创建租户用户 tenant-a,设置密码kafka-configs.sh --bootstrap-server127.0.0.1:9092\--alter--entity-typeusers--entity-name tenant-a\--add-config SCRAM-SHA-256=[password=123456]- 配置ACL权限(核心:前缀匹配隔离)
# 允许租户 tenant-a 读写前缀为 tenant-a- 的所有Topickafka-acls.sh --bootstrap-server127.0.0.1:9092--add\--allow-principal User:tenant-a\--operationRead--operationWrite--operationDescribe\--resource-pattern-type prefixed\--topictenant-a-- 客户端配置指定账号(生产者/消费者)
security.protocol=SASL_PLAINTEXT sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \ username="tenant-a" password="123456"; sasl.mechanism=SCRAM-SHA-2562. 配额(Quota)机制
Kafka 也有对配额的操作支持。管理员可以对请求定义和强制配额,以控制客户端使用的 Broker 资源。
配额可以从以下维度进行限制:
| 配额维度 | 说明 |
|---|---|
| 生产速率 | 限制某个客户端每秒可以发送的数据量(bytes/sec) |
| 消费速率 | 限制某个客户端每秒可以拉取的数据量(bytes/sec) |
| 请求处理时间占比 | 限制某个客户端请求占用 Broker 处理能力的百分比 |
通过配额机制,即使某个租户的流量突然暴增,也不会把整个集群的资源吃光,其他租户的正常使用不受影响。
总结
- 主从同步:ISR 机制动态维护同步副本集合,同步复制等所有 Follower 确认,异步复制先入队再批量发送,各有利弊
- 消息丢失:生产端因 acks 配置不当丢失(acks=0 网络异常丢、acks=1 Leader 宕机丢),消费端因先提交 offset 后处理导致丢失
- 流处理平台:轻量级 Java 类库、零外部依赖、本地状态容错、exactly-once 语义、ms 级延迟,五大特性使 Kafka 不只是消息队列
- 活锁问题:通过
max.poll.interval.ms检测"活着但不干活"的消费者,超时自动踢出消费组 - 顺序消费:Partition 内有序但跨 Partition 无序,通过指定 MessageKey 将同类消息路由到同一 Partition
- 多租户隔离:通过主题级访问控制和配额机制,实现多业务共享集群且互不影响
这六个机制构成了 Kafka 可靠性和隔离性的完整图景。从副本同步保证数据不丢,到 ACK 和 offset 管理堵住丢消息的口子,再到活锁检测和顺序消费保证消费端的正确性,最后通过多租户隔离让多个业务安全共存。