Apache Pulsar 深度源码级架构解析
—— 千亿级消息引擎的底层实现艺术
一、分层存储架构:Segment 分片与冷热分离
设计思想
基于 LSM-Tree 变体 + 分片策略,实现数据生命周期管理:
Hot Data (BookKeeper) → Warm Data (SSD Cache) → Cold Data (Object Storage)
源码实现(行级剖析)
关键类:org.apache.pulsar.broker.service.persistent.PersistentTopic
// 创建 Segment 分片 (行号: 842)
public void createNewSegmentIfNeeded() {
if (currentSegment.isFull()) {
// 当前 Segment 达到阈值
LedgerHandle newLedger = bookkeeper.createLedger(
ensembleSize, writeQuorumSize, ackQuorumSize, // 副本策略
DigestType.CRC32, passphrase);
currentSegment.close(); // 关闭旧 Segment (只读)
currentSegment = new Segment(newLedger); // 激活新 Segment
offloadOldSegments(); // 触发冷数据下沉 (行号 901)
}
}
冷数据下沉流程:
graph TB
A[Segment Full] --> B{是否达到冷数据阈值?}
B -->|Yes| C[标记 Segment 为只读]
C --> D[启动 Offloader 线程]
D --> E[上传数据到 S3/HDFS]
E --> F[更新 ZK 元数据]
F --> G[删除本地副本]
参数调优
参数 | 默认值 | 优化建议 |
---|---|---|
managedLedgerMaxSizePerSegment |
128MB | 根据消息大小调整(小消息设小值) |
offloadThresholdInBytes |
-1 (禁用) | 设为热存储容量的 80% |
高深扩展:冷热分离一致性协议
采用 Versioned CAS(Compare-and-Swap) 保证数据一致性:
// OffloaderScheduler.java (行号 215)
long expectedVersion = zkStore.getDataVersion();
if (zkStore.compareAndSetOffloadPosition(segmentId, expectedVersion, newVersion)) {
uploadSegment(segment); // 原子性提交
}
速记口诀:
“分片大小阈值控,副本策略三参数;
冷热下沉原子锁,ZK 版本保一致”
源码路径:
PersistentTopic.java
参考文献:
VLDB’17: BookKeeper’s Segment-based Storage
二、写入流程:多级流水线优化
核心设计
三阶段管道化处理:
- Producer 批处理:内存合并小消息
- Broker 转发:异步并行写 Bookies
- Bookie 持久化:Journal + Ledger 双写
源码级流水线剖析
// ProducerImpl.java (行号 687)
public void sendAsync(Message message) {
batcher.add(message); // 1. 批量聚合 (行号 705)
}
// PersistentTopic.java (行号 1124)
void publishMessage(ByteBuf data) {
managedLedger.asyncAddEntry(data, new AddEntryCallback() {
public void addComplete() {
// 2. 并行写 Bookies (行号 1188)
for (Bookie bookie : ensemble) {
bookie.asyncAddEntry(ledgerId, entryData);
}
}
});
}
// Bookie.java (行号 1523)
void asyncAddEntry(long ledgerId, byte[] data) {
journal.write(data); // 3.1 写 Journal (同步刷盘)
entryLogger.addEntry(ledgerId, data); // 3.2 写 Ledger (异步刷盘)
}