这不是一篇讲RAG原理的文章,而是讲RAG系统上线之后怎么活下来的文章。我做了三年Java后端,近一年半全面转向大模型应用运维,踩过最多的坑既不在模型效果上,也不在代码逻辑里,而在部署、数据链路、连接管理、存储一致性和监控盲区。这个专题以《RAG系统运维与故障排查:从部署到监控的全流程指南》为核心,把我在生产环境从零搭建RAG知识库、并持续维护近一年积累的经验全部拆开,给同样正在做Spring AI + 大模型项目的朋友一条可以直接照抄的路。
1. 生产环境RAG系统:本地Demo与可运维架构之间的鸿沟
1.1 为什么本地跑通了一切,一上线就变成事故现场
我先说一个绝大多数RAG项目都会遇到的场景:开发在本地用Spring AI写了一个最小闭环,文档加载、切分、向量化、检索、调用大模型生成回答,全链路通,效果也不错。测试环境也没问题。结果部署到生产,第一天还好,第二天文档内容更新后ES里出现了成百上千条重复向量,检索结果全是同一篇文章的不同片段,回答质量跳崖式下跌。
这个问题几乎和RAG本身的效果无关,纯粹是运维设计缺失。本地Demo是单机、小数据量、人工触发重建的玩具;生产环境是分布式、持续写入、并发查询、依赖多个外部服务的真实系统。两者的差别集中在七个地方:数据写入的幂等性、连接池和重试机制、资源配额与弹性、日志与链路追踪、权限与安全、监控告警、以及配置管理。任何一项没做,系统都能跑,但总有一天会以事故的方式提醒你它存在。
我在这个专题里反复强调一句话:RAG系统比普通Web应用多出来的复杂度,几乎全部来自两条链路——一条是文档写入的离线链路,一条是问答检索的在线链路。离线链路要解决"数据怎么进得去、进得对、不重复",在线链路要解决"请求怎么稳定出去、快速回来、答得准"。运维的一切动作都是围绕这两条链路展开的。
1.2 核心组件选型:Spring AI、向量库与模型服务的边界
Java技术栈做RAG,目前的主流组合绕不开Spring AI。Spring AI 1.x/2.x提供了完整的RAG抽象层:DocumentReader、TextSplitter、EmbeddingModel、VectorStore、QuestionAnswerAdvisor,配合Spring AI Alibaba 1.x系列还能把阿里云的通义系列模型接入进来。相比LangChain4j与Spring AI Alibaba,Spring AI的生态明显更贴合Java开发者的心智,而且它把很多容易被忽略的细节封装成了Spring Boot风格的自动配置。
但是组件选型永远不是技术越新越好,而是运维越省心越好。我从运维角度推荐的组合是:
- 后端服务:Spring Boot 3.x + Spring AI
- 向量库:Elasticsearch(生产环境用官方的dense vector search功能)
- 模型服务:API优先,本地模型用Ollama兜底
- 缓存与状态:Redis + Redisson
- 任务调度:Spring本身的事件机制 + xxl-job(用于批量重建向量索引)
- 可观测性:Prometheus + Grafana + Loki + SkyWalking
这套组合的核心理由是:你在生产环境做故障排查时,社区资料最多、踩坑记录最全的一定是这些主流组件。选太冷门的技术,出了问题连查都没地方查。
1.3 从Agentic RAG到Ontology RAG:运维视角的架构权衡
这个系列前面几个专题讲了不少RAG的进阶变体,比如Agentic RAG、Ontology RAG、GraphRAG。但我在生产环境见过太多团队把Agentic RAG直接搬到线上,结果运维成本直线上升——多轮工具调用意味着链路变长,每一步都可能超时、失败、返回非法格式,排查多轮调度问题比排查单个问答请求痛苦十倍。
我的建议是:生产环境第一版老老实实做Naive RAG,架构上预留Agentic的扩展点即可。先保证数据链路稳定、检索质量可衡量,再逐步加入路由、多工具调用和反思机制。Ontology RAG可以用于知识密集、实体关系复杂的垂直领域,但它的本体构建本身就是重运维任务,需要保证本体与文档的同步更新,否则会出现本体和文档数据不一致的诡异问题。
运维的本质是控制复杂度。每引入一个进阶机制,都要问自己:崩溃时我能不能在30分钟内定位到根因?如果不能,这个阶段就不该上。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 文档入库与向量化流程:ES中的数据质量是RAG的生命线
2.1 RAG向量化流程拆解:从原始文档到可检索向量的完整路径
RAG向量化流程有很多文章写过,但大部分都在讲算法原理,我要从运维视角讲数据流转和状态管理。
完整的向量化流程是五个环节:
- 文档加载(DocumentLoader):从OSS/S3/MinIO读取原始文件,解析PDF、Word、Markdown、TXT等格式。运维要关注的不是解析效果,而是文件格式异常、超大文件、加密文件这些边界情况。默认超时和内存限制一定要设置,否则一个损坏的PDF就能拖垮整个批量任务。
- 文档切分(TextSplitter):按固定长度、递归字符、语义边界等方式切分。这里的核心运维参数是chunk_size和overlap。调大chunk_size会提高语义完整性但降低检索精度;overlap可以缓解上下文断裂,但会增加向量数量和数据冗余。
- 向量化(Embedding):调用Embedding模型,把文本变成向量。这里是整个流程中唯一产生真实计算成本的地方,也是最需要关注QPS限制和超时的地方。
- 存储(VectorStore):把向量和原始文本、元数据写入向量库。ES的dense vector search是生产环境最稳妥的选择。
- 索引维护:分词器配置、向量维度mapping、刷新频率、副本分片,全部要提前规划。
我在Spring AI里会这样组织代码,用事件驱动的方式把各个环节串起来:
java复制@Component
public class DocumentIngestionPipeline {
private final VectorStore vectorStore;
private final EmbeddingModel embeddingModel;
@EventListener(DocumentUploadEvent.class)
public void handleDocumentUpload(DocumentUploadEvent event) {
Document document = loadDocument(event.getFileUrl());
List<Document> chunks = new TokenTextSplitter(800, 100).apply(document);
// 关键点:在写入之前就计算文档哈希,用于幂等判断
List<Document> enrichedChunks = chunks.stream()
.map(chunk -> {
Map<String, Object> metadata = new HashMap<>(chunk.getMetadata());
metadata.put("docHash", DigestUtils.sha256Hex(document.getContent()));
metadata.put("sourceId", event.getDocumentId());
return Document.builder()
.text(chunk.getText())
.metadata(metadata)
.build();
})
.toList();
vectorStore.add(enrichedChunks);
}
}
这段代码看起来简单,但这里就埋着下一个要讲的大坑:没有幂等控制,每次重新上传同一份文档,向量库里就会多一份数据。
2.2 ES中重复文档的根因与覆盖更新策略
热度词里有一条很典型的问题:"Spring AI将文档作为向量存入ES时每次都会添加一个文档,怎么实现覆盖"。这是所有RAG系统从开发走向生产的必经之路。
根因很简单:Spring AI对VectorStore接口的add方法设计是追加语义,它不做按业务ID去重。如果你在业务代码里直接把切分后的chunks加入向量库,每次执行都会追加一批新向量。本地测试时你无所谓,因为数据量小、效果看不太出来;生产环境文档一多,重复向量会让检索结果被同一篇文档霸占,同时ES存储成本和查询延迟同步上涨。
覆盖策略要从三个层面同时做:
第一,业务层生成稳定ID。每个切分后的chunk必须有一个确定性的ID。我常用的方案是sourceId + "_" + md5(text)。只要原文没变,无论重建多少次,chunk的ID都不变。
第二,利用ES的文档ID天然幂等。ES写入时如果指定_id,同一_id的文档会直接覆盖旧文档。所以只要你把chunk ID映射到ES的_id字段,那么重复插入就变成了upsert。
在Spring AI的ES实现里,可以通过自定义VectorStore或者在插入前先删除旧数据来达到覆盖效果。我自己的做法是封装一个UpsertVectorStore:
java复制@Component
public class ElasticsearchUpsertVectorStore extends ElasticsearchVectorStore {
public ElasticsearchUpsertVectorStore(ElasticsearchClient client,
ElasticsearchVectorStoreProperties properties) {
super(client, properties);
}
public void upsert(String sourceId, List<Document> documents) {
// 同一sourceId下先删除旧向量,再插入新向量
deleteBySourceId(sourceId);
add(documents);
}
private void deleteBySourceId(String sourceId) {
try {
Query query = Query.of(q -> q
.term(t -> t.field("metadata.sourceId").value(sourceId)));
DeleteByQueryRequest request = DeleteByQueryRequest.of(r -> r
.index("your-vector-index")
.query(query));
esClient.deleteByQuery(request);
} catch (IOException e) {
throw new RuntimeException("Failed to delete old vectors", e);
}
}
}
第三,对于全量数据不一致的情况,不要依赖delete-by-query一条条清理,而是采用索引别名切换方案。维护两个索引:knowledge-base-v1和knowledge-base-v2,业务读写流量走别名knowledge-base-active。重建时向v2写入全量数据,写完后把别名原子切换,再删除v1。这套方案已经在我负责的系统里稳定运行了大半年,重建过程中用户无感知,回滚也只需要切换别名。
2.3 增量更新与批量重建:生产环境的数据维护节奏
数据维护不可能永远全量重建,你需要一套增量更新的节奏。我把数据更新分成三个级别,对应三种频率:
- 实时级:知识库里的高频变更文档(比如公告、价格配置),上传后立即触发upsert。
- 定时级:每天凌晨跑一次定时任务,扫描源数据有变更的文档,按
sourceId重建对应的chunk。变更判断依据是源文档的hash值,不要每次全扫全建。 - 版本级:当Embedding模型升级、切分策略调整、向量维度变化时,必须全量重建。这是最容易被忽略的场景——你换了一个Embedding模型,向量维度可能从1536变成1024,旧的向量和新的向量根本没法混合检索,如果只做增量更新,新老数据之间的相似度会完全失真。
我实际维护过的系统还遇到过一个更隐蔽的问题:删除文档。用户在前端删了一篇文档,但你的增量任务没有感知到,向量库里残留了那篇文章的所有chunk,检索还会继续命中已删除的内容。所以在增量更新任务里,必须同时跑一条「孤儿向量清理」逻辑:定期扫描向量库中所有sourceId,和上游文件系统的主数据源比对,把已经没有源文件的向量全部清掉。
3. 连接中断、资源衰竭与K8s故障排查:生产环境的生存实录
3.1 故障排查的基本链路顺序:日志、拓扑、代码
RAG系统的故障表象千奇百怪,但排查路径是有固定套路的。我用的是「三层定位法」。
第一层:看调用链。用户请求进来之后,流量经过了哪些服务?Spring AI应用 -> 模型API/本地模型服务 -> ES -> Redis -> 文件存储。在SkyWalking或Zipkin里把一次请求的Trace捞出来,立刻能看到是哪一跳耗时最高或者直接报错。
第二层:看日志聚合。日志不是用来"看"的,是用来"筛"的。生产环境必须把日志接入Loki或ELK,按traceId聚合。排查时直接按traceId搜索,把一次请求在所有服务上的日志串起来读一遍,80%的问题就已经定位了。
第三层:看资源曲线。如果日志没有明显报错,但系统就是慢,问题往往在资源层:GC停顿、连接池耗尽、线程池拒绝、TCP重传、磁盘I/O抖动。这时候拉起Prometheus里对应的指标,看请求量、延迟、错误率和资源使用率四个曲线的时间重叠关系,基本能锁定根因。
很多运维新手第一时间去看源代码,这是最耗时的做法。日志和指标能告诉你的东西,远比你读代码想象的要多。
3.2 Spring AI连接中断和重连:从HTTP客户端到底层连接池
热度词里有关键词叫"Spring AI连接中断和重连",这绝对是RAG运维高频事故。我见过最典型的现象是:模型API调用偶发超时,错误日志显示连接中断,过几十秒自己恢复。但用户已经因此感受到了明显的回答卡顿。
连接中断产生的原因通常不是网络抖动那么简单,而是连接池和超时配置不当。我逐个排查过这些点:
第一,HTTP客户端超时设置。Spring AI的RestClient/WebClient默认超时在配置里写得比较保守。生产环境建议明确设置连接超时、读取超时和响应超时三个参数。我常用的配置是connectTimeout=3s、readTimeout=60s(大模型生成本身慢)、callTimeout=65s。
yaml复制spring:
ai:
openai:
base-url: ${LLM_API_BASE}
api-key: ${LLM_API_KEY}
chat:
options:
model: qwen-plus
temperature: 0.2
retry:
max-attempts: 3
backoff:
initial-interval: 500ms
multiplier: 2
max-interval: 5s
第二,连接池大小。模型API调用是阻塞式的,连接池太小,并发一高所有请求都在排队等待连接。我遇到过连接池默认5个连接、并发30个请求的情况,直接导致大量请求在等待连接时超时。现在生产环境我把连接池最大连接数调到100,并开启空闲连接存活检测。
第三,优雅处理重试。OpenAI和通义等模型API的限流返回是429或503,Spring AI的retry机制可以处理,但要注意重试退避不能太激进,否则会把整个服务彻底压垮。我的做法是只对可重试的异常(超时、5xx、429)重试,业务型异常不做重试。
第四,本地模型Ollama的断线问题。Ollama本身没有实现请求级的断开重连,当Ollama进程重启或者GPU驱动异常,应用和Ollama之间的HTTP长连接会全部失效。我建议在应用侧做连接池的健康检查,定期用探活请求判断Ollama状态,异常时自动切流到备用模型API。
3.3 K8s环境下的RAG故障排查:从Pod级到服务级
生产环境的RAG系统我强烈建议部署在K8s里,但K8s本身又是新的故障来源。常见的问题集中在四类:
第一,CrashLoopBackOff。RAG应用启动时需要连接ES、Redis、模型服务。如果依赖服务还没就绪,应用会启动失败,不断重启。解决方式不是在应用里反复重试,而是配置合理的readinessProbe和initContainers,让应用在依赖就绪后才开始接收流量。我踩过最深的一个坑是:ES集群在重启后恢复分片需要时间,应用启动后立刻加载索引映射,ES返回的mapping解析失败导致应用崩溃。最后通过initContainer定时探测ES健康状态才解决。
yaml复制initContainers:
- name: wait-for-es
image: busybox
command:
- sh
- -c
- until wget -q -O - http://elasticsearch:9200/_cluster/health | grep -q '"status":"green"\|"status":"yellow"'; do
echo "Waiting for ES...";
sleep 3;
done
第二,OOMKilled。RAG应用是内存大户。向量化大批量文档时,如果同时加载几十个chunk的文本,堆内存会飙升。我在生产环境把Spring Boot的堆内存上限设为容器内存的60%,其余留给JVM的元空间、线程栈和直接内存。另外,批量向量化任务建议单独部署一个worker应用,避免和在线问答服务互相影响内存和CPU。
第三,连接数超限。K8s默认的Service负载均衡是基于TCP连接的四层转发,如果应用建立了大量长连接并复用到后端Pod,连接数会异常膨胀,超出Pod的file descriptor限制。RAG应用尤其容易触发这个问题,因为模型调用、ES查询、Redis读写都可能复用连接。解决方式是调大Pod的文件描述符上限,同时给各个客户端的连接池设置上限。
第四,配置漂移。K8s里最危险的问题是不同环境配置不一致。我用ConfigMap统一管理Spring AI参数、模型API地址、ES地址,所有环境从同一套Git仓库渲染ConfigMap,杜绝测试环境改一处、生产环境忘记改的情况。
3.4 模型服务的部署运维:API优先、Ollama兜底、GPU资源监控
RAG系统里最不稳定的一环往往不是自己的代码,而是大模型推理服务。我处理大模型部署的方式很简单:业务流量优先走云厂商API,本地用Ollama部署开源模型作为成本备份和断网备选。
用API的成本高、稳定性依赖厂商;用本地模型成本低、但如果GPU资源不足,推理延迟会直接拉垮整个问答链路。我实测过Ollama在一个24GB显存的GPU上部署7B模型,单次生成的ttft在300ms左右,一旦并发超过3个请求,排队机制会让后面的请求等待几十秒。所以本地模型必须配好并发控制和隔离,不要让在线问答和离线向量化任务抢同一块GPU。
GPU监控是很多人忽略的点。K8s原生不监控GPU显存使用,你需要部署dcgm-exporter把GPU利用率、显存占用、温度暴露给Prometheus。否则当Ollama进程OOM杀掉GPU进程时,你只能看到"连接中断"这样模糊的错误日志。
大模型接口的调用规范也要提前设计好。我维护了一套接口网关层,统一处理鉴权、限流、模型路由和熔断降级。用户请求进网关后,先做令牌桶限流,再按配置把请求路由到OpenAI兼容接口或Ollama接口。OpenAI兼容协议是目前最通用的标准,Ollama也支持原生OpenAI API格式,切换成本极低。
4. RAG系统的监控指标与质量评估:别让系统变成不可知的黑盒
4.1 技术可运维性指标:延迟、错误率、饱和度
RAG系统的在线服务本质上还是一个高并发Web服务,所以经典的黄金四指标——延迟、流量、错误率、饱和度——依然是监控的地基。
具体到RAG场景,我额外加了三组指标:RAG链路分段耗时(文档查询时间、重排序时间、模型生成时间、总耗时)、向量库服务状态(分片健康度、查询QPS、查询延迟、写入延迟)、模型服务状态(token消耗速率、首token延迟、完整性)。
其中RAG链路分段耗时是最有价值的。用户在客服场景里等10秒会觉得卡,但如果10秒里8秒花在模型生成上,那是正常的业务耗时;如果8秒花在ES查询上,那一定是出了性能事故。没有分段指标,你根本无法回答"这个请求为什么慢"这个最基础的问题。
4.2 RAG知识库质量指标:检索质量与生成质量的度量体系
热度词里反复出现"RAG知识库指标有哪些,如何理解各指标""RAG测评怎么做",这是RAG运维区别于传统系统运维的核心部分。技术可用不代表业务可用,RAG系统必须持续衡量"答得对不对"。
我把它拆成三个层次:
检索层指标:
- 召回率(Recall):标准答案中应该被检索到的文档片段,实际被检索到的比例。
- 精确率(Precision):检索结果中真正相关的文档片段占比。
- MRR(Mean Reciprocal Rank):第一个正确答案出现在第几位的倒数均值,衡量"第一个有效信息找得够不够快"。
- NDCG(Normalized Discounted Cumulative Gain):考虑排序位置的加权指标,适用于多级相关性评分。
生成层指标:
- 忠实度(Faithfulness):生成答案是否严格基于检索到的文档,有没有胡编乱造。这是RAG特有且最重要的一项。
- 答案相关性(Answer Relevance):答案是否切题,是否直接回应用户的问题。
- 上下文相关性(Context Relevance):检索到的上下文是否包含足够多的有效信息。
端到端指标:
- 首token延迟:从用户提问到模型吐出第一个token的时间,用户对"快慢"最直观的感知。
- 完整回答时间:整个回答生成完毕的时间。
- 用户反馈指标:点赞、点踩、采纳率、重新提问率。
实现方式上,离线评测用人工标注的标准问答集+RAGAS等评估框架跑分;在线监控则采集用户真实请求日志,定期抽样做人工复核。我每个月固定做一轮全量离线评测,内容包括新增测试集后的整体得分、每个业务域的得分变化、上个版本以来的效果回归情况。效果回归往往比提升更重要——很多配置改动(切分参数、Embedding模型、检索topK)当时看是优化,一段时间后才发现伤害了某个业务域的检索精度。
4.3 RAG专项监控:提示词配置、数据版本与会话轨迹
RAG系统的监控还有一个特殊性:它引入了"知识版本"和"提示词版本"两个新维度。
知识版本指的是ES向量库当前对应的数据快照版本。每次全量重建或增量更新后,都要把版本号写入一个配置中心或数据库表。查询请求进来时,把版本号打到日志和Trace中。排查"为什么答案变了"时,先确认是不是知识库版本变化引发的。
提示词版本同理。Spring AI的system prompt(System Reminder)直接决定模型怎么组织回答。如果提示词被改动过,同一问题在改动前后的回答质量可能是天壤之别。我会在Spring AI的advisor配置中用占位符读取配置中心里的提示词模板,每次修改都做版本快照,并记录修改人和修改时间。生产环境如果出现回答语气突变,先查提示词是不是被动过。
会话轨迹方面,要把用户问题、检索出的文档ID列表、chunk内容摘要、模型最终回答、以及token消耗全部落库。这是评估和排障的数据基础。每次模型回答出现幻觉或错误,都可以翻出轨迹来分析是检索层没召回到正确内容,还是生成层没忠实引用文档。没有会话轨迹的RAG系统,等于在黑暗中飞行。
4.4 大模型投毒测试与安全运维:比功能故障更隐蔽的风险
热度词里提到"大模型投毒测试",这个在RAG运维里越来越重要。所谓投毒测试,就是人为向知识库中注入恶意或误导性内容,验证系统是否会检索到并被模型采信,从而输出错误答案。这类测试要定期做,不能假设自己团队用的都是"正常文档"就一定没事——知识库的数据来源多了之后,总会有脏数据混进来。
我的做法是每月在测试知识库里注入一组高危险题目:把错误的药物用量、错误的接口地址、错误的配置参数放进文档,然后看问答系统会不会被误导。如果系统原原本本把错误信息答出来,就说明检索层没有做好来源可信度控制,或者生成层没有足够强的「只基于检索内容回答」约束。
对抗措施包括:元数据中标注文档来源级别(官方文档、用户上传、爬虫抓取),在Prompt里明确要求"优先采用高可信来源的信息",以及在检索后处理阶段对低可信来源的结果做降权。这里要特别提醒,提示词里约束「只基于检索结果回答」在Model层面是软约束,不能完全依赖提示词防御,数据源治理才是根本。
5. 跨语言协同与运维细节:Spring AI生态里的那些隐藏边界
5.1 Spring AI与Python服务协同:Java重编排、Python重算法
热度词里有"Spring AI如何和Python交互",这是很多Java团队的困惑。我的经验是:不要追求让Java服务调用Python模型代码,而是把Python擅长的事拆成独立微服务,通过HTTP接口交互。
我们系统的架构是:Spring AI应用负责编排、会话管理、向量检索、提示词组装和输出整改;独立部署的Python服务负责文本预处理、embedding自定义计算和特殊文档格式解析等强算法逻辑。两边只通过RestTemplate或HTTP调用通信,接口定义成OpenAPI规范,互相通过Mock联调。
这样的好处是部署解耦——Java服务升级不影响Python算法迭代,Python环境依赖问题也不会污染Java进程。坏处是多了一个网络调用节点,所以Python服务的超时和降级策略一定要做好,否则Python服务一挂,Java端所有RAG请求都会卡在调用链上。我们给Python服务配置了sentence-transformers模型,embedding请求超时设为2秒,失败时Java端自动回退到Spring AI内置的EmbeddingModel调用云端API,链路抖动不影响主流程。
5.2 配置管理:把Prompt、模型、参数全部外置化
RAG系统里最容易出现配置漂移的地方有三个:Prompt模板、Embedding参数、检索参数。我强制要求这些内容全部外置到配置中心,不允许硬编码在代码里。
检索参数里最值得关注的是topK。这里的topK直接影响回答质量和查询延迟。topK太大,无关内容多,模型容易被噪声干扰;topK太小,关键信息可能漏掉。我在一个客服问答场景里测过,topK从3调到5,答案相关性和上下文相关性双双提升,但ES查询耗时增加了40%。所以必须做量化测试,不要凭感觉调参。
系统级Prompt(System Reminder)也必须进入版本管理。Spring AI里可以通过Advisor统一插入System Prompt,例如:
java复制@Component
public class RagPromptAdvisor implements Advisor {
@Override
public ChatResponse advise(ChatRequest request) {
String systemPrompt = configCenter.get("rag.system-prompt");
var modifiedRequest = ChatRequest.builder()
.messages(request.messages())
.system(systemPrompt)
.build();
return chain.next(modifiedRequest);
}
}
这份Prompt在配置中心里的每个版本都有时间戳和变更说明,出问题可以秒级回滚。
5.3 免费API、模型网关与成本控制
热度词里有"免费大模型API"和"本地部署大模型",这说明很多项目初期都在控制成本。免费API适合开发和测试环境验证流程,但生产环境不要拿免费API扛流量——限流、稳定性、数据安全都是隐患。更靠谱的做法是统一走模型网关,网关后面可以接多个供应商和企业自建的Ollama集群,按模型能力、成本和配额动态路由。
成本控制上,embedding是隐形的费用大头。生产环境每天的文档增量如果不大,embedding调用量也还好;但全量重建时一次性调用几万次embedding,费用很容易被忽略。我的经验是:全量重建前先计算文档chunk总量,估算embedding成本和接口限流时长,选在业务低峰期执行,避免重建任务和生产流量叠加导致双重压力。
5.4 经验清单:我在长期运维中沉淀的18条注意事项
- ES的refresh_interval在批量写入时调大到30秒,写完再调回1秒,可以显著提速。
- embedding模型的维度变更不是小事,一旦变更,所有存量向量必须重建,尽量保持embedding模型版本稳定。
- 日志中必须记录每个chunk的sourceId,否则排查"这条回答来自哪篇文档"时会非常痛苦。
- 向量索引的mapping在创建后不可修改,ES里的向量字段一旦建立就需要新建索引。该问题需要提前规划好。
- 文档切分overlap不要设得过大,否则一个chunk被重复检索的概率上升,用户看到的回答会出现大量重复信息。
- Spring AI的QuestionAnswerAdvisor默认会从检索结果中提取全部内容拼接Prompt,当chunk数量大时,Prompt会超出模型上下文窗口。要对检索结果长度做动态裁剪。
- 项目里接入Agentic RAG时,一定要给每个工具调用加上独立的超时时间,否则任何一个工具卡住都会拖垮整个请求。
- 本地模型Ollama的模型加载会占用显存,多个模型切来切去会导致冷启动延迟,建议按业务场景拆分专属实例。
- 向量化任务的并发不要拍脑袋决定,先压测出embedding接口的QPS上限,再按上限的70%设置并发。
- 所有与模型API相关的敏感配置(api-key、base-url)必须走K8s Secret或配置中心加密,不允许出现在明文环境变量里。
- 生产环境尽量不要用快照版本依赖,Spring AI的版本升级要评估API变动,我踩过升级后VectorStore接口签名变化的坑。
- 灰度发布时先让1%流量切到新版本,观察RAG链路分段耗时和用户负反馈率,确认无异常再逐步放量。
- 对RAG应用做压测时,不要只压在线搜索接口,一定要混合压测「文档上传+增量向量化+在线检索」全链路,因为写入和查询在争抢同一批ES资源。
- 数据库或文件源里的原始文档更新后,长时间不触发重建会导致用户读到旧知识,必须把增量同步做成周期性保障任务。
- 大模型接口返回400错误的原因常常是Prompt超长,Spring AI在拼接完上下文之后要检查token数,超过模型上限就自动截断或减小检索范围。
- 系统提示词里禁止出现绝对化承诺,比如"保证准确",一旦模型回答出错,法律和舆情风险都会落到你头上。
- K8s的Pod优雅终止要配置好preStop钩子和terminationGracePeriodSeconds,否则大量在途请求被直接掐断,用户会看到大面积的连接中断。
- 做RAG知识库测评时,用线上真实请求做回归集的采样时,注意脱敏,用户隐私信息不能进入评测数据集。
以上每一项都来自我真实处理过的问题。这个行业的技术迭代很快,AI框架的版本、模型的选型、组件的最佳实践都在变,但围绕"数据链路可靠、在线服务稳定、质量可度量"这三个目标的运维方法论,很长一段时间内不会变。希望这篇专题能帮你少踩几个坑。
