🌊 消息海洋中的导航坐标
Apache Pulsar的消息系统如同一条永不停息的数据河流,每条消息都是这条河流中的独特水滴。消费者需要精确的定位系统来捕捉那些未被处理的消息"水滴"。
📍 Pulsar中的坐标系统
每条消息都有专属的三维坐标,通过以下方式精确定位:
ledgerId:entryId:partitionIdx
🔍 含义详解
🧱 1. ledgerId
这是指当前消息所在的 BookKeeper ledger 文件的 ID。
- Pulsar 使用 BookKeeper 存储消息。
- 每个 ledger 是 BookKeeper 写入的一个文件(或逻辑段)。
- ledgerId 是一个全局唯一的长整型编号,用来标识这个 ledger。
📦 一个 topic 通常由多个 ledger 组成,随着时间滚动、数据分片等原因会不断创建新 ledger。
📄 2. entryId
这是该 ledger 中的具体消息(entry)编号,表示第几条消息。
- 从 0 开始递增。
- 每条消息写入时都会获得一个顺序的 entryId。
坐标解析示例
以387529368:2727:-1为例:
- 387529368 (ledgerId):消息存储的物理账簿编号
- 2727 (entryId):账簿内的消息序列号(从0开始)
- -1 (partitionIdx):非分区主题标识(分区主题显示如0)
🎯 两个关键位置标记
🧭 markDeletePosition 的语义:它表示该订阅已经成功 ack 的最后一条消息的“前一条”位置
{'markDeletePosition': '390694044:218'}
也就是说:
- 订阅实际已确认的最后一条消息是:387529368:2727
- 下一条需要消费的位置是:387529368:2728
Pulsar 是这样处理的:
- markDeletePosition 是“已删除(已确认)的位置”
- 所以实际消费进度是从下一个位置开始的
- readPosition
{'readPosition': '390694044:246'}
📌 两者的区别
指标名 | 含义 |
---|---|
markDeletePosition | 已 ack 的最后一条消息的位置 |
readPosition | 下一条将被推送给消费者的位置,即当前消费进度 |
✅ 示例图示
LedgerID:EntryID
|
v
387529368:2725 ✅ acked
387529368:2726 ✅ acked
387529368:2727 ✅ acked <-- markDeletePosition(最后一条已确认消息)
387529368:2728 📤 已发送,未 ack <-- 已推送但未确认(in-flight 消息)
387529368:2729 ⏳ 待发送 <-- readPosition(下一条将被推送的消息)
说明:
- `markDeletePosition` 表示消费者已确认(ack)消息的最后位置。
- `readPosition` 表示下一条将被推送给消费者的消息位置。
- `markDeletePosition` 与 `readPosition` 之间的消息可能已被发送,但尚未被 ack,处于“in-flight”状态。
🚀 积压数据处理策略
当需要重新处理消息时,选择正确的起始点至关重要:
场景 | 起始位置 | 代码实现 |
---|---|---|
处理所有未确认消息,无论它们是否已被客户端读取过 | markDeletePosition+1 | entry_id += 1 |
处理全新未读消息 | readPosition+1 | 不推荐常规使用 |
正确代码实现:
# ---------- 辅助:计算 markDeletePosition 的下一条 ----------
def next_message_id(pos: str) -> [int, int]:
"""返回 (ledgerId, entryId) -> markDeletePosition 的下一条"""
led, ent = map(int, pos.split(":"))
if led == -1 and ent == -1: # 从未确认过 -> 从 Earliest 开始
return 0, 0
ent = ent + 1
return led, ent
# 从管理接口获取标记位置
led, ent = next_message_id(pos['markDeletePosition'])
# 构建消息ID
msg_id = pulsar.MessageId(ledger_id=led,
entry_id=ent,
partition_idx=part_idx)
💥 常见陷阱与解决方案
陷阱1:参数顺序错位
# 危险写法(易错参数顺序)
msg_id = pulsar.MessageId(ent, led, part_idx) # 反序参数!
# 安全写法(明确参数命名)
msg_id = pulsar.MessageId(
ledger_id=led,
entry_id=ent,
partition_idx=part_idx
)
陷阱2:混淆位置指针
{
"markDeletePosition": "390647951:17508", // 已确认位置
"readPosition": "390647951:17530" // 已读取位置
}
-
✅ 正确起始点:17509(未确认消息起点)
-
❌ 错误起始点:17531(跳过未确认消息)