08 - 向量存储与文档解析

文档解析策略模式、Milvus+MySQL双写、智能去重与共享向量

一、文档处理全流程

              文档处理全流程
              ══════════════

  用户上传文档 (PDF/DOCX/MD)
         │
         ▼
  ┌──────────────────────────────┐
  │ 1. 文档解析 (hub-doc-parser) │
  │    ParserFactory → 策略选择  │
  │    PDF → PdfDocumentParser   │
  │    DOCX → WordDocumentParser │
  │    MD → MarkdownDocParser    │
  │    → 提取纯文本              │
  └──────────────┬───────────────┘
                 ▼
  ┌──────────────────────────────┐
  │ 2. 文本分块 (TextChunker)    │
  │    递归字符分块:              │
  │    · 块大小: 500字符          │
  │    · 重叠: 100字符            │
  │    · 段落边界感知             │
  └──────────────┬───────────────┘
                 ▼
  ┌──────────────────────────────┐
  │ 3. 去重检查 (DuplicateCheck) │
  │    SHA-256哈希 → 精确匹配    │
  │    向量相似度 → 语义去重      │
  │    (阈值: >0.8)              │
  └──────────────┬───────────────┘
                 ▼
  ┌──────────────────────────────┐
  │ 4. 向量化 + 双写              │
  │    文本 → Embedding → 向量   │
  │    Milvus: 存向量 + 搜索     │
  │    MySQL: 存元数据 + 全文索引│
  └──────────────────────────────┘

二、文档解析 — 策略模式

ParserFactory 根据文件类型选择不同的解析策略:

ParserFactory (策略模式):

              ┌─────────────────────┐
              │   ParserFactory     │
              │   getParser(type)   │
              └─────────┬───────────┘
                        │
         ┌──────────────┼──────────────┐
         │              │              │
         ▼              ▼              ▼
  ┌────────────┐ ┌────────────┐ ┌────────────┐
  │ PdfParser  │ │ WordParser │ │ MdParser   │
  │ (PDFBox)   │ │ (POI)      │ │(CommonMark)│
  └────────────┘ └────────────┘ └────────────┘

  策略接口: DocumentParser.parse(inputStream) → String

  新增格式只需:
  1. 实现 DocumentParser 接口
  2. 注册到 ParserFactory
  3. 无需修改任何现有代码

三、TextChunker — 智能分块

TextChunker 分块算法:

  原始文本:
  "第一段内容。这是关于AI的知识。
   第二段内容。机器学习是AI的子集。
   第三段内容。深度学习是机器学习的子集。"

  配置: chunkSize=50, overlap=10

  Chunk 1: "第一段内容。这是关于AI的知识。第二段内容。机器" (50字符)
  Chunk 2: "器学习是AI的子集。第三段内容。深度学习是机器学" (50字符)
           ↑ 重叠10字符
  Chunk 3: "机器学习的子集。" (剩余部分)

  关键特性:
  · 优先在段落边界 (\n\n) 处分割
  · 段落过长时用滑动窗口强制分割
  · overlap确保上下文连续性

四、Milvus + MySQL双写

VectorIngestService 实现了向量数据库和关系数据库的双写:

VectorIngestService 双写架构:

  文档块 → Embedding模型 → 向量
                │
    ┌───────────┴───────────┐
    ▼                       ▼
  Milvus                   MySQL
  ┌──────────────┐   ┌──────────────────┐
  │ 存储向量      │   │ 存储元数据        │
  │ 向量搜索      │   │ 全文索引搜索      │
  │ (语义匹配)    │   │ (关键词匹配)      │
  │              │   │                  │
  │ collection:  │   │ document_chunk:  │
  │  vector_id   │   │  id, document_id │
  │  embedding   │   │  content         │
  │  metadata    │   │  vector_id ←─────│── 共享向量指针
  └──────────────┘   │  chunk_index     │
                     │  content_hash    │
                     └──────────────────┘

  优势:
  · Milvus擅长语义搜索 (理解"年假"≈"带薪休假")
  · MySQL擅长精确搜索 (匹配"Q4-2024-001"编号)
  · 两者互补,通过RRF融合

五、共享向量优化

亮点: 当多个用户上传相同文档时,不需要重复向量化。DuplicateCheckService检测到内容相同后,新的DocumentChunk直接引用已有的vector_id,而不是重新计算Embedding。这节省了API调用成本和存储空间。

六、智能去重

DuplicateCheckService 两阶段去重:

  阶段1: SHA-256精确匹配 (快速)
  ┌──────────────────────────────────┐
  │ hash = SHA-256(文件内容)         │
  │ SELECT * FROM document           │
  │ WHERE content_hash = hash        │
  │ AND user_id = current_user       │
  │                                  │
  │ 命中 → 返回 "文档已存在" (2001)  │
  │ 未命中 → 进入阶段2               │
  └──────────────────────────────────┘

  阶段2: 向量语义相似度 (深度)
  ┌──────────────────────────────────┐
  │ 对新文档取样几个chunk            │
  │ 在Milvus中搜索相似文档           │
  │ 相似度 > 0.8 → 可能是同一文档   │
  │                                  │
  │ 返回 "相似文档已存在" (2002)     │
  │ 可选择强制上传或取消             │
  └──────────────────────────────────┘

七、面试高频问题

Q: 为什么选择Milvus而不是其他向量数据库?
A: Milvus是开源的云原生向量数据库,支持分布式部署、GPU加速、多种索引类型(IVF、HNSW等)。相比Pinecone(托管服务,数据出境合规风险)和FAISS(单机库,不支持分布式),Milvus在功能、性能和自主可控之间取得了好的平衡。项目使用Milvus 2.6.0,支持Collection级别的隔离和RBAC。
Q: 双写如何保证一致性?
A: 先写MySQL元数据,再写Milvus向量。如果Milvus写入失败,MySQL记录标记为"向量化失败",后台任务会自动重试。删除时采用"智能删除":先检查vector_id是否被其他文档引用,只有没有其他引用时才从Milvus中删除向量。
Q: 共享向量是怎么实现的?
A: ingestSharedFromExisting()方法:当检测到新文档内容与已有文档内容相同时,新文档的chunk直接复用已有chunk的vector_id。在MySQL中插入新的DocumentChunk记录,但vector_id指向已有的Milvus向量。删除时采用引用计数,只有引用为0时才真正删除向量。

八、文档解析引擎 — 深度讲解

hub-doc-parser 模块负责将用户上传的文档解析为可检索的文本块,供 RAG 系统使用。采用策略模式(Strategy Pattern)实现多格式支持。

文档解析引擎完整流程:

  用户上传文档
    │
    ▼
  ParserFactory.getDocumentParser(contentType)
    │
    ├─ application/pdf       → PdfDocumentParser  (PDFBox 3.0.3)
    ├─ application/...word.. → WordDocumentParser  (POI 5.3.0)
    └─ text/markdown         → MarkdownDocParser   (CommonMark 0.22.0)
    │
    ▼
  DocumentParser.parse(inputStream) → 纯文本
    │
    ▼
  TextChunker.chunk(text) → List<ParsedChunk>
    │
    ▼
  向量入库 (hub-vector-store → Milvus)

DocumentParser 接口设计

public interface DocumentParser {
    boolean supports(String contentType);   // 是否支持此MIME类型
    String parse(InputStream inputStream);  // 解析为纯文本
    String supportedExtension();            // 支持的文件扩展名
}

// 新增格式零侵入:
@Component
public class TxtParser implements DocumentParser { ... }
// Spring 自动发现 → ParserFactory 自动注册

三大解析器对比

解析器依赖格式特性
PdfDocumentParserApache PDFBox 3.0.3.pdf提取文本内容,处理多页文档
WordDocumentParserApache POI 5.3.0.docx提取段落、表格、列表等结构化内容
MarkdownDocumentParserCommonMark 0.22.0.md解析Markdown为纯文本,保留结构

TextChunker — 文本分块详解

ParsedChunk 模型:每个分块携带 index(块索引)、content(块内容)、metadata(元数据Map),为后续向量化和来源引用提供完整信息。

分块策略:

与其他模块协作链路

步骤模块操作
1. 上传hub-apiDocumentController 接收文件
2. 解析hub-doc-parserParserFactory 选择解析器,解析为文本
3. 分块hub-doc-parserTextChunker 切分为 ParsedChunk 列表
4. 向量化hub-vector-storeVectorIngestService 双写 Milvus + MySQL
5. 检索hub-llm-coreHybridSearchService 混合语义+关键词搜索
Q: 文档解析引擎用了什么设计模式?为什么这样设计?
A: 策略模式 + 工厂模式。DocumentParser 接口定义统一的解析行为,每种格式一个策略实现类。ParserFactory 根据 MIME 类型路由到对应策略。新增格式(如 TXT、Excel)只需实现接口 + @Component,零侵入现有代码。这是开闭原则(OCP)的典型应用。
Q: 文档解析失败怎么办?
A: 解析失败的文档状态标记为 FAILED,记录错误信息。提供 reparse 接口支持用户重试。当前不做 OCR(过重),先支持文本层 PDF,后续可通过实现新的 DocumentParser 接入 OCR 服务,渐进式增强。