RAG实现思路流程

上传接口

阶段一:前端分片与"指纹"识别(Gateway)

在处理 GB 级别的文档时,传统的单文件上传会导致连接超时或内存崩溃。

  • 预处理(MD5 计算) :前端利用 spark-md5 预先扫描文件,生成 FileMD5 。这是文件的"身份证号",用于实现秒传(如果后端已存该 MD5,直接返回成功)。
  • 物理切片(Slicing) :利用 JavaScript 的 Blob.slice() 将大文件切割为固定大小(如 5MB)的 Chunks
  • 并发上传 :前端同时发起多个 uploadChunk 请求,携带 chunkIndexfileMd5

阶段二:后端接入与可靠性保障(Warehouse)

后端不仅要收数据,还要做精密的状态管理。

  • 二级校验(Redis + MySQL)
    • Redis :作为高性能计数器,记录已到达的分片编号,实现高并发下的快速判重
    • MySQL:持久化文件元数据(文件名、总大小、上传者、组织权限)。
  • 断点续传逻辑 :如果上传中断,用户下次上传同一 MD5 文件时,后端通过查询 Redis/MySQL 返回已收到的 chunkIndex,前端从断点处继续发送。
  • 云端合并(MinIO) :当所有分片到齐,后端调用 MinIO 的 composeObject 接口进行服务端合并,避免将几十 GB 的数据拉回应用服务器内存。

阶段三:任务调度与异步解耦(Assembly Line)

文件存入 MinIO 只是开始,真正的"重型加工"必须异步化。

  • 任务下发(Kafka) :后端合并完成后,立即向 Kafka 发送一条包含 fileMd5objectUrl事务消息
  • 削峰平谷 :Kafka 充当了缓冲池。即便用户瞬间上传了 1000 份文档,后台的 Consumer(消费者) 也会按照自身算力(CPU/内存情况)有序地领取任务,防止系统宕机。
  • 存算分离:Web 服务器只负责"收货",解析任务交给专门的"加工服务器"。

阶段四:文档解析与语义切片(Alchemy)

这是 RAG 系统区分于传统网盘的关键点,目标是将非结构化数据转化为"AI 易读"的格式。

  • 流式下载与解析 :Consumer 收到消息后,从 MinIO 拉取流式数据,利用 Apache Tika 等工具提取纯文本。
    • 注意:避免将大文件一次性加载进内存,应使用磁盘临时目录作为缓冲区。
  • 语义切片(Chunking) :将提取的长文本切分为小段(如 500-800 Token)。
    • 重叠度(Overlap):相邻切片需保留 10%~20% 的重复内容,确保语义在切断处不丢失。
    • 语义边界:优先按段落、标题、句号进行物理切割。

阶段五:向量化与知识入库(Indexing)

将文字转化为数字,存入搜索引擎。

  • Embedding 向量化 :将每一个 Chunk 喂给 Embedding 模型(如 OpenAI text-embedding-3 或本地模型),得到一串固定长度的高维浮点数组
  • 索引构建(Elasticsearch)
    • [原始文本, 向量数组, 元数据] 存入 ES。
    • ES 底层利用 HNSW 算法 构建导航图索引,实现毫秒级的语义近似搜索
  • 状态闭环 :全部入库完成后,更新 MySQL 中的文件状态为 SUCCESS,用户此时即可在界面上看到"文档已就绪"。

Mysql,Redis,Elasticsearch,minIO如何协同工作保证一致性?

系统通过 MySQL 中的 status 字段强制约束各组件的动作。

状态 语义 潜在冲突 / 异常情况 解决策略 (自愈机制)
0: INIT 任务初始化,未开始上传 孤儿分片:MinIO 存在该 MD5 文件夹,但 MySQL 仅停留在 INIT。 定时任务清理:扫描 MinIO 临时目录,若 MySQL 记录为 INIT 且超时,物理删除 MinIO 数据。
1: UPLOADING 分片上传中 计数不一致:前端重试导致 Redis 计数 > 实际分片数,或分片丢失导致无法合并。 幂等计数 :Redis 记录已上传的 chunkIndex 集合而非单纯累加;定时任务检查超时未完成的任务。
2: MERGING 合并触发中 并发合并:两个请求同时发现分片到齐,触发两次 MinIO 合并请求。 分布式锁 + 状态前置检查 :使用 Redis SETNX 锁定 fileMd5;执行合并前必须确认状态仍为 UPLOADING,合并开始瞬间更新为 MERGING。
3: PROCESSING 任务已发 Kafka,解析中 丢失任务:MySQL 状态为 PROCESSING,但 Kafka 消费失败或消费者宕机。 超时重推:定时任务扫描超过 30 分钟仍处于 PROCESSING 的记录,重新投入 Kafka 队列(补偿机制)。
4: SUCCESS ES 索引完成 数据残留:MySQL 已成功,但 MinIO 临时分片未清理,占用空间。 后置清理:状态改为 SUCCESS 后,触发异步删除 MinIO 临时分片;若失败,由垃圾回收 Job 兜底。
5: FAILED 处理失败 脏数据堆积:ES 中存了部分向量,MinIO 存在合并后的文件。 物理抹除 :状态转为 FAILED 时,根据 fileMd5 同步删除 ES 相关索引及 MinIO 物理文件。
1. 逆向校对策略 (Backward Reconciliation)
  • 原则:MinIO/Redis/ES 的数据必须在 MySQL 中找到"合法身份"。
  • 执行 :定时任务(Reconciliation Job)以存储侧(MinIO/ES)为基准扫描。
    • 如果 MinIO 有文件MySQL 无记录/状态为 INIT →\rightarrow→ 物理删除 MinIO 文件。
    • 如果 ES 有向量MySQL 状态非 SUCCESS →\rightarrow→ 物理删除 ES 向量。
2. 状态原子性与可见性
  • 先写库,后动作:在调用 MinIO 合并或发送 Kafka 消息前,必须先在数据库中完成状态变更。
  • CAS (Compare And Swap) :更新状态时使用 SQL:UPDATE file_table SET status = 2 WHERE file_md5 = 'xxx' AND status = 1。利用数据库行锁防止并发冲突。
3. 消费者幂等性
  • 冲突:Kafka 消息重复消费导致重复解析。
  • 解决 :ES 写入使用 fileMd5_chunkIndex 作为文档 ID。无论解析多少次,ES 始终执行 Overwrite(覆盖) 而非 Append(追加),保证最终向量数据的一致性。


混合检索接口

阶段一:权限画像与查询语义化

在检索开始前,系统首先确定"谁在搜"以及"搜什么"。

  • 语义向量化:利用 Embedding 模型将原始提问(Query)转化为高维向量。
  • 文本分析与提取:利用分词器将整个句子切分为词组
  • 双路准备 :此时系统手中同时持有查询向量 (用于语义搜索)和原始关键词(用于文本搜索)。

阶段二:向量召回与强过滤

这是检索的"漏斗"入口,核心是语义找近邻,权限做隔离

  • 向量召回 (KNN):在向量空间中寻找语义最接近的 topN 个片段。这解决了"意思相近但词不同"的问题。
  • 硬性权限过滤 (Filter) :这是一个"一票否决"环节。系统强制要求结果必须满足:是我的文档 OR 是公开文档 OR 是我组织的文档。不满足条件的片段即使语义再接近也会被剔除。

阶段三:关键词召回与分数融合

向量检索虽然聪明,但有时会产生语义幻觉(词不达意)。本阶段通过重排纠偏。

  • 关键词二次打分:对召回的候选片段进行传统的文本频率算法(BM25)打分。
  • 分数加权融合
    • 给语义分(向量)分配权重。
    • 给关键词匹配分(文本)分配权重。
  • 逻辑目标 :确保那些既符合语义,又含有精确关键词的片段能够排在最前面。

阶段四:结果装配与元数据溯源

将 ES 检索到的"数字碎块"还原为用户可读的结构化信息。由于向量数据库(ES)主要负责高性能检索,并不适合存储频繁变动或详尽的业务属性,因此需要此阶段进行"数据对账"。

  • 数据映射 :利用 ES 检索结果中的 fileMd5 回查 MySQL 。将哈希值还原为真实的文件名称。这实现了存算分离:即使文件在数据库中改名,也无需重新计算向量。
  • 属性补全 :将 ES 中的权限 ID(如 userId, orgTag)转换为可读的业务标签(如上传者姓名、所属部门)。这为前端展示提供依据,也为大模型(LLM)提供准确的引用出处(Citation)。
  • (如果是模型对话功能)片段截取 :对文本进行清洗(去乱码/换行)并按需截断。确保提供给大模型的上下文(Context)既精炼高质,有效节省 Token 消耗并提升回答准确度。

阶段五:功能分流与生成增强

根据用户的业务需求,决定最终数据的去向

  • 判断当前请求类型。若为搜索功能,则直接将阶段四封装好的结构化列表返回给前端,展示带权限的文件片段。
  • 上下文拼接 :若为模型对话,则将多个检索片段按相关性顺序进行文本串联,形成增强知识背景。
  • 提示词增强 (RAG) :将拼接好的背景知识与用户原始提问填入预设的 Prompt 模板 ,调用大模型(LLM)接口,并将生成的回复以 SSE 流式(Streaming)或 WebSocket 实时推送到前端。

相关推荐
橙淮18 小时前
并发编程(六)
java·jvm
拽着尾巴的鱼儿18 小时前
springboot openfeign 自定义feign 接口重试机制
java·spring boot·后端
白露与泡影18 小时前
2026大厂Java面试题大全!牛客网最新版
java·开发语言
EntyIU19 小时前
JVM内存与GC笔记
java·jvm·笔记
XS03010619 小时前
并发编程 六
java·后端
yaoxin52112319 小时前
419. 现代 Java IO 最佳实践 - 写入文本文件
java·windows·python
雪宫街道19 小时前
synchronized 锁的范围:对象锁、类锁与代码块锁
java·jvm·后端·面试
x***r15119 小时前
linux安装 jdk-8u291-linux-x64.tar.gz 详细步骤(解压配置环境变量)
java
极光代码工作室20 小时前
基于SpringBoot的校园论坛系统
java·springboot·web开发·后端开发
XS03010620 小时前
Spring Bean 作用域 & 生命周期
java·后端·spring