RocketMQ 与 Kafka 长轮询详解
一、背景
Consumer 消费消息有两种基本模式:
| 模式 | 说明 | 问题 |
|---|---|---|
| Push | Broker 主动推送消息给 Consumer | Consumer 可能来不及处理,造成积压 |
| Pull | Consumer 主动向 Broker 拉取消息 | 频繁拉取浪费资源;消息到达慢导致延迟 |
长轮询 是对 Pull 模式的优化:Consumer 发起请求后,若暂无消息,Broker 不立即返回,而是挂起请求,等到有新消息或超时后再响应。既避免了频繁空轮询,又保证了消息的实时性。
二、RocketMQ 长轮询
2.1 整体机制
RocketMQ 本质是 Pull 模式,通过长轮询模拟 Push 的实时感。
Consumer Broker
│ │
│ Pull 请求(带超时时间) │
│ ─────────────────────────> │
│ │ 有消息?
│ ├── YES → 立即返回消息
│ │
│ └── NO → 挂起请求
│ 进入 PullRequestHoldService
│ 等待新消息到达 or 超时
│ │
│ 新消息写入 CommitLog
│ │
│ <───────────────────────── 唤醒挂起请求,立即返回消息
│ │
│ 立即发起下一次 Pull │
│ ─────────────────────────> │
2.2 核心组件
PullMessageProcessor # 处理 Pull 请求的入口
↓(无消息时)
PullRequestHoldService # 管理挂起请求的后台线程
├── 每 5 秒定时轮询(短轮询兜底,防止消息遗漏)
└── 被 ReputMessageService 实时唤醒
ReputMessageService # 监听 CommitLog 写入
└── 新消息写入后调用 notifyMessageArriving()
└── 唤醒对应 Topic/Queue 的挂起请求
2.3 挂起超时时间
| 配置 | 挂起时长 | 说明 |
|---|---|---|
longPollingEnable=true(默认) |
30 秒 | 长轮询,实时性强 |
longPollingEnable=false |
1 秒 | 短轮询,兜底方案 |
2.4 唤醒时机
- 实时唤醒 :新消息写入 CommitLog →
ReputMessageService→notifyMessageArriving()→ 立即唤醒 - 超时唤醒 :
PullRequestHoldService定时线程每 5 秒扫描一次,超过挂起时长则强制返回
2.5 关键特点
- 消息级别触发:一条新消息写入即可唤醒,延迟极低(毫秒级)
- 设计目标:低延迟,尽快将消息送达 Consumer
- 适合场景:对消息实时性要求高的业务(如订单、支付通知)
三、Kafka 长轮询
3.1 整体机制
Kafka Consumer 调用 poll(timeout) 发起 Fetch 请求,Broker 端通过 DelayedFetch 实现挂起等待。
Consumer Kafka Broker
│ │
│ Fetch 请求 │
│ fetch.min.bytes=1MB │
│ fetch.max.wait.ms=500ms │
│ ─────────────────────────> │
│ │ 数据量 >= fetch.min.bytes?
│ ├── YES → 立即返回
│ │
│ └── NO → 创建 DelayedFetch
│ 放入 DelayedOperationPurgatory
│ 等待触发条件:
│ ① 数据量 >= fetch.min.bytes
│ ② 等待时间 >= fetch.max.wait.ms
│ 满足其一则返回
│ <───────────────────────── 返回数据(可能是空或少量消息)
3.2 核心组件
KafkaApis # 处理 Fetch 请求入口
↓(数据不足时)
DelayedFetch # 延迟 Fetch 操作对象
└── 放入 DelayedOperationPurgatory(炼狱)
├── 新消息写入 Partition → tryComplete() → 检查是否满足条件
└── fetch.max.wait.ms 超时 → onExpiration() → 强制返回
DelayedOperationPurgatory(炼狱) 是 Kafka 管理所有延迟操作的核心组件,长轮询、延迟生产(acks=-1)等都由它统一管理。
3.3 核心配置参数
Consumer 端:
properties
# 每次 Fetch 最少返回的数据量(默认 1 字节,即有消息就返回)
fetch.min.bytes=1
# Broker 最长等待时间(默认 500ms)
fetch.max.wait.ms=500
# 每次 poll 最多拉取的消息条数
max.poll.records=500
# Consumer 两次 poll 的最长间隔(超过则认为 Consumer 宕机)
max.poll.interval.ms=300000
Broker 端(副本同步):
properties
# 副本 Fetch 最小字节数
replica.fetch.min.bytes=1
# 副本 Fetch 最长等待时间
replica.fetch.wait.max.ms=500
3.4 批量优化场景
通过调大 fetch.min.bytes,可以实现攒批效果:
properties
# 至少积累 1MB 数据再返回,减少网络交互次数,提升吞吐量
fetch.min.bytes=1048576
fetch.max.wait.ms=1000
3.5 关键特点
- 数据量阈值控制 :通过
fetch.min.bytes攒批,减少网络交互 - 设计目标:高吞吐,批量拉取摊薄网络开销
- 适合场景:日志采集、大数据流处理等吞吐量优先的业务
四、两者对比
| 维度 | RocketMQ | Kafka |
|---|---|---|
| 消费模型 | Pull 模拟 Push | 纯 Pull |
| 挂起组件 | PullRequestHoldService |
DelayedOperationPurgatory |
| 唤醒粒度 | 消息级别(一条消息即唤醒) | 数据量阈值(fetch.min.bytes) |
| 默认等待超时 | 30 秒 | 500ms |
| 最小数据量控制 | 无 | fetch.min.bytes(可配置) |
| 实时性 | 极高(毫秒级) | 受 fetch.max.wait.ms 影响 |
| 吞吐量 | 中等 | 高(批量设计) |
| 设计目标 | 低延迟 | 高吞吐 |
| 适用场景 | 订单、支付、实时通知 | 日志、埋点、大数据流 |
五、关键区别深度分析
5.1 触发粒度不同
RocketMQ:消息级别触发
一条消息写入 CommitLog → 立即唤醒挂起的 Consumer
优势:延迟极低
劣势:高消息频率下,唤醒过于频繁
Kafka:数据量阈值触发
积累 fetch.min.bytes 数据 → 才返回给 Consumer
优势:批量传输,网络效率高
劣势:低流量时可能等到 fetch.max.wait.ms 超时才返回
5.2 设计目标不同
RocketMQ 长轮询 → 追求低延迟
消息产生 → 毫秒内送达 Consumer
Kafka 长轮询 → 追求高吞吐
通过 fetch.min.bytes 攒批 → 单次网络传输携带更多数据
5.3 超时策略不同
RocketMQ:30s 长超时
维持长连接,减少重连开销
超时后 Consumer 立即重新发起 Pull
Kafka:500ms 短超时(默认)
快速返回空结果,由 Consumer poll() 循环控制节奏
可调大以减少空轮询次数
六、总结
| RocketMQ | Kafka | |
|---|---|---|
| 一句话概括 | 用长轮询模拟 Push,追求实时 | 用长轮询攒批拉取,追求吞吐 |
| 核心参数 | longPollingEnable、挂起 30s |
fetch.min.bytes、fetch.max.wait.ms |
| 选型建议 | 业务消息、实时性优先 | 数据流、吞吐量优先 |