
面试现场面试官往椅背上一靠抛出一个特别现实的问题你们公司那套系统是老架构——MySQL 加 RedisSpring Boot 跑了好几年连 PostgreSQL 都没有。现在老板说必须接大模型。你怎么干这个问题的坑不在技术在你上来就答先上一套向量数据库。面试官听完基本就凉了老系统能让你随便动架构吗运维同意吗预算批得下来吗上线窗口在哪存量系统接 AI考的不是你会不会搭向量库考的是你会不会按改造成本排优先级知道每一步的天花板在哪。这篇文章给你一条最稳的路径老架构一行不改旁挂一个 AI 微服务再配一个独立的向量库。一、先别急着选型你要的到底是哪件事很多人一开口就聊向量数据库其实压根没想清楚需求。老系统要接大模型无非三种-A. 只想要模型的嘴文本生成、改写、总结、分类、抽取。数据从哪来你自己用老办法查出来喂进去就行了。这种需求一个向量数据库都不需要。-B. 想要模型的知识让模型基于你的私有文档回答问题。这才是 RAG才需要向量检索。-C. 想要模型的手让模型调用你的业务接口查订单、改状态。这是 Function Calling / Agent 的活。关键结论国内绝大多数老系统的第一个 AI 需求是 A不是 B。客服话术、工单摘要、评论分类、报表解读——全是 A。上来就冲着 B 去搭向量库是把简单问题做复杂了。所以正确的顺序是先看需求属于哪一类再决定要不要动数据层。二、第 0 层零新依赖把模型当上下文翻译器这一层的思路很简单老系统负责查事实模型负责说人话。数据还是从 MySQL 走原来的 SQL 查出来拼进 Prompt让模型基于这些事实组织语言。不建向量库不改表结构不动 Redis。先看链路Java 侧用 Spring AI代码短到你会怀疑人生Service public class LegacyAssistant { private final ChatClient chatClient; private final JdbcTemplate jdbc; // 老系统本来就有的东西 public LegacyAssistant(ChatModel chatModel, JdbcTemplate jdbc) { this.chatClient ChatClient.builder(chatModel).build(); this.jdbc jdbc; } public String askAboutOrder(String orderNo, String question) { // 1) 用老办法把事实查出来还是原来那套 SQL ListMapString, Object rows jdbc.queryForList( SELECT order_no, status, amount, created_at FROM t_order WHERE order_no ?, orderNo); // 2) 拼成一段纯文本顺手做脱敏和截断 String facts rows.stream() .map(r - 单号: r.get(order_no) 状态: r.get(status) 金额: r.get(amount) 下单: r.get(created_at)) .collect(Collectors.joining(\n)); // 3) 交给模型只让它依据 facts 回答不许自由发挥 return chatClient.prompt() .system(你是客服助手。只能依据 facts 里的内容回答 信息不足就直接说不知道禁止编造。) .user(u - u.text( facts {facts} /facts 用户问题{q} ).param(facts, facts).param(q, question)) .call() .content(); } }这段代码能跑是因为ChatClient、ChatModel都是 Spring AI 现成的 BeanJdbcTemplate是你项目里本来就有的。整段代码零新组件。这一层能扛什么、扛不住什么心里要有一杆秤-能扛低频的问答、摘要、分类、内容生成。数据量小、对延迟不敏感的场景完全够用。-扛不住需要从海量文档里找相关片段的需求。你没法把十万篇文档塞进 Prompt上下文窗口和 Token 成本卡得死死的。-必须做的三件事① 脱敏别把手机号、身份证、金额明细直接喂给外部模型② 截断控制单次输入长度超长就分段或只取摘要③ 兜底模型超时或报错要有降级路径不能让它把主流程拖死。一句话记住第 0 层不是凑合方案它是成本最低、上线最快、风险最小的第一步。很多老系统的 AI 需求到这一步就结束了。三、主力方案旁挂一个 AI 微服务配一个独立向量库需求升级到要基于私有文档回答就得做检索了。这时候最稳的做法不是去改老系统而是旁挂。为什么是旁挂微服务而不是改造老系统把 AI 能力塞进老系统里听起来省事其实是给自己挖坑-老系统一行不改业务逻辑、表结构、部署流程全都不动风险被隔离到最小。改老系统意味着回归测试、上线窗口、连带故障全是成本。-新服务独立扩缩容AI 流量和业务流量互不干扰。大模型调用一慢也只影响 AI 服务拖不垮交易主链路。-出问题能一键回滚AI 服务整个下线老系统的原有功能照样跑。-技术栈解耦老系统是 JDK 8 Spring Boot 2 不敢动新服务直接上 JDK 17 最新 Spring AI各玩各的。链路长什么样1. 老系统照常写 MySQL、跑 Redis一个字都不改。2. 新增一个ai-serviceSpring Boot Spring AI它只干三件事检索、拼 Prompt、调模型。3. 文档切片和向量存到一个全新的 PostgreSQL pgvector 库里——注意这是新库不是老库老库还是 MySQL。4. 需要实时数据时ai-service 通过 HTTP 调老系统新暴露的查询接口按需拉取保证数据新鲜。切片与入库新库独立不动老库pgvector 只是 PostgreSQL 的一个扩展建表很直接CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE kb_chunk ( id BIGSERIAL PRIMARY KEY, doc_id BIGINT NOT NULL, tenant VARCHAR(64) NOT NULL, -- 用于权限过滤 content TEXT NOT NULL, embedding vector(1536) -- 维度要和 embedding 模型对上 ); -- 建议先灌数据、再建索引边建边灌很慢 CREATE INDEX idx_kb_chunk_vec ON kb_chunk USING hnsw (embedding vector_cosine_ops) WITH (m 16, ef_construction 64);向量是应用侧算好再写进来的pgvector 只负责存和查。这块的细节——HNSW 参数怎么调、三种距离算子怎么选、Spring AI 怎么接 pgvector——我在第 26 篇已经掰开揉碎讲透了这里不重复只说存量改造上真正独有的部分。实时数据老服务暴露接口ai-service 按需拉老系统只需增加一个只读查询接口代码短得不能再短RestController public class LegacyDataApi { // 挂在老系统里 private final JdbcTemplate jdbc; // 老系统原有组件 GetMapping(/api/tickets/latest) public ListMapString, Object latest(RequestParam String userId) { return jdbc.queryForList( SELECT ticket_no, status, updated_at FROM t_ticket WHERE user_id ? ORDER BY updated_at DESC LIMIT 5, userId); } }为什么是按需拉不是同步进向量库因为实时数据变得太快。工单状态一分钟变三回你同步进向量库就开始滞后用户问我单子到哪了模型拿着十分钟前的状态回答比不回答还糟。实时数据永远走接口现查向量库里只放那些变化慢的知识类文档。ai-service 侧检索 实时 拼 PromptService public class KnowledgeAssistant { private final VectorStore vectorStore; // Spring AI pgvector private final ChatClient chatClient; private final RestClient legacyClient; // 调老系统的接口 public KnowledgeAssistant(VectorStore vectorStore, ChatModel chatModel, Value(${legacy.api.base}) String legacyBase) { this.vectorStore vectorStore; this.chatClient ChatClient.builder(chatModel).build(); this.legacyClient RestClient.builder().baseUrl(legacyBase).build(); } public String ask(String userId, String question) { // 1) 知识类内容从独立向量库检索相关切片带权限过滤 ListDocument docs vectorStore.similaritySearch( SearchRequest.builder() .query(question) .topK(5) .filterExpression(tenant userId ) .build()); // 2) 实时内容调老系统接口现拉保证不过期 String live legacyClient.get() .uri(/api/tickets/latest?userId{u}, userId) .retrieve() .body(String.class); String context docs.stream() .map(Document::getText) .collect(Collectors.joining(\n---\n)); return chatClient.prompt() .system(只能依据 docs 和 live 的内容回答 没有依据就说不知道禁止编造。) .user(u - u.text(docs{d}/docs\nlive{l}/live\n问题{q}) .param(d, context).param(l, live).param(q, question)) .call() .content(); } }三个要点面试官很爱追-权限过滤走filterExpression在向量库这一步就把不属于该用户的切片挡掉别等检索完再在内存里筛——既慢又容易漏。-实时数据走接口现查知识文档才进向量库两条数据源职责分清。-接口要有超时和兜底老系统的接口一旦抖动ai-service 要能降级用缓存、或只答知识部分别把整个问答拖死。四、Redis 别当向量主库但语义缓存是真香铺垫了这么久回过头回答一个高频追问老系统本来就有 Redis为什么不直接拿 Redis 当向量库因为对存量系统这个前提来说它不够站得住-版本门槛老的开源 Redis 不带模块压根没有向量能力。要用得换Redis Stack或者升级到Redis 8的原生 vector sets。你手上那台跑了好几年的 Redis 大概率不是——那这就不叫不加组件了。-纯内存成本随规模陡增按 float32 算1536 维一条向量约 6KB100 万条光原始向量就 6GB再加 HNSW 图结构的开销一台十几 G 内存的机器直接吃满。几十万条以内很香上千万条就该上独立向量库了。-高可用和集群要自己扛主从、持久化、集群分片下的索引一致性用 Redis 做向量比用专用库要多操不少心。所以对存量系统把向量检索交给独立的 pgvector 库比硬榨 Redis 更干净。不过 Redis 有一个技能对老系统是性价比之王——语义缓存。用户问退货要几天和几天内能退传统缓存当成两个 key各调一次模型语义缓存用向量比对判定是一回事直接返回缓存结果。不上任何新组件就能砍掉一大截模型调用成本。SemanticCacheAdvisor cacheAdvisor SemanticCacheAdvisor.builder() .cache(semanticCache) // 底层还是 Redis 向量检索 .build(); String answer ChatClient.builder(chatModel) .build() .prompt() .user(question) .advisors(cacheAdvisor) // 命中就走缓存绕过模型 .call() .content();一句话Redis 拿来省模型调用不拿它扛向量主库。五、数据量和并发上来之后CDC 旁路增量 回填主方案跑起来后会冒出一个新问题文档更新了向量库怎么跟着更新如果文档变动不频繁最简单是定时任务重新灌。但数据一多、要求一变高就得上真正的增量链路CDCChange Data Capture旁路同步——主库一行不改从 binlog 里偷变更。链路是这样的1. 业务照常写库一个字都不用改。2. 用 CDC 工具订阅 binlog——国内常用Canal也有人用Debezium。3. 消费变更消息把新增/修改的文档送去重新切片、算 embedding。4. 写入独立向量库旧系统这边只多一个 HTTP 客户端。这条链路能把风险隔离得最干净向量库挂了不影响业务重建索引不用停机模型换了一边重灌一遍就行。但旁路同步有四个必须处理的现实问题面试官很爱追-同步延迟binlog 消费是有延迟的。刚改完的数据马上问模型可能还拿着旧答案。要么接受这个窗口要么在关键路径上做读己之写的兜底。-回填backfill系统上线那天存量数据怎么一次性灌进去得有一套能断点续跑的初始化脚本。-幂等binlog 可能重放同一条记录被消费两次不能插出两份向量。用业务主键做去重。-删除上游删了数据向量库里的也得跟着删否则模型会拿已死的文档回答问题。这块最容易被漏掉。六、真正决定能不能上生产的是这几件横切事方案搭起来只是及格能不能上线看的是下面这些-降级大模型接口挂了怎么办必须有兜底——退回关键词检索、退回规则话术、退回上一条缓存。让 AI 功能的失败不要变成业务功能的失败。-灰度开关新功能一定要有开关能按用户、按流量比例放量出问题一键关掉。-成本与限流给模型调用加限流和配额别让一次异常流量把账单刷爆。语义缓存也是在这里发力的。-权限与脱敏喂给模型的数据要按权限过滤敏感字段要脱敏。老系统的权限模型本来就在接 AI 时别绕过去。-可观测把每次调用的耗时、Token 数、命中缓存的比率都打点不然出了问题两眼一抹黑。这五条听着像运维八股但它们才是第 0 层方案和生产级方案的真正分界线。第二章那段代码能不能变成线上功能差的就是这些。七、落地顺序从第 0 层走到 CDC把上面的东西串成一条可执行的路径1.先做第 0 层挑一个高频、低风险、错了也不出事的场景比如客服话术、工单摘要。上线收集真实使用数据。这一步验证的是这个需求到底有没有用。2.需求升级到文档检索再旁挂 ai-service 独立 pgvector 库实时数据走老系统接口现查。这一步验证的是检索质量够不够用。3.数据量或更新频率压不住才上 CDC 旁路增量加存量回填。这一步验证的是成本和收益划不划算。4.全程把第六节那五件横切事补齐缺一件都别急着放量。每一步都有明确的触发条件没到就不往下走。这就是按改造成本排优先级的具体含义。 面试官视角的标准回答被问存量老系统怎么接大模型可以这么答我不会一上来就动架构。先看需求属于哪一类——只是要模型的表达能力还是要基于私有知识检索还是要让模型调业务接口。多数老系统的第一个需求其实只是第一类用现有的 SQL 查数据、拼进 Prompt 就够了一个组件都不用加。brbr需求升级到文档检索我的做法是旁挂新增一个 AI 微服务跑 Spring AI切片存到独立的 pgvector 库老系统一行不改实时数据让老系统暴露只读接口ai-service 按需现拉不进向量库——因为实时数据进库就滞后了。brbr有人会说用现成的 Redis 做向量库但存量系统那台 Redis 往往不带向量能力要换 Stack 或升 8等于还是改架构而且纯内存规模一上来成本陡增。所以向量检索我交给独立库Redis 只留着做语义缓存省成本。brbr等更新频率上来再上 CDC 旁路同步做增量。整个过程里我最在意的是降级、灰度、成本、权限和数据一致性这几件横切的事——方案能不能上生产差别就在这些地方不在选哪个向量库。这套回答之所以能打是因为它同时展示了三样东西需求判断力、成本意识、和生产风险意识。面试官要的就是这个。到这儿「面 Java 岗问我 AIGC」这个系列就 27 篇了。从 AIGC 概念一路讲到存量系统落地主线始终是一句话技术选型没有标准答案只有看你手里有什么牌、要解决什么问题。后面如果还有值得挖的场景我们接着聊。