Redis+Spring AI搭建RAG知识库问答系统:从原理到生产实践

发布时间:2026/10/10 6:43:38
Redis+Spring AI搭建RAG知识库问答系统:从原理到生产实践 上个月我们给内部知识库问答系统做技术评审架构师第一句话就问向量数据库选型定了吗Milvus 还是 ES我说都没用直接拿 Redis 顶上他当场愣了几秒。不是我在耍个性而是算完账之后发现对于我们这种几十万分段、QPS 峰值不到 200 的内部系统单独引入一套向量库纯属给自己找运维麻烦。团队本来就在用 RedisSpring AI 官方也把 Redis 列为一等公民向量存储组合起来就是一条完整可用的 RAG 链路。下面的内容不打算讲大模型原理就是一份实操记录怎么用 Redis Spring AI 在 Spring Boot 工程里搭一套能直接上生产的知识库问答系统覆盖文档导入、切分、向量化、检索、问答组装以及生产环境里的调优和踩坑。适合那些已经有 Redis、不想再引入新基础设施、又想把 RAG 快速落地的 Java 团队。1. 为什么我会放弃“额外向量库”直接在 Redis 上跑 RAG1.1 RAG 三件套和团队现状的矛盾任何一套 RAG 系统拆开看都是三件事把外部知识切成片段、用 Embedding 模型把片段向量化、存进一个能按向量距离检索的存储用户提问时把问题同样向量化找出最相近的片段最后把片段拼进 Prompt 交给大模型生成回答。向量存储是中间那个绕不开的零件所以很多人默认它必须是一个专门的向量数据库。但现实约束往往不支持这个默认。公司不会为了一个内部知识库系统就引入一套全新的基础设施Milvus 最低配置也要几台机器ES 要装插件要调分片Weaviate 或 Pinecone 这类托管服务又涉及网络和合规问题。就算这些都打通审批、部署、监控、排障每一环都在消耗团队精力。更尴尬的是很多项目的向量数据量根本没有大到需要专用引擎的程度——十万级分段、日均几百次检索用专业向量库属于杀鸡用牛刀。Spring AI 官方支持的 VectorStore 实现里Redis 是部署成本最低的一种。团队里只要有人会看 Redis 的 info 和 slowlog这套系统就算有人能接管了。不用招新人不用新申请机器不用跟 DBA 扯皮这是我这套方案最现实的价值。1.2 Redis 向量检索的原理边界Redis 做向量检索靠的是 RediSearch 模块这个模块在 Redis Stack 里默认内置。它会给向量字段构建 HNSW 索引支持 KNN 查询也支持按 metadata 字段做条件过滤。Spring AI 的 RedisVectorStore 正是把索引创建、向量写入、相似度搜索这些操作封装成了 Java API所以你不需要手写一条 FT.CREATE 或 FT.SEARCH 命令。我不建议你在这层原理上钻太深但有三件事必须知道Redis 支持 HNSW 和 FLAT 两种向量索引类型支持 COSINE、内积、L2 三种距离度量数据全部常驻内存容量受单机内存限制。这意味着它天然适合中小规模场景而不是千亿级向量检索平台。我做过一次简单压测10 万条 1536 维向量写入 RediSearch单条 KNN 查询的 P99 在 5 毫秒左右召回质量跟专业向量库在同等参数下没有肉眼可见的差别。很多外部认知说Redis 不适合做向量检索那是拿它跟分布式向量库比架构天花板而不是比中小场景的实际效果。技术和方案只有合不合适没有绝对的高低。2. 环境准备与 Spring AI Redis 的基础接线2.1 Spring Boot 工程里的依赖配置我用的技术栈是 Spring Boot 3.2 Spring AI 当前稳定版本。Spring AI 的版本迭代很快API 在不同大版本之间有调整下面代码里的包名和构建器写法以你本地实际依赖的版本为准但整体思路是一致的。pom.xml 里需要加两个关键依赖spring-ai-vector-store-redis 和 spring-ai-starter-model-openai或者你实际用的其他 Chat 模型 starter。dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-vector-store-redis/artifactId version${spring-ai.version}/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId version${spring-ai.version}/version /dependencyEmbedding 模型和 Chat 模型共用同一个 OpenAI 兼容配置但要注意两点Embedding 的维度直接决定了 Redis 索引结构一旦跑起来再换模型索引必须重建Chat 模型的选择会影响回答质量和成本知识库问答场景建议用上下文窗口大一点的模型否则片段拼多了容易截断。2.2 Redis Stack 启动与连接参数原生 Redis 不带 RediSearch 模块所以最省事的做法是直接用 Redis Stack 镜像启动它已经把模块内置好了。我们的测试环境和生产环境都用 Docker 起一条命令搞定docker run -d --name redis-stack -p 6379:6379 redis/redis-stack:latestapplication.yml 里配置连接信息。Lettuce 是 Spring Boot 默认的 Redis 客户端够用但要注意连接池参数不能照抄单机小 Demo。spring: data: redis: host: localhost port: 6379 timeout: 3s lettuce: pool: max-active: 16 max-idle: 8 min-idle: 2向量写入时如果一次性导入大量分段连接池太小会出现获取连接超时。我一开始用默认连接池导 2 万分段直接超时报错把 max-active 调到 32 之后才稳定。生产环境建议把 min-idle 设成 2 以上避免突发流量打过来时冷启动建连接。3. 从文档导入到检索问答的核心实现3.1 文档载入和中文场景的切分策略文档读取直接用 Spring AI 的 DocumentReader 实现PDF、Word、TXT 都有对应封装下面是 PDF 的示例import org.springframework.ai.reader.PagePdfDocumentReader; PagePdfDocumentReader reader new PagePdfDocumentReader(classpath:/docs/manual.pdf); ListDocument documents reader.get();文档载入之后最关键的步骤是切分这一步直接影响 RAG 的召回质量。Spring AI 自带 TokenTextSplitter按 token 数切分并支持重叠import org.springframework.ai.transformer.splitter.TokenTextSplitter; TextSplitter splitter new TokenTextSplitter(500, 100, 5, 10000, true); ListDocument chunks splitter.apply(documents);切分参数里我做了调整默认 chunkSize 是 800中文语境下建议降到 500 左右。原因很简单中文一个词大约对应 1 到 1.5 个 token800 token 大约是一大段文字切出来的片段信息密度高但语义容易发散500 token 是经验和效果折中后的值。overlap 保留一点重叠字符可以缓解切分位置恰好切断关键句子的问题。3.2 向量入库与索引的自动创建先把 VectorStore 的 Bean 定义好。我用的是 Spring AI 1.0 的 builder 风格如果你用的老版本是构造函数直接 new换成上面这种写法即可import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.vectorstore.RedisVectorStore; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; Bean public VectorStore vectorStore(LettuceConnectionFactory connectionFactory, EmbeddingModel embeddingModel) { return RedisVectorStore.builder(connectionFactory, embeddingModel) .indexName(knowledge-index) .prefix(doc:) .distanceType(RedisVectorStore.DistanceType.COSINE) .indexType(RedisVectorStore.IndexType.HNSW) .embeddingDimensions(1536) .build(); }embeddingDimensions 必须跟 Embedding 模型输出维度一致OpenAI 的 text-embedding-3-small 是 1536 维。如果配错写入时不会报错但检索时向量距离计算完全没意义。导入文档时直接调 add 方法。第一次 add 时 Spring AI 会自动执行建索引命令也就是 RediSearch 的 FT.CREATE后面再 add 就是增量写入Service public class DocumentImportService { private final VectorStore vectorStore; public void importDocument(String path) { PagePdfDocumentReader reader new PagePdfDocumentReader(path); ListDocument documents reader.get(); TokenTextSplitter splitter new TokenTextSplitter(500, 100, 5, 10000, true); ListDocument chunks splitter.apply(documents); vectorStore.add(chunks); System.out.println(导入完成切分片段数量: chunks.size()); } }metadata 里的字段会同步映射到 Redis 索引所以导入前最好统一给每篇文档打上 docId、category、title 之类的公共字段后面检索过滤和去重都靠它们。3.3 检索召回和问答链路的组装检索这一步的关键参数是 topK 和 similarityThreshold。我的经验是先不过滤靠 topK 召回再做应用层重排原因后面专门讲。检索代码很简单Autowired private VectorStore vectorStore; Autowired private ChatClient chatClient; public String answer(String question) { SearchRequest request SearchRequest.builder() .query(question) .topK(5) .similarityThreshold(-1) .build(); ListDocument contexts vectorStore.similaritySearch(request); String prompt 你是一个知识库助手。请只根据以下资料回答用户问题。 如果资料里没有相关内容请直接回答“不知道”不要编造。 资料 %s 用户问题%s .formatted( contexts.stream() .map(Document::getText) .collect(Collectors.joining(\n\n---\n\n)), question); return chatClient.prompt().user(prompt).call().content(); }Prompt 里的不知道就说不知道不是客套话是防止大模型在检索结果为空时强行生成编造内容。生成回答前宁可让用户多等一次检索重试也不要让它一本正经地胡说。4. 生产级调优从“能跑”到“跑得好”4.1 HNSW/FLAT 选择与索引重建Redis 的 HNSW 索引是近似最近邻搜索速度快但索引构建有额外内存开销FLAT 是暴力计算数据量小时精度最高但查询耗时随数据量线性上涨。我实测下来十万级分段用 HNSW 几乎感知不到精度损失查询速度比 FLAT 快一个数量级所以生产环境无脑选 HNSW 即可。真正容易踩坑的是换 Embedding 模型。比如从 1536 维模型换成 1024 维模型旧索引和旧向量还留在 Redis 里新数据写入会报维度不一致的错。此时必须手动删掉旧索引让 Spring AI 重新创建redis-cli -p 6379 FT.DROPINDEX knowledge-index DDDD 后缀表示连文档一起删掉。删完之后重启应用重新跑一遍导入流程。这个操作通常放在凌晨低峰期做因为删除期间知识库问答服务是不可用的。4.2 similarityThreshold 的真实语义这是我在生产环境调优时踩得最深的一个坑。Spring AI 的 similarityThreshold 不是相似度而是 Redis 返回的向量距离值。在 COSINE 距离下distance 1 - cosine_similarity也就是说两个向量越相似这个值越接近 0。similarityThreshold 值Redis 实际行为常见理解误区-1不过滤只按 topK 返回很少人知道这是默认值0.2只接受距离小于 0.2 的向量即余弦相似度高于 0.8误以为相似度 20%0.8接受距离小于 0.8 的向量即余弦相似度高于 0.2误以为相似度 80%1.5基本放行大部分向量距离范围上限是 2我一开始设置 0.7以为相似度要超过 70%结果检索结果经常为空因为实际召回片段里大部分距离都在 0.3 到 0.6 之间被阈值一刀切了。后来把日志打开打印每个片段的距离值才意识到这个参数是被直接透传给了 Redis 搜索命令。如果要过滤建议先收集一批真实查询的 distance 分布取一个能保留大部分有用结果的分位值如果没时间分析就先设成 -1 不过滤靠 topK 和后续重排控制质量。4.3 批量导入、幂等去重与检索缓存一次性导入大量文档时Spring AI 的 add 方法已经封装了批量写入逻辑不需要自己手写 pipeline。我实测导入 1 万条 500 token 的分段大概几秒就能完成瓶颈反而在 Embedding API 的调用限流上。如果用的是远程 Embedding 服务建议在导入程序里做简单的限速比如每 50 条 sleep 100 毫秒避免触发限流重试。重复导入同一份文档是个容易忽视的问题。没有幂等控制的话同一内容会被向量化多次检索时同一个片段反复出现占满 topK 名额回答质量直线下降。我的做法是在导入入口处查一次 Redis 里的 docId metadatapublic void importDocument(String path, String docId) { // 先查是否已存在该文档的向量 ListDocument exists vectorStore.similaritySearch( SearchRequest.builder() .query() .topK(1) .similarityThreshold(-1) .filterExpression(docId docId ) .build()); if (!exists.isEmpty()) { return; } // 真正的导入逻辑 }检索入口建议加一层短效缓存。同一个高频问题在 5 分钟内反复问没必要每次都调 Embedding 和 Chat 接口。Spring Cache 加 Redis 是很成熟的组合Cacheable(cacheNames rag_answer, key #question, unless #result.contains(不知道)) public String answer(String question) { // 完整检索生成逻辑 }注意 unless 条件模型回答不知道的缓存没有意义直接跳过。还要防止缓存穿透——恶意或异常的高频问题会每次都打到底层 Embedding API费用很痛。我的方案是给没有答案的问题也缓存一个未命中标记过期时间设短一点比如 10 分钟。5. 我实测踩过的坑从序列化到中文切分5.1 最阴间的序列化异常有段时间导入 PDF 后一调检索就报错异常信息里是 Jackson 序列化失败。排查到最后发现罪魁祸首是 metadata 里放了一个 LocalDate 字段。Spring AI 在把 metadata 写入 Redis 时默认用 JSON 序列化LocalDate 默认序列化格式在 RediSearch 的 TAG 字段里解析不了。解决方式很简单所有 metadata 值统一存成 String。日期提前格式化嵌套对象提前转 JSON 字符串。你在设计 Document 结构时最好就约定好这一点凡是进 metadata 的必须是可以安全 JSON 化的基本类型。这个坑排查起来完全不直观因为报错点不在写入时而在偶发读取时。5.2 metadata 结构不一致引发的静默漏检另一个更隐蔽的坑是不同批次的文档 metadata 字段不一致。比如第一批文档有 category 字段第二批文档漏了这个字段。RediSearch 的 TAG 索引对字段缺失的文档采用的是过滤时匹配不到策略不是报错而是静默漏掉一批本该命中的文档。这个问题非常难发现因为线上检索结果少了一部分但不仔细对比不会意识到是过滤条件导致的。我现在强制要求所有文档在入库前经过一层转换把 metadata 统一成固定 schema缺失字段补默认值Document normalized Document.builder(doc.getText()) .metadata(docId, doc.getMetadata().getOrDefault(docId, )) .metadata(category, doc.getMetadata().getOrDefault(category, default)) .metadata(title, doc.getMetadata().getOrDefault(title, untitled)) .build();代价是每个字段都要显式定义一遍但换来的是检索结果可预期。5.3 中文文本被切在句子中间TokenTextSplitter 按 token 切分遇到长段落会在句子中间硬切。英文还好中文一旦把一个完整语义单元拦腰切断检索召回的片段就变成半句话大模型拿这种上下文生成回答质量很差。我做过一轮对比同一个小节固定 token 切分成 6 段按空行段落切分成 3 段后者回答质量明显更稳定。原因是同一个问题的关键信息往往集中在一两个语义完整的段落里而不是分散在 6 个被腰斩的 token 块里。如果你要处理的是规范的 Markdown 或纯文本文档建议自定义一个简单的段落切分器按连续空行切再过滤掉太短的碎片ListDocument chunks documents.stream() .flatMap(doc - Arrays.stream(doc.getText().split(\\n\\s*\\n)) .filter(segment - segment.trim().length() 50) .map(segment - Document.builder(segment.trim()) .metadata(doc.getMetadata()) .build())) .toList();这种切分方式对结构化文档特别友好代价是每段可能略长需要控制好 Prompt 里塞进去的片段数量避免超 token 限制。5.4 Embedding 维度切换导致整库不可用有一次我为了省成本把 Embedding 模型从 text-embedding-3-small 换成了另一个输出 1024 维的模型结果新数据写入一直报维度错误。原因是旧索引还是按 1536 维建的新向量往旧索引里塞RediSearch 直接拒绝。处理方式就是上面说的 FT.DROPINDEX 后重建。但更想提醒的是在生产环境切换 Embedding 模型前先确认你的应用配置里 embeddingDimensions 是否同步改掉再确认 Redis 里已有旧数据能否接受一次性清空。最稳妥的做法是换模型时换一个 indexName 或换一个 Redis 库新老索引并行跑验证结果符合预期后再把旧索引下线。6. 数据量涨到什么程度才需要换专门的向量库6.1 内存预算的账怎么算Redis 向量检索做的是内存生意预算必须心里有数。单条向量的裸大小大约是维度乘 4 字节。1536 维就是 6144 字节约 6KB。100 万条向量就是 6GB 裸数据加上 HNSW 索引结构和 metadata 的额外开销我一般按 2.5 倍估算也就是 100 万分段大概需要 15GB 内存。内部知识库文档量通常撑死几十万分段一台内存 32GB 的 Redis 实例完全能扛住。但如果你做的是面向 C 端的高频问答数据量奔着千万级去光是内存成本就不划算了专业向量库能落盘、能分布式扩展机器成本反而低。6.2 混合检索、复杂过滤和生产运维是硬门槛我目前的方案里检索是纯向量距离排序。如果业务需要全文关键词匹配 向量相似度混合打分或者复杂的多条件加权排序Redis 的 RediSearch 虽然支持全文索引但综合排序能力和专业搜索引擎差距明显。这种需求通常出现在电商商品搜索、UGC 内容推荐这类高并发复杂召回场景。另外多租户隔离是个很容易被忽略的点。如果最终系统要接几十个租户每个租户的数据量不大但过滤条件复杂Redis 的 TAG 过滤可以做但运维上管理一大堆索引和前缀会很别扭。专业向量库大多数是云端托管或分布式架构隔离和扩展能力成熟很多。6.3 我的换库判断标准给个直接可用的决策清单满足以下大多数条件可以继续用 Redis分段数量低于 100 万QPS 在几百以内业务方不需要复杂排序团队熟悉 Redis 运维。反过来如果数据量千万级、检索 QPS 上千、需要全文和向量混合召回、或者需要稳定的分布式部署那就别纠结直接选 Milvus、ES 或托管向量库。对比维度Redis Spring AI独立向量数据库部署成本复用已有 Redis几乎为零新集群运维复杂数据容量内存受限适合百万级分段以内支持分布式扩展混合检索全文向量可用但排序弱专业方案成熟查询延迟单机内存检索毫秒级网络开销跨分片有延迟团队认知度Java 后端普遍熟悉需要学习曲线我做这套方案的核心逻辑很简单基础设施的复杂度要和业务规模匹配。一个内部知识库问答系统用重启都要等半天的分布式向量库属于过度设计。拿现有的 Redis 把 RAG 跑起来把业务价值先交付了等到数据量真正涨上来再按上面的标准迁移也不迟。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询