快速回顾
| 项目 | 内容 |
|---|---|
| 核心目的 | 上传与解析、分块、向量化解耦,支持大文件续传和任务恢复 |
| 处理链路 | MinIO 分片上传 → 合并确认 → MySQL 状态与 Outbox → Kafka → 后台处理 |
| 状态与恢复 | Redis Bitmap 记录进度;MinIO 校对实际分片;MySQL 保存阶段 checkpoint |
| 一致性 | 稳定业务键、条件更新、稳定 chunk ID upsert、Outbox 重试发布 |
面试主回答
文档解析、分块和向量化耗时较长,如果放在上传请求里同步完成,用户等待时间会很长,处理失败也会影响上传接口。因此我用 Kafka 解耦文件上传和后台入库,上传完成后投递文档任务,由消费者执行后续处理。Kafka 的日志和消费位点便于追踪、恢复处理进度,消费者组也便于按负载扩展。
它并非所有场景下都优于其他 MQ;RabbitMQ 的任务队列和路由语义更直接,选型还要看任务规模、团队经验和现有基础设施。无论使用哪种 MQ,都要处理重复消费、失败重试和任务状态,不能把消息投递成功等同于文档已经可检索。
用户创建上传会话后,后端校验文件信息和访问权限,再向 MinIO 创建 Multipart Upload 并取得 uploadId。客户端按序号上传各分片,记录每片返回的 ETag;Redis Bitmap 记录分片进度,供续传时快速查询。网络中断后,客户端使用原会话补传未完成分片。
恢复时与 MinIO 的实际分片列表核对,避免 Redis 进度丢失或过期造成状态不一致。全部分片完成后,后端按序号和 ETag 请求 MinIO 合并对象,确认原文件可用后再更新任务状态并投递 Kafka 入库任务。上传完成和入库完成是两个不同状态:只有解析、分块和索引流程成功后,文档才可检索。
原理与选型
面试回答
我们这条链路里,文件上传之后还要解析、分块和向量化,处理耗时比较长,所以需要把上传接口和后台任务解耦。我选择 Kafka,主要看中它的分区日志、消息保留和消费位点,任务失败后可以重新消费消息,消费者组也便于扩展处理能力。
上传完成后,系统发布文档入库任务,消费者根据数据库里的阶段状态继续处理。Kafka 负责消息传递和重放,具体恢复到哪一个阶段仍由业务 checkpoint 决定。RabbitMQ 也能完成这种异步任务,它的队列和路由语义更直接;我们这套方案更侧重消息重放和消费进度管理,因此使用 Kafka,并在业务侧补齐幂等和失败恢复。
实现细节
Kafka 消费位点描述消息消费进度,业务 checkpoint 描述文档处理进度,两者需要配合。更换 MQ 也不能省掉任务状态与幂等控制。消息发布成功后,文档仍需完成索引才能进入可检索状态。
| 比较点 | Kafka | RabbitMQ |
|---|---|---|
| 主要机制 | 分区日志、消费位点、消费者组 | 任务队列、消费确认与消息路由 |
| 本项目的考虑 | 便于任务追踪、消息重放和消费者扩展 | 也能承担异步任务,队列与路由语义更直接 |
| 共同需要处理 | 重复消费、失败恢复、业务任务状态 | 重复消费、失败恢复、业务任务状态 |
| 选择依据 | 任务规模、已有基础设施与团队经验 | 任务规模、已有基础设施与团队经验 |
实现与恢复
面试回答
整体分成创建会话、上传分片、合并确认和触发入库几个阶段。用户发起上传后,后端先校验文件和权限,再向 MinIO 创建 Multipart Upload,拿到 uploadId。客户端按分片序号上传,保存每片的 ETag,Redis Bitmap 用来快速记录哪些分片已经完成。
网络中断后沿用原来的上传会话,先与 MinIO 的实际分片列表核对,再补传缺失部分,避免只相信缓存进度。全部分片上传成功后,后端按序号和 ETag 请求合并,并确认目标对象可用。之后在数据库中更新文档状态、写入 Outbox 事件,再由发布器投递 Kafka。后台完成解析、分块和索引后,文档才会标记为可检索。
实现细节
uploadId 标识 MinIO 的一次分片上传,partNumber 决定分片次序,ETag 用于合并时指定已上传的分片。Redis 进度可能过期或丢失,恢复以 MinIO 中实际存在的分片为校对依据。上传状态与索引处理状态分别维护。
| 步骤 | 关键动作 | 记录或检查 |
|---|---|---|
| 1. 创建会话 | 校验文件与权限,创建 Multipart Upload | uploadId 与上传会话 |
| 2. 上传分片 | 按 partNumber 上传,保存返回 ETag | 分片序号、ETag、Redis Bitmap |
| 3. 中断续传 | 使用原会话,校对 MinIO 实际分片 | 补传缺失分片,纠正缓存进度 |
| 4. 合并文件 | 按序号与 ETag 提交合并 | 确认目标对象实际可用 |
| 5. 触发入库 | 更新文档状态并写入 Outbox 事件 | 由发布器投递 Kafka |
| 6. 完成索引 | 解析、分块、向量化与索引完成 | 推进为可检索状态 |
面试回答
我按消息可能重复到达来设计消费逻辑,先用文档版本和处理阶段组成稳定业务键。消费者拿到消息后,先检查数据库里的阶段状态,已经完成的直接跳过,处理到一半的根据持久化 checkpoint 从未完成的位置继续。
对于最终的索引写入,我给每个子块生成稳定的 chunk ID,使用 upsert,重复处理时更新同一条记录,避免新增重复索引。每个阶段都在产物和状态落库后再提交消费进度。这样即使处理成功后提交位点失败,消息再次到达,也可以根据已有状态安全跳过。Redis 可以辅助加速判断,但持久化状态才是恢复依据。
实现细节
阶段业务键使用 documentVersionId + stage,不能只用文档名称或本次消息 ID。阶段完成判断与持久化写入需要配合,避免并发处理时重复推进状态。存在非幂等的外部副作用时,另存调用状态,并通过结果核对或补偿处理。
| 层次 | 关键标识或机制 | 重复到达时 |
|---|---|---|
| 任务阶段 | documentVersionId + stage |
查询持久化状态,跳过已完成阶段 |
| 阶段产物 | 持久化 checkpoint 与产物 | 从未完成位置继续 |
| 索引写入 | 稳定 chunk ID + upsert | 更新同一条索引记录 |
| 外部副作用 | 调用状态记录与补偿 | 先确认结果,避免重复执行 |
面试回答
消费失败后,我先判断错误能否通过重试恢复。模型超时、存储暂时不可用这类临时故障采用退避重试;文件格式错误、内容为空这类确定性问题,继续重试没有意义,就记录原因,停止自动重试并转人工或死信流程。
恢复依赖数据库里的阶段 checkpoint。解析、分块和向量化各阶段完成后,先保存产物和状态,再提交消费进度。Worker 重启或者消息重放时,先读取已完成阶段,从失败位置继续,避免每次重新处理整个文件。重复到达的消息仍然走同一套幂等逻辑,所以重试和恢复可以配合使用。
实现细节
恢复前需要同时确认阶段状态与对应产物,不能仅凭一个“成功”标记跳过缺失结果。消费进度确认与业务阶段完成分开理解,阶段中断不能提前当作处理成功。
| 情况 | 判断或状态 | 处理方式 |
|---|---|---|
| 模型超时、存储暂不可用 | 临时故障 | 退避重试 |
| 格式错误、内容为空 | 确定性错误 | 停止自动重试,记录原因并转人工或死信流程 |
| Worker 重启或消息重放 | 已保存阶段 checkpoint | 跳过已完成阶段,从未完成阶段继续 |
| 阶段执行成功 | 产物与状态均已持久化 | 再提交消费进度 |
面试回答
这里涉及数据库和 Kafka 两个系统,我使用 Transactional Outbox 保证任务不会因为发送失败而丢失。MinIO 文件合并确认成功后,在同一个数据库事务里更新文档状态,同时写入一条待发布事件,事务提交后再由独立发布器发送 Kafka。
Kafka 暂时不可用时,事件仍然保留在数据库,发布器后续继续重试;收到 Kafka 确认后才标记已发布。如果发送成功但标记前进程崩溃,事件可能再次投递,这部分交给消费端幂等处理。这样通过持久化待发布事件解决漏投递,通过幂等解决可能的重复投递。
实现细节
Outbox 事务覆盖文档状态与事件记录,MinIO 对象合并发生在这一事务之前。发布器不能在得到 Kafka 确认前将事件标成已发布;发布结果不确定的事件保留重试资格。
| 环节 | 持久化或确认 | 失败后的行为 |
|---|---|---|
| 数据库事务 | 同时更新文档状态与写入待发布事件 | 事务失败则一起回滚 |
| 发布器投递 | 扫描 Outbox 并发送 Kafka | 未获确认的事件保留并重试 |
| 确认发布 | 收到 Kafka 确认后标记已发布 | 标记前崩溃可能重复投递,由消费端幂等处理 |
面试回答
我先区分分片不完整、合并明确失败和合并结果不确定几种情况。合并前校验分片序号和 ETag,并和 MinIO 的分片列表核对;如果缺片,就保留上传会话,让客户端补传后再合并。
如果调用合并接口超时,不能直接认为文件没有生成。我会先查询目标对象,核对文件是否已经完整可用;已经成功就推进后续入库,确实未成功且原上传会话仍有效时再重试。会话已经失效就重新创建上传。只有确认原文件可用后才触发入库,失败过程中保留状态和原因,便于续传或清理遗留分片。
实现细节
合并成功通常会结束对应 Multipart Upload,因此超时后再次找不到上传会话,不能单独据此判断失败,需要结合目标对象检查。长期未完成的会话和分片应清理,避免存储长期占用;清理前检查任务仍是否有效。
| 故障位置 | 判断依据 | 恢复方式 |
|---|---|---|
| 分片不完整 | MinIO 实际分片与预期列表 | 保留会话,补传缺失分片 |
| 分片信息不一致 | partNumber 与对应 ETag | 校正后重新请求合并 |
| 合并请求超时 | 目标对象是否已经生成 | 先确认结果,再决定是否重试 |
| 上传会话过期未完成 | 会话有效期与处理状态 | 按清理策略回收 |
| 合并明确成功 | 对象已确认可用 | 推进数据库状态并触发入库 |
易混点与回答检查
易混点:上传成功、消息发布成功和文档可检索是不同状态;Redis Bitmap 用于进度加速,恢复仍要核对 MinIO。
回答检查
补充记录
简历表述:基于 Kafka 将解析、分块与向量化转为后台任务;结合 Redis Bitmap 与 MinIO 分片上传实现大文件断点续传。
事实 / 代码依据:待补充。