Druid Kafka 数据源消费到 Segment 生成全链路深度分析
一、概述
本文档基于 Apache Druid 源码,系统梳理了 Kafka 数据源从消费到生成 Segment 的完整流程。整个流程涉及 5 个层级,从 Supervisor 管理层到底层存储层,形成完整的数据处理链路。
核心类总览
| 层级 |
核心类 |
职责 |
| Supervisor 管理层 |
KafkaSupervisor / SeekableStreamSupervisor |
管理 Task 生命周期,分配分区 |
| Task 执行层 |
KafkaIndexTask / SeekableStreamIndexTask |
创建 Runner、RecordSupplier、Appenderator |
| Runner 消费层 |
SeekableStreamIndexTaskRunner / IncrementalPublishingKafkaIndexTaskRunner |
消费主循环、增量发布 |
| 消费者层 |
KafkaRecordSupplier |
封装 KafkaConsumer,提供 poll/seek/assign |
| Driver 层 |
StreamAppenderatorDriver / BaseAppenderatorDriver |
管理 Segment 分配/发布/Handoff |
| Appenderator 层 |
StreamAppenderator |
add/persistAll/push/mergeAndPush |
| Segment 生成层 |
IndexMergerV9 |
V9 格式 Segment 合并/持久化 |
二、整体架构流程图
scss
复制代码
用户提交 Supervisor Spec
│
▼
KafkaSupervisor 启动
│
▼
定时 runInternal 循环
│
├── updatePartitionDataFromStream() → 获取 Kafka Topic 分区信息
├── discoverTasks() → 发现已有任务
├── createNewTasks() → 创建新任务
│ │
│ ▼
│ createTasksForGroup() → KafkaIndexTask 提交到 TaskQueue
│
▼
KafkaIndexTask.run()
│
▼
IncrementalPublishingKafkaIndexTaskRunner.runInternal()
│
├── 创建 KafkaRecordSupplier (封装 KafkaConsumer)
├── 创建 StreamAppenderator
├── 创建 StreamAppenderatorDriver
├── driver.startJob() 恢复元数据
│
▼
主消费循环 (while stillReading)
│
├── recordSupplier.poll() → 从 Kafka 拉取消息
├── StreamChunkParser.parse() → 解析为 InputRow
├── driver.add() → 写入 Appenderator
│ │
│ ├── Sink.add() → IncrementalIndex.add()
│ │
│ ├── 触发持久化? → driver.persistAsync()
│ │ → IndexMergerV9.persist()
│ │
│ └── 达到 maxRowsPerSegment? → checkpoint + 创建新 Segment
│
├── maybePersistAndPublishSequences() → 增量发布已完成的 Sequence
│
▼
消费循环结束
│
├── driver.persist() 最终持久化
│
├── publishAndRegisterHandoff()
│ │
│ ├── driver.publish()
│ │ ├── pushInBackground() → mergeAndPush() → Deep Storage
│ │ └── publishInBackground() → 发布元数据到 MetaStore
│ │
│ └── registerHandoff() → 等待 Historical 加载
│
▼
TaskStatus.SUCCESS
三、第一层:KafkaSupervisor 管理层
3.1 核心类与文件
| 类 |
文件路径 |
KafkaSupervisor |
extensions-core/kafka-indexing-service/src/main/java/org/apache/druid/indexing/kafka/supervisor/KafkaSupervisor.java |
SeekableStreamSupervisor |
indexing-service/src/main/java/org/apache/druid/indexing/seekablestream/supervisor/SeekableStreamSupervisor.java |
3.2 流程说明
- 启动 :用户通过 REST API 提交
KafkaSupervisorSpec,Overlord 创建 KafkaSupervisor 实例并调用 start()
- 定时调度 :
start() 中注册定时任务 buildRunTask(),按 ioConfig.period 周期性执行 runInternal()
runInternal() 核心循环:
scss
复制代码
updatePartitionDataFromStream() → 获取 Kafka Topic 的分区列表
discoverTasks() → 发现已运行的 KafkaIndexTask
updateTaskStatus() → 更新任务状态
checkTaskDuration() → 检查任务是否超过 taskDuration
checkPendingCompletionTasks() → 检查待完成的任务
checkCurrentTaskState() → 检查当前任务状态
createNewTasks() → 创建缺失的任务
-
创建任务 :createTasksForGroup() → KafkaSupervisor.createIndexTasks()
- 根据
replicas 配置创建多个 KafkaIndexTask 实例
- 每个 Task 包含
DataSchema、TuningConfig、IOConfig(含起止 offset)
- 通过
taskQueue.add(indexTask) 提交到 Overlord 的任务队列
-
分区分配策略 :getTaskGroupIdForPartition(partitionId) = partitionId % taskCount
- 每个 TaskGroup 负责一组 Kafka 分区
- 每个 TaskGroup 有
replicas 个副本任务
四、第二层:KafkaIndexTask 执行层
4.1 核心类与文件
| 类 |
文件路径 |
KafkaIndexTask |
extensions-core/kafka-indexing-service/src/main/java/org/apache/druid/indexing/kafka/KafkaIndexTask.java |
SeekableStreamIndexTask |
indexing-service/src/main/java/org/apache/druid/indexing/seekablestream/SeekableStreamIndexTask.java |
IncrementalPublishingKafkaIndexTaskRunner |
extensions-core/kafka-indexing-service/src/main/java/org/apache/druid/indexing/kafka/IncrementalPublishingKafkaIndexTaskRunner.java |
KafkaRecordSupplier |
extensions-core/kafka-indexing-service/src/main/java/org/apache/druid/indexing/kafka/KafkaRecordSupplier.java |
4.2 调用链
scss
复制代码
KafkaIndexTask.run(toolbox)
→ getRunner().run(toolbox) // SeekableStreamIndexTask.run()
→ IncrementalPublishingKafkaIndexTaskRunner.run(toolbox)
→ runInternal(toolbox) // SeekableStreamIndexTaskRunner.runInternal()
4.3 runInternal() 初始化阶段
scss
复制代码
// 1. 创建 Kafka 消费者
RecordSupplier recordSupplier = task.newTaskRecordSupplier();
// → KafkaIndexTask.newTaskRecordSupplier()
// → new KafkaRecordSupplier(consumerProperties, configMapper)
// → 内部创建 KafkaConsumer<byte[], byte[]>,设置 auto.offset.reset=none
// 2. 创建 Appenderator(实时模式)
appenderator = task.newAppenderator(toolbox, metrics, rowIngestionMeters, parseExceptionHandler);
// → toolbox.getAppenderatorsManager().createRealtimeAppenderatorForTask(...)
// → 创建 StreamAppenderator 实例
// 3. 创建 StreamAppenderatorDriver
driver = task.newDriver(appenderator, toolbox, metrics);
// → new StreamAppenderatorDriver(appenderator, segmentAllocator, handoffNotifier, ...)
// → segmentAllocator 使用 ActionBasedSegmentAllocator(通过 Overlord 分配 Segment ID)
// 4. 启动 Job,恢复元数据
Object restoredMetadata = driver.startJob(lockHelper);
// → 恢复之前持久化的 commit 元数据(如果有)
// → 恢复 currOffsets(当前消费位置)
4.4 关键组件创建关系
scss
复制代码
KafkaIndexTask
├── KafkaRecordSupplier (封装 KafkaConsumer)
├── StreamAppenderator (管理 Sink/FireHydrant/IncrementalIndex)
└── StreamAppenderatorDriver (管理 Segment 分配/发布/Handoff)
├── ActionBasedSegmentAllocator (通过 Overlord 分配 SegmentId)
├── SegmentHandoffNotifier (监听 Historical 加载完成)
└── ActionBasedUsedSegmentChecker (检查已使用的 Segment)
五、第三层:消费循环层
5.1 核心代码位置
SeekableStreamIndexTaskRunner.java 的 runInternal() 方法
5.2 主循环流程
scss
复制代码
while (stillReading) {
// 1. 检查暂停请求
if (possiblyPause()) { ... }
// 2. 检查停止请求
if (stopRequested.get()) break;
// 3. 检查并发布已完成的 Sequence
maybePersistAndPublishSequences(committerSupplier);
// 4. 从 Kafka 拉取消息
List<OrderedPartitionableRecord> records = getRecords(recordSupplier, toolbox);
// → IncrementalPublishingKafkaIndexTaskRunner.getRecords()
// → recordSupplier.poll(pollTimeout)
// → KafkaConsumer.poll(Duration.ofMillis(timeout))
// → 将 ConsumerRecord 包装为 OrderedPartitionableRecord<Integer, Long, KafkaRecordEntity>
// 5. 遍历每条记录
for (OrderedPartitionableRecord record : records) {
// 5a. 验证 offset 在有效范围内
boolean shouldProcess = verifyRecordInRange(
record.getPartitionId(), record.getSequenceNumber()
);
if (shouldProcess) {
// 5b. 解析消息为 InputRow
List<InputRow> rows = parser.parse(record.getData(), ...);
// → StreamChunkParser.parse()
// → InputFormat.createReader() 或 InputRowParser.parseBatch()
// → 应用 TransformSpec 转换
// → 过滤 minimumMessageTime / maximumMessageTime 之外的行
// 5c. 找到对应的 SequenceMetadata
SequenceMetadata sequenceToUse = sequences.stream()
.filter(seq -> seq.canHandle(this, record))
.findFirst().orElse(null);
// 5d. 写入每一行
for (InputRow row : rows) {
AppenderatorDriverAddResult addResult = driver.add(
row, sequenceToUse.getSequenceName(), committerSupplier,
true, // skipSegmentLineageCheck
false // allowIncrementalPersists (先不允许,等批次处理完)
);
// → StreamAppenderatorDriver.add()
// → BaseAppenderatorDriver.append()
// → 分配 SegmentId(如果需要)
// → appenderator.add(segmentId, row, committerSupplier)
// → Sink.add(row) → IncrementalIndex.add(row)
// 5e. 检查是否需要创建新 Segment
boolean isPushRequired = addResult.isPushRequired(
maxRowsPerSegment, maxTotalRows
);
if (isPushRequired && !sequenceToUse.isCheckpointed()) {
sequenceToCheckpoint = sequenceToUse;
}
isPersistRequired |= addResult.isPersistRequired();
}
// 5f. 触发异步持久化
if (isPersistRequired) {
driver.persistAsync(committerSupplier.get());
// → appenderator.persistAll(wrappedCommitter)
// → 将 IncrementalIndex 持久化为磁盘文件
// → IndexMergerV9.persist()
}
// 5g. 更新 offset
lastReadOffsets.put(record.getPartitionId(), record.getSequenceNumber());
currOffsets.put(record.getPartitionId(), record.getSequenceNumber() + 1);
}
// 5h. 检查是否读到了 endOffset
if (!moreToReadAfterThisRecord) {
assignment.remove(record.getStreamPartition());
stillReading = !assignment.isEmpty();
}
}
// 6. 定期 Checkpoint
if (System.currentTimeMillis() > nextCheckpointTime) {
sequenceToCheckpoint = getLastSequenceMetadata();
}
if (sequenceToCheckpoint != null && stillReading) {
// 向 Supervisor 发送 Checkpoint 请求
CheckPointDataSourceMetadataAction checkpointAction = ...;
toolbox.getTaskActionClient().submit(checkpointAction);
}
}
5.3 maybePersistAndPublishSequences() --- 增量发布
scss
复制代码
private void maybePersistAndPublishSequences(Supplier<Committer> committerSupplier) {
for (SequenceMetadata sequenceMetadata : sequences) {
sequenceMetadata.updateAssignments(currOffsets, this::isMoreToReadBeforeReadingRecord);
// 如果 Sequence 已关闭(所有分区都读完了)且未在发布中
if (!sequenceMetadata.isOpen()
&& !publishingSequences.contains(sequenceMetadata.getSequenceName())) {
publishingSequences.add(sequenceMetadata.getSequenceName());
driver.persist(committerSupplier.get()); // 同步持久化
publishAndRegisterHandoff(sequenceMetadata); // 异步发布
}
}
}
这是 增量发布(Incremental Publishing) 的关键:在消费过程中,已完成的 Sequence 可以提前发布,不需要等到整个 Task 结束。
六、第四层:发布与 Handoff 层
6.1 核心类与文件
| 类 |
文件路径 |
StreamAppenderatorDriver |
server/src/main/java/org/apache/druid/segment/realtime/appenderator/StreamAppenderatorDriver.java |
BaseAppenderatorDriver |
server/src/main/java/org/apache/druid/segment/realtime/appenderator/BaseAppenderatorDriver.java |
6.2 publishAndRegisterHandoff() 流程
scss
复制代码
// SeekableStreamIndexTaskRunner.publishAndRegisterHandoff()
protected void publishAndRegisterHandoff(SequenceMetadata sequenceMetadata) {
// 1. 调用 driver.publish() 发布 Segment
ListenableFuture<SegmentsAndCommitMetadata> publishFuture = driver.publish(
sequenceMetadata.createPublisher(...), // TransactionalSegmentPublisher
sequenceMetadata.getCommitterSupplier(...).get(),
Collections.singletonList(sequenceMetadata.getSequenceName())
);
publishWaitList.add(publishFuture);
// 2. 发布成功后注册 Handoff
Futures.addCallback(publishFuture, new FutureCallback<>() {
@Override
public void onSuccess(SegmentsAndCommitMetadata published) {
// 从 sequences 列表中移除已发布的 sequence
sequences.remove(sequenceMetadata);
publishingSequences.remove(sequenceMetadata.getSequenceName());
persistSequences(); // 持久化 sequence 状态
// 注册 Handoff 等待
driver.registerHandoff(published);
}
});
}
6.3 StreamAppenderatorDriver.publish() 内部流程
typescript
复制代码
public ListenableFuture<SegmentsAndCommitMetadata> publish(...) {
List<SegmentIdWithShardSpec> theSegments = getSegmentIdsWithShardSpecs(sequenceNames);
// 步骤1: pushInBackground --- 合并并推送到 Deep Storage
// 步骤2: publishInBackground --- 发布元数据到 MetaStore
return Futures.transformAsync(
pushInBackground(wrapCommitter(committer), theSegments, true),
sam -> publishInBackground(null, null, null, sam, publisher, Function.identity()),
Execs.directExecutor()
);
}
6.4 pushInBackground() --- Push 到 Deep Storage
javascript
复制代码
// BaseAppenderatorDriver.pushInBackground()
ListenableFuture<SegmentsAndCommitMetadata> pushInBackground(...) {
return Futures.transform(
appenderator.push(segmentIdentifiers, wrappedCommitter, useUniquePath),
// → StreamAppenderator.push()
// → persistAll() 确保所有数据已持久化
// → 遍历每个 Sink 调用 mergeAndPush()
segmentsAndMetadata -> {
// 校验 push 的 segment 与请求的一致
return segmentsAndMetadata;
},
executor
);
}
6.5 registerHandoff() --- 等待 Historical 加载
scss
复制代码
// StreamAppenderatorDriver.registerHandoff()
public ListenableFuture<SegmentsAndCommitMetadata> registerHandoff(
SegmentsAndCommitMetadata sam
) {
for (SegmentIdWithShardSpec segmentId : waitingSegmentIdList) {
handoffNotifier.registerSegmentHandoffCallback(
new SegmentDescriptor(
segmentId.getInterval(), segmentId.getVersion(), ...
),
Execs.directExecutor(),
() -> {
// Historical 加载完成后的回调
metrics.incrementHandOffCount();
appenderator.drop(segmentId); // 从本地删除 Segment
}
);
}
return resultFuture;
}
七、第五层:Segment 生成层(mergeAndPush)
7.1 核心代码位置
StreamAppenderator.java 的 mergeAndPush() 方法
7.2 mergeAndPush() 详细流程
scss
复制代码
private DataSegment mergeAndPush(
SegmentIdWithShardSpec identifier, Sink sink, boolean useUniquePath
) {
// 1. 前置校验
// - Sink 不再可写
// - 所有 FireHydrant 已 swap(已持久化)
// 2. 检查是否已推送(幂等性)
if (descriptorFile.exists() && !useUniquePath) {
return objectMapper.readValue(descriptorFile, DataSegment.class);
}
// 3. 收集所有 QueryableIndex
List<QueryableIndex> indexes = new ArrayList<>();
for (FireHydrant fireHydrant : sink) {
Pair<ReferenceCountingSegment, Closeable> segmentAndCloseable =
fireHydrant.getAndIncrementSegment();
QueryableIndex queryableIndex =
segmentAndCloseable.lhs.asQueryableIndex();
indexes.add(queryableIndex);
}
// 4. 合并所有 Hydrant 的 QueryableIndex
File mergedFile = indexMerger.mergeQueryableIndex(
indexes, // 多个持久化的 Hydrant
schema.getGranularitySpec().isRollup(), // 是否 Rollup
schema.getAggregators(), // 聚合器
schema.getDimensionsSpec(), // 维度规格
mergedTarget, // 输出目录
tuningConfig.getIndexSpec(), // 索引规格(压缩、位图类型等)
tuningConfig.getIndexSpecForIntermediatePersists(),
new BaseProgressIndicator(),
tuningConfig.getSegmentWriteOutMediumFactory(),
tuningConfig.getMaxColumnsToMerge()
);
// → IndexMergerV9.mergeQueryableIndex()
// → 多 Segment 字典合并 (DictionaryMergingIterator)
// → 行合并 (RowCombiningTimeAndDimsIterator 或 MergingRowIterator)
// → 列式写入 + 倒排索引构建
// → 生成 V9 格式 Segment 文件
// 5. 推送到 Deep Storage(带重试,最多 5 次)
DataSegment segment = RetryUtils.retry(
() -> dataSegmentPusher.push(mergedFile, segmentToPush, useUniquePath),
exception -> exception instanceof Exception,
5
);
// 6. 写入描述文件(用于幂等性检查)
objectMapper.writeValue(descriptorFile, segment);
return segment;
}
八、完整时序图
scss
复制代码
用户 Overlord KafkaSupervisor KafkaIndexTask TaskRunner
│ │ │ │ │
│ POST /supervisor │ │ │ │
│───────────────────>│ │ │ │
│ │ 创建 Supervisor │ │ │
│ │───────────────────>│ │ │
│ │ │ start() 启动定时 │ │
│ │ │ │ │
│ │ │ [每隔 period] │ │
│ │ │ runInternal() │ │
│ │ │ ├─ getPartitions() │ │
│ │ │ ├─ discoverTasks() │ │
│ │ │ └─ createNewTasks()│ │
│ │ │ │ │
│ │ taskQueue.add() │ │ │
│ │<───────────────────│ │ │
│ │ │ │ │
│ │ 分配到 MiddleManager│ │ │
│ │───────────────────────────────────────>│ │
│ │ │ │ createTaskRunner() │
│ │ │ │───────────────────>│
│ │ │ │ │
TaskRunner Kafka Appenderator Driver DeepStorage
│ │ │ │ │
│ 创建 Consumer │ │ │ │
│───────────────────>│ │ │ │
│ 创建 Appenderator │ │ │ │
│────────────────────────────────────────>│ │ │
│ 创建 Driver │ │ │ │
│─────────────────────────────────────────────────────────────>│ │
│ startJob() │ │ │ │
│─────────────────────────────────────────────────────────────>│ │
│ │ │ │ │
│ [主消费循环] │ │ │ │
│ │ │ │ │
│ poll(timeout) │ │ │ │
│───────────────────>│ │ │ │
│<───────────────────│ ConsumerRecords │ │ │
│ │ │ │ │
│ parse → InputRow[] │ │ │ │
│ │ │ │ │
│ [每条 InputRow] │ │ │ │
│ driver.add(row) │ │ │ │
│─────────────────────────────────────────────────────────────>│ │
│ │ │ add(segmentId,row) │ │
│ │ │<────────────────────│ │
│ │ │ Sink.add→Index.add │ │
│ │ │ │ │
│ [触发持久化] │ │ │ │
│ persistAsync() │ │ │ │
│─────────────────────────────────────────────────────────────>│ │
│ │ │ persistAll() │ │
│ │ │<────────────────────│ │
│ │ │ IndexMergerV9 │ │
│ │ │ .persist() │ │
│ │ │ │ │
│ [Sequence 完成] │ │ │ │
│ publish() │ │ │ │
│─────────────────────────────────────────────────────────────>│ │
│ │ │ push(segments) │ │
│ │ │<────────────────────│ │
│ │ │ mergeAndPush() │ │
│ │ │ IndexMergerV9 │ │
│ │ │ .mergeQueryableIndex() │
│ │ │ │ │
│ │ │ push(mergedFile) │ │
│ │ │────────────────────────────────────────>│
│ │ │<────────────────────────────────────────│
│ │ │ │ DataSegment │
│ │ │ │ │
│ │ │ │ publishInBackground│
│ │ │ │ → MetaStore │
│ │ │ │ │
│ │ │ │ registerHandoff │
│ │ │ │ → 等待 Historical │
│ │ │ │ │
│ [Historical 加载完成] │ │ │
│ │ │ drop(segmentId) │ │
│ │ │<────────────────────│ │
│ │ │ │ │
│ TaskStatus.SUCCESS │ │ │ │
九、KafkaRecordSupplier 详解
9.1 核心职责
KafkaRecordSupplier 封装了 KafkaConsumer<byte[], byte[]>,提供统一的消息拉取接口。
9.2 关键方法
csharp
复制代码
// 创建 KafkaConsumer
private KafkaConsumer<byte[], byte[]> getKafkaConsumer() {
// 设置 key/value 反序列化器为 ByteArrayDeserializer
// 设置 auto.offset.reset = none(不自动重置 offset)
// 设置 enable.auto.commit = false(手动管理 offset)
return new KafkaConsumer<>(properties);
}
// 拉取消息
public List<OrderedPartitionableRecord<Integer, Long, KafkaRecordEntity>> poll(long timeout) {
ConsumerRecords<byte[], byte[]> records = consumer.poll(Duration.ofMillis(timeout));
List<OrderedPartitionableRecord<Integer, Long, KafkaRecordEntity>> result = new ArrayList<>();
for (ConsumerRecord<byte[], byte[]> record : records) {
result.add(new OrderedPartitionableRecord<>(
record.topic(),
record.partition(), // partitionId (Integer)
record.offset(), // sequenceNumber (Long)
Collections.singletonList(new KafkaRecordEntity(record))
));
}
return result;
}
// 定位到指定 offset
public void seek(StreamPartition<Integer> partition, Long sequenceNumber) {
consumer.seek(
new TopicPartition(partition.getStream(), partition.getPartitionId()),
sequenceNumber
);
}
// 分配分区
public void assign(Set<StreamPartition<Integer>> streamPartitions) {
consumer.assign(
streamPartitions.stream()
.map(p -> new TopicPartition(p.getStream(), p.getPartitionId()))
.collect(Collectors.toSet())
);
}
十、关键设计要点
10.1 增量发布(Incremental Publishing)
| 特性 |
说明 |
| 核心思想 |
消费过程中,已完成的 Sequence 可以提前发布,不需要等到 Task 结束 |
| 触发条件 |
Sequence 的所有分区都读到了 endOffset |
| 优势 |
减少数据延迟,降低内存压力 |
| 实现 |
maybePersistAndPublishSequences() 在每次消费循环中检查 |
10.2 Exactly-Once 语义保障
| 机制 |
说明 |
| useUniquePath |
Kafka Task 使用 useUniquePath=true 推送到 Deep Storage,避免重复数据 |
| 事务性发布 |
TransactionalSegmentPublisher 确保 Segment 元数据和 offset 原子性更新 |
| 幂等性检查 |
mergeAndPush() 中检查 descriptorFile 是否已存在,避免重复推送 |
10.3 Offset 管理
| 机制 |
说明 |
| currOffsets |
当前消费位置,每消费一条消息后 +1 |
| lastReadOffsets |
最后读取的 offset |
| Committer |
将 Kafka offset 与 Segment 元数据绑定,持久化到 commit.json |
| Checkpoint |
定期向 Supervisor 发送 Checkpoint,支持任务失败后从断点恢复 |
10.4 Handoff 等待
| 机制 |
说明 |
| registerHandoff |
发布后注册 Handoff 回调 |
| SegmentHandoffNotifier |
监听 Coordinator 的 Segment 加载通知 |
| 完成条件 |
Historical 加载完成后,从本地删除 Segment |
| 超时处理 |
如果 Handoff 超时,Task 仍然返回 SUCCESS(数据已在 Deep Storage) |
10.5 多 Hydrant 合并
| 机制 |
说明 |
| Hydrant 来源 |
一个 Sink 可能有多次持久化产生的多个 FireHydrant |
| 合并时机 |
push 阶段通过 mergeQueryableIndex 合并所有 Hydrant |
| 合并内容 |
字典合并(DictionaryMergingIterator)+ 行合并 + 倒排索引合并 |
| 输出 |
单个 V9 格式 Segment 文件 |
10.6 重试与容错
| 机制 |
说明 |
| Deep Storage 推送重试 |
支持 5 次重试,应对网络抖动 |
| commit.json |
记录已持久化的 hydrant 数量,支持从磁盘恢复 |
| persistError 传播 |
异步持久化错误通过 persistError 变量传播到主线程 |
| OffsetOutOfRange 处理 |
IncrementalPublishingKafkaIndexTaskRunner 特殊处理 offset 越界 |
十一、涉及的核心源码文件汇总
| 层级 |
文件 |
说明 |
| Supervisor 层 |
extensions-core/kafka-indexing-service/.../supervisor/KafkaSupervisor.java |
Kafka Supervisor 实现,管理 Task 生命周期 |
|
indexing-service/.../seekablestream/supervisor/SeekableStreamSupervisor.java |
流式 Supervisor 基类,包含 runInternal 主循环 |
| Task 层 |
extensions-core/kafka-indexing-service/.../KafkaIndexTask.java |
Kafka 索引任务,创建 Runner 和 RecordSupplier |
|
indexing-service/.../seekablestream/SeekableStreamIndexTask.java |
流式任务基类,创建 Appenderator 和 Driver |
| Runner 层 |
extensions-core/kafka-indexing-service/.../IncrementalPublishingKafkaIndexTaskRunner.java |
Kafka 任务运行器,处理 OffsetOutOfRange |
|
indexing-service/.../seekablestream/SeekableStreamIndexTaskRunner.java |
核心 :runInternal 消费主循环 + 发布逻辑 |
| 消费层 |
extensions-core/kafka-indexing-service/.../KafkaRecordSupplier.java |
封装 KafkaConsumer,提供 poll/seek/assign |
| Driver 层 |
server/.../appenderator/StreamAppenderatorDriver.java |
流式 Driver,管理 publish/handoff |
|
server/.../appenderator/BaseAppenderatorDriver.java |
Driver 基类,pushInBackground/publishInBackground |
| Appenderator 层 |
server/.../appenderator/StreamAppenderator.java |
核心:add/persistAll/push/mergeAndPush |
| Segment 生成层 |
processing/.../segment/IndexMergerV9.java |
V9 格式 Segment 合并/持久化 |