基于Spring Boot构建生产级AI应用平台:多租户架构与工程实践
1. 为什么“生产级”三个字才是这个项目真正的门槛先把结论摆在前面用 Spring Boot 搭一个能跑通对话的 AI Demo一个下午就够了但要把它做成一个能扛住真实流量、能隔离不同客户数据、能控制成本、能排查线上问题的生产级 AI 应用平台那是完全另一个量级的工程。这个项目标题里最值钱的不是“Spring Boot”也不是“AI”而是“生产级”这三个字。我自己从最早用 Spring Boot 写单体接口到后来接大模型做对话服务再到给多个业务方做多租户的 AI 中台中间踩的坑基本都集中在“Demo 能跑”和“线上能用”之间的那条鸿沟上。Demo 阶段你只需要一个 Controller 调一次模型 API 返回字符串生产阶段你要面对的是模型响应超时怎么降级、同一个租户的并发怎么限流、不同租户的 Key 和配额怎么隔离、对话上下文怎么存、流式输出怎么和网关配合、模型调用成本怎么核算、出问题怎么定位到具体是哪一次请求哪一段 Prompt。所以这篇东西我不打算写成一份“Spring Boot 入门教程”而是按一个真实落地项目的思路把基于 Spring Boot 构建生产级 AI 应用平台这件事拆开讲整体架构怎么设计、多租户怎么落地、Agent 能力怎么接、并发和安全怎么兜底、线上问题怎么排查。适合已经会写 Spring Boot、但还没把 AI 能力真正推到生产环境的同学也适合正在做 AI 中台选型的技术负责人参考。需要提前说明的是下面涉及的具体版本号、参数值、目录结构都是基于当前主流实践给出的合理参考方案不是唯一解。你在自己项目里要结合团队规模、模型供应商、预算来调整但设计思路和踩坑点是通用的。2. 平台整体架构与核心思路拆解2.1 先想清楚这个平台到底给谁用很多人一上来就写代码结果写到一半发现架构根本撑不住需求。我的习惯是先把“谁用、怎么用、用到什么程度”这三件事写清楚再动手。一个生产级 AI 应用平台典型的使用方有三类内部业务团队他们不想关心模型怎么调、Key 怎么管只想拿到一个稳定的对话/生成接口接入自己的业务系统。外部租户多租户场景每个租户有自己的用户、自己的数据、自己的配额彼此绝对不能看到对方的东西。平台运营方也就是你自己需要看每个租户用了多少 Token、哪个模型最慢、哪个接口报错最多。这三类使用方对应三种完全不同的诉求业务团队要简单租户要隔离运营方要可观测。架构设计本质上就是同时满足这三件事。2.2 分层设计把“AI 能力”当成一种可替换的资源我最终采用的是一种偏“中台”思路的分层结构从上到下大致是层级职责关键技术点接入层统一鉴权、限流、路由Spring Security、网关限流、租户识别应用层对话、Agent、知识库等业务编排Spring AI、Agent 编排、会话管理能力层模型调用、向量检索、工具调用模型适配器、Embedding、Function Calling基础设施层存储、缓存、消息、监控MySQL、Redis、向量库、Micrometer这么分层的核心目的只有一个让模型是可替换的。今天你用某个云厂商的模型明天要换成自建的或者要同时接两家做灰度如果模型调用散落在各个 Service 里改起来就是灾难。所以我在能力层做了一个模型适配器抽象上层只认接口不认具体厂商。2.3 为什么选 Spring Boot 而不是别的这个问题我被问过很多次。老实说如果只是做一个纯 AI 推理服务Python 生态确实更顺手。但“生产级 AI 应用平台”的重点在“应用平台”四个字上——它本质上是一个企业级 Web 系统AI 只是它的一个能力模块。Spring Boot 在这个场景下的优势非常明确生态成熟鉴权、事务、缓存、消息、监控这些企业系统必备的东西Spring 全家桶都有现成方案不用自己造轮子。多租户支持好无论是基于 Schema 隔离还是基于字段隔离Spring 的拦截器、AOP、数据源路由都能优雅实现。团队上手快大部分后端团队本来就熟悉 Spring招人、交接、维护成本都低。和 AI 框架衔接顺畅Spring AI 这类框架本身就是为 Spring 生态设计的接入成本比想象中低。提示如果你的团队是 Python 背景硬上 Spring Boot 反而会增加沟通成本。技术选型永远要服务于团队而不是服务于“哪个更酷”。2.4 多租户这个项目里最容易翻车的地方多租户是标题里明确点出来的关键词也是我见过翻车最多的地方。常见的三种隔离方案各有取舍独立数据库隔离最彻底但成本高、运维复杂适合大客户。共享库独立 Schema隔离较好成本适中适合中等规模。共享库共享表 租户字段成本最低隔离最弱适合海量小租户。我的建议是混合策略默认走共享表 租户字段给付费高的大租户提供独立 Schema 的选项。这样既控制了成本又给了大客户安全感。具体实现上我用一个TenantContext基于 ThreadLocal 或 Reactor Context在请求入口处解析出租户 ID然后通过 MyBatis 拦截器自动给 SQL 加上租户条件业务代码完全无感知。这里有个坑必须提前说异步和线程池会丢租户上下文。ThreadLocal 在跨线程时会失效所以要么用可传递的上下文如 TransmittableThreadLocal要么在提交任务时手动把租户 ID 传进去。这个问题我在线上排查了整整一个下午最后发现是某个异步日志任务把租户上下文丢了导致日志串了租户。3. 核心模块的细节解析与实操要点3.1 模型适配器让换模型像换配置一样简单模型适配器的核心是一个接口加多个实现。接口定义要足够抽象只暴露平台真正需要的能力public interface ModelProvider { ChatResponse chat(ChatRequest request); FluxChatResponse streamChat(ChatRequest request); EmbeddingResponse embed(EmbeddingRequest request); String getName(); }然后针对不同厂商写实现类比如QwenProvider、OpenAiProvider、LocalModelProvider。上层业务通过一个ModelProviderFactory根据配置或租户策略拿到具体实现。这么做的价值在于灰度切换和故障降级变得极其简单。当某个模型供应商响应变慢时我只需要在配置中心把对应租户的路由权重调低流量自动切到备用模型业务代码一行都不用改。实操要点每个 Provider 内部要统一处理超时、重试、熔断不要让这些逻辑散落在业务层。请求和响应对象要做归一化不同厂商的字段名不一样统一映射到平台自己的 DTO。流式接口要特别注意背压模型吐字速度和你下游消费速度不匹配时容易把内存打爆。3.2 会话与上下文管理别把历史消息全塞进 Prompt对话类应用绕不开上下文管理。新手最常见的做法是把所有历史消息拼成一个超长 Prompt 发给模型结果就是Token 消耗爆炸、响应变慢、还容易触发模型的上下文长度限制。我的做法是分层管理上下文短期上下文最近 N 轮对话直接进 Prompt。长期记忆把历史对话做摘要或向量化存储需要时检索回来。会话元数据租户 ID、用户 ID、会话 ID、创建时间等存数据库用于审计和统计。具体到存储我用 Redis 存活跃会话的短期上下文设置合理过期时间用 MySQL 存会话元数据和归档消息用向量库存需要语义检索的记忆片段。三层各司其职成本可控。注意上下文裁剪策略一定要可配置。不同模型支持的上下文长度不同硬编码一个数字换模型时必然出问题。3.3 Agent 能力接入从“会聊天”到“会干活”Agent 是这个项目里最能体现“平台”价值的部分。普通对话只是问答Agent 能调用工具、能多步推理、能完成复杂任务。Spring AI 提供了 Function Calling 的基础能力但真正落地时你需要自己解决几个问题工具注册与发现工具怎么定义、怎么注册、怎么让模型知道有哪些工具可用。多步编排一次任务可能需要调用多个工具中间结果怎么传递。执行安全模型决定调用某个工具时怎么防止它调用不该调用的东西。超时与中断Agent 跑飞了怎么及时止损。我的实现思路是每个工具实现一个统一的Tool接口包含名称、描述、参数 Schema 和执行逻辑。平台维护一个工具注册表根据租户权限决定哪些工具对该租户可见。Agent 执行时用一个带最大步数限制的循环来驱动每一步都记录日志超过步数或超时直接中断。这里有个经验工具描述写得好不好直接决定 Agent 的成功率。模型是根据描述来判断该不该调用某个工具的描述含糊模型就会乱调。我一般要求工具描述包含“什么时候用、什么时候不用、参数含义”三部分。3.4 多租户下的配额与限流多租户平台必须做配额管理否则一个租户把资源吃光其他租户全遭殃。我的方案是两级限流租户级每个租户有独立的 QPS 和 Token 配额用 Redis 做计数器。接口级不同接口对话、Embedding、Agent分别限流防止某个重接口拖垮整体。配额的计算要结合模型成本。比如对话接口按 Token 计费Embedding 按调用次数计费Agent 因为可能多步调用要按“预估步数 × 单步成本”来算。这些数据我都存在 Redis 里定时同步到 MySQL 做账单。限流算法我用的是令牌桶因为它允许一定程度的突发流量比固定窗口更平滑。实现上可以用 Redis Lua 脚本保证原子性也可以用现成的限流组件。4. 完整实操流程与关键环节实现4.1 项目骨架搭建先给出我实际用的模块划分这是一个多模块 Maven 项目ai-platform ├── ai-platform-common // 通用工具、常量、异常 ├── ai-platform-auth // 鉴权、租户识别 ├── ai-platform-model // 模型适配器 ├── ai-platform-agent // Agent 编排 ├── ai-platform-chat // 对话业务 ├── ai-platform-admin // 运营后台 └── ai-platform-bootstrap // 启动模块这么拆的好处是职责清晰、依赖单向。common 被所有模块依赖bootstrap 依赖所有模块其他模块之间尽量不互相依赖需要交互就走接口或事件。4.2 租户上下文与拦截器实现租户识别的入口是一个 Servlet Filter 或 Spring 拦截器从请求头比如X-Tenant-Id或 JWT 中解析出租户 ID写入上下文public class TenantContext { private static final ThreadLocalString CURRENT new TransmittableThreadLocal(); public static void set(String tenantId) { CURRENT.set(tenantId); } public static String get() { return CURRENT.get(); } public static void clear() { CURRENT.remove(); } }用TransmittableThreadLocal是为了解决线程池传递问题。然后在 MyBatis 拦截器里自动给查询条件加上tenant_id ?Intercepts(Signature(type Executor.class, method query, args {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})) public class TenantInterceptor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { String tenantId TenantContext.get(); if (tenantId ! null) { // 改写 SQL追加租户条件 } return invocation.proceed(); } }提示SQL 改写要特别小心尤其是带子查询、Union 的复杂语句。我建议对关键表强制走拦截器对统计类查询单独处理避免改写出错。4.3 模型调用的完整链路一次对话请求的完整链路是这样的请求进入Filter 解析租户 ID 和用户身份。限流器检查租户配额超限直接返回 429。会话管理器加载上下文。路由策略选择模型 Provider。Provider 调用模型处理超时和重试。结果写回会话记录 Token 消耗。返回响应异步上报监控指标。每一步都要有日志和指标。我用的监控方案是 Micrometer Prometheus关键指标包括请求量、响应时间 P99、Token 消耗、错误率、各模型调用占比。这些指标按租户维度打标签运营后台可以直接看板展示。4.4 流式输出的实现细节流式输出是 AI 应用的标配体验但实现起来坑不少。Spring 里我用SseEmitter或 WebFlux 的Flux来做。关键点超时设置SSE 连接默认超时时间要调大否则长回答会被截断。异常处理模型中途报错要能优雅地给前端发一个结束事件而不是直接断连。租户上下文流式场景下上下文传递更复杂WebFlux 要用 Reactor Context不能依赖 ThreadLocal。背压下游消费慢时要有缓冲或丢弃策略防止内存堆积。我实测下来流式接口的 P99 延迟比非流式低很多因为首字返回快但资源占用更高因为连接保持时间长。所以流式接口的限流要比非流式更严格。5. 常见问题与排查技巧实录5.1 线上问题速查表现象可能原因排查方向响应突然变慢模型供应商限流或故障看各 Provider 的 P99 指标租户数据串了上下文丢失或 SQL 未加租户条件检查异步任务和拦截器Token 消耗异常高上下文未裁剪或 Agent 死循环看单次请求的 Token 统计流式输出中断SSE 超时或网关缓冲检查网关配置和超时设置内存持续上涨会话未过期或流式背压失效看 Redis 内存和 JVM 堆5.2 几个我踩过的坑坑一异步任务丢租户上下文。前面提过用普通 ThreadLocal 在线程池里会失效。解决方案是换成 TransmittableThreadLocal或者在任务提交时显式传递租户 ID。这个坑的隐蔽性在于它不会立刻报错而是偶发地串数据排查起来很费劲。坑二模型超时没做降级。早期我没给模型调用设超时结果某个供应商抽风时请求全部堆积线程池被打满整个服务雪崩。后来加了超时 熔断 降级三件套才稳住。坑三Agent 死循环。有一次 Agent 在两个工具之间反复调用Token 消耗飙升。后来加了最大步数限制和循环检测才解决。经验是任何自主决策的循环都必须有硬性上限。坑四Prompt 注入。用户输入里如果包含恶意指令可能让模型执行不该执行的操作。我的做法是在 Prompt 组装时对用户输入做转义和隔离同时限制 Agent 可调用的工具范围。5.3 性能优化的几个实用技巧连接池复用模型调用的 HTTP 客户端要用连接池别每次新建。结果缓存相同问题 相同上下文的请求可以缓存尤其是 Embedding 这种幂等操作。批量处理Embedding 支持批量就批量能显著降低调用次数。预热服务启动后对模型连接做预热避免首批请求慢。6. 关于这个平台后续可以怎么扩展写到这里核心的东西基本都覆盖了。最后分享几个我实际在做的扩展方向给有需要的同学参考。一个是多模型协作。现在很多场景不是单模型能搞定的比如一个负责理解、一个负责生成、一个负责审核。平台层面可以支持“模型编排”把多个模型串成一条流水线每个环节用最合适的模型。另一个是成本精细化运营。把每次调用的 Token 消耗、模型单价、租户归属都记录下来做成实时账单。这样运营方可以清楚地知道哪个租户在烧钱哪个功能性价比低。还有一个是评测体系。生产环境最怕的是模型悄悄变差。我搭了一套离线评测流程定期用固定测试集跑一遍对比不同模型、不同 Prompt 的效果数据驱动地做优化决策。我个人在实际操作中的体会是AI 应用平台的技术难点八成不在 AI 本身而在那些“传统”的工程问题上——隔离、限流、监控、降级。把 Spring Boot 这套企业级工程能力用扎实了AI 能力接进来就是水到渠成的事。反过来如果工程底座不牢模型再强也撑不住生产环境。