1. 从一堆“重复造轮子”的痛说起
做过企业级 AI 应用落地的朋友大概都有这种体会:业务部门今天要一个智能客服,明天要一个文档问答,后天又想要个知识库检索。每个需求单独看都不复杂,但真动手做的时候,你会发现每个项目都在重复干同样的事——搭用户体系、接权限、配日志、搞监控、对接模型接口、处理会话上下文、做限流降级。三五个项目下来,团队一半的精力耗在了这些“地基”上,真正跟业务相关的 AI 能力反而没时间打磨。
QuickBlue 就是冲着这个场景来的。它本质上是一套AI 应用底座,把企业做 AI 应用时那些绕不开的通用能力——用户、权限、网关、配置、监控、模型接入、会话管理、向量检索——全部沉淀成标准化的基础服务,让上层业务只需要关注“我这个 AI 应用到底要解决什么问题”。你可以把它理解成盖楼时先打好的地基和框架,后面无论你是盖住宅、写字楼还是商场,水电管网这些基础设施都已经就位了。
这篇文章我打算从实际落地的角度,把 QuickBlue 这类 AI 应用底座的设计思路、技术选型、核心模块、实操要点和踩坑经验完整拆一遍。不管你是正在选型的技术负责人,还是准备动手搭底座的开发同学,都能从中拿到可以直接参考的东西。关键词会围绕QuickBlue、AI 应用底座、微服务、Spring Cloud、JDK 21这些展开,但重点永远落在“怎么做”和“为什么这么做”上。
2. 为什么企业非得要一个 AI 应用底座
2.1 没有底座的世界:每个项目都是一次从零开始的折腾
我先描述一个没有底座的典型场景。团队接到三个 AI 需求:智能工单分类、合同智能审查、内部知识助手。三个项目组各自开工,A 组用 Flask 写了个服务,用户认证直接读公司 LDAP;B 组用 Spring Boot 搭了一套,权限自己写了一套 RBAC;C 组干脆把用户信息硬编码在配置文件里。三个月后,三个系统上线,问题来了:用户要在三个地方分别登录,权限规则各不相同,日志格式五花八门,模型调用超时了没有任何降级策略,某个服务挂了排查半天找不到链路。
这就是典型的“烟囱式”建设。每个项目单独看都能跑,但合在一起就是灾难。运维成本随项目数量线性增长,安全策略无法统一,模型调用成本无法归集,数据也无法打通。更麻烦的是,当公司想换一个更好的大模型时,发现三个项目里模型调用代码写得完全不一样,改造成本高得吓人。
2.2 底座到底解决了什么:把“共性”抽出来,把“个性”留给业务
AI 应用底座的核心思路其实很朴素:把多个 AI 应用之间重复的、通用的能力抽出来做成共享服务,业务侧只保留真正差异化的部分。具体来说,底座通常要覆盖这几类能力:
- 统一接入层:所有 AI 应用的流量都经过同一个网关,做认证、鉴权、限流、路由、日志埋点。
- 统一用户与权限:一套账号体系,一套权限模型,所有应用共享。
- 统一模型接入:把不同厂商、不同版本的模型调用封装成标准接口,业务侧不关心底层用的是哪家。
- 统一会话与上下文管理:多轮对话的状态、历史记录、上下文窗口管理集中处理。
- 统一可观测性:日志、指标、链路追踪一套标准,出问题能快速定位。
- 统一配置与治理:配置中心、服务注册发现、熔断降级这些微服务基础设施。
QuickBlue 的定位就是把这些能力打包成一个开箱即用的底座。业务团队接入后,写一个 AI 应用可能只需要关注 prompt 设计、业务逻辑和少量定制化接口,剩下的全部复用底座能力。这就是为什么标题里强调“企业需要一个 AI 应用底座”——不是技术炫技,而是实实在在的成本和效率问题。
2.3 自研还是选型:一笔要算清楚的账
很多团队第一反应是“我自己搭一个不就行了”。我算过一笔账:一个最小可用的 AI 应用底座,包含网关、认证、权限、配置中心、服务注册、日志监控、模型接入层,两个熟练后端至少要做两到三个月,这还不算后续的维护和迭代。而这两个月里,业务需求是停不下来的。更关键的是,底座这种东西“能用”和“好用”之间差距巨大,自研版本往往在稳定性、安全性、扩展性上埋了一堆坑,等到项目多了再回头重构,代价更大。
所以我的建议是:除非你的团队规模足够大、AI 应用数量足够多、且有长期投入的打算,否则优先考虑成熟的底座方案,把精力放在业务价值上。QuickBlue 这类项目的价值就在于它把底座该有的东西都想到了,你拿来就能用,省下的时间就是实打实的竞争力。
3. QuickBlue 的技术骨架:微服务 + Spring Cloud + JDK 21
3.1 为什么是微服务而不是单体
有人会问,一个底座而已,搞个单体应用不行吗?行,但走不远。底座本身要服务多个上层 AI 应用,这些应用的流量特征、资源需求、发布节奏都不一样。如果底座是单体,任何一个模块出问题都会拖垮全部,任何一个模块升级都要整体重启。微服务架构把底座拆成独立的服务单元,每个单元可以独立部署、独立扩容、独立降级,这对底座这种“被依赖方”来说至关重要。
QuickBlue 采用微服务架构,意味着它的网关、认证、用户、模型接入、会话管理等都是独立服务。上层 AI 应用通过服务注册发现找到它们,某个服务挂了不影响其他服务。这种隔离性是单体架构给不了的。当然,微服务也带来了分布式事务、服务间通信、链路追踪等复杂度,但这些复杂度由底座统一消化,业务侧反而更简单了。
3.2 Spring Cloud 生态的选型逻辑
微服务落地离不开一套治理框架,QuickBlue 选了 Spring Cloud 生态。这个选择背后的考量很实际:Spring Cloud 在国内企业级开发中积累深厚,组件成熟,社区资料多,招人也好招。具体到组件层面,几个关键角色是这样的:
| 能力 | 常用组件 | 作用 |
|---|---|---|
| 服务注册发现 | Nacos / Eureka | 服务实例自动注册与发现 |
| 配置中心 | Nacos Config | 配置集中管理与动态刷新 |
| 网关 | Spring Cloud Gateway | 统一入口、路由、过滤 |
| 熔断限流 | Sentinel | 流量控制、熔断降级 |
| 负载均衡 | Spring Cloud LoadBalancer | 服务间调用负载均衡 |
| 链路追踪 | Micrometer Tracing | 请求链路追踪 |
这里要特别说一下Sentinel。AI 应用有个特点:模型调用耗时波动大,有时候几秒,有时候几十秒。如果没有限流和熔断,一个慢请求可能拖垮整个线程池。Sentinel 能针对模型调用接口设置 QPS 阈值和熔断策略,超时或异常比例过高时自动切断,保护底座不被拖垮。配合 Redis 做集群限流,多实例部署时也能保证全局阈值准确。
3.3 JDK 21 带来的实际收益
QuickBlue 基于 JDK 21,这不是为了追新,而是有实打实的好处。JDK 21 是 LTS 版本,虚拟线程(Virtual Threads)正式转正,这对 AI 应用底座这种 IO 密集型场景意义重大。传统线程模型下,一个模型调用请求占一个平台线程,线程池大小有限,高并发时请求排队。虚拟线程让每个请求可以用一个轻量级线程处理,阻塞时自动让出底层载体线程,同样的硬件能支撑高得多的并发。
我实测过一个对比:同样的模型调用代理服务,JDK 17 平台线程池 200 个线程,压测到 500 并发时开始大量超时;换成 JDK 21 虚拟线程后,2000 并发下响应时间依然平稳。当然虚拟线程不是银弹,CPU 密集型任务提升有限,但在 AI 底座这种大量等待模型响应的场景里,收益非常明显。另外 JDK 21 的模式匹配、记录类、密封类等特性也让代码更简洁,维护成本更低。
4. 核心模块拆解与实操要点
4.1 统一网关:所有 AI 流量的第一道关
网关是底座的入口,所有上层 AI 应用的请求都先到这里。QuickBlue 的网关基于 Spring Cloud Gateway,核心职责有四块:路由转发、认证鉴权、限流熔断、日志埋点。
路由配置上,我建议按“应用 + 版本”维度划分,比如/api/ai/chat/v1/**转发到对话服务,/api/ai/knowledge/v1/**转发到知识库服务。这样后续做灰度发布、版本切换都很方便。认证鉴权放在网关的全局过滤器里,校验 JWT token,解析出用户身份和权限,把用户信息透传到下游服务。这里有个细节:下游服务不要重复做认证,信任网关透传的身份信息即可,否则每个服务都要维护一套认证逻辑,又回到烟囱式了。
限流这块要重点说。AI 应用的限流不能只看 QPS,还要看 token 消耗速率。我见过一个案例:某应用 QPS 不高,但每个请求都让模型生成几千 token,结果模型侧配额瞬间被打满。所以网关层除了 QPS 限流,最好再加一层基于 token 预算的限流,把成本控制住。Sentinel 的热点参数限流可以做到按用户、按应用维度分别限流,配合 Redis 存储计数,集群环境下也能准确。
注意:网关的过滤器顺序很关键。认证过滤器要排在限流之前,否则未认证的请求也会消耗限流配额;日志埋点过滤器要排在最后,确保能记录到完整的处理结果。
4.2 模型接入层:把“换模型”变成改配置
模型接入层是 AI 应用底座区别于普通微服务底座的核心模块。它的目标是:业务侧调用统一的模型接口,底层用哪家模型、哪个版本,通过配置切换。
实现上,QuickBlue 定义了一套标准的模型调用抽象,比如ChatModelService、EmbeddingService、RerankService,每个抽象有多个实现,对应不同的模型提供商。业务侧注入的是接口,运行时根据配置决定用哪个实现。这样换模型时,业务代码一行不用改,只改配置中心的配置项,动态刷新即可生效。
这里有个实操要点:不同模型的输入输出格式差异很大,抽象层要做好适配和归一化。比如有的模型返回content字段,有的返回message.content,有的返回choices[0].text。适配层要把这些统一成标准结构,业务侧才不用关心底层差异。另外超时时间、重试策略、降级方案也要在接入层统一配置。模型调用超时了,是重试还是降级到备用模型,还是直接返回兜底话术,这些策略集中管理,比散落在业务代码里靠谱得多。
参数配置上,我一般会设置三级超时:连接超时 3 秒,读取超时根据模型类型区分,对话类 30 秒,嵌入类 10 秒,重排类 5 秒。重试次数默认 1 次,且只对网络类错误重试,模型返回的业务错误不重试。这些参数都放在配置中心,不同应用可以覆盖。
4.3 会话与上下文管理:多轮对话的“记忆”怎么存
多轮对话是 AI 应用的常见需求,但上下文管理比想象中复杂。一个对话可能有几十轮,全部塞给模型会超 token 限制,只保留最近几轮又可能丢失关键信息。QuickBlue 的会话管理模块要解决的就是这个问题。
存储结构上,我建议用 Redis 存活跃会话,用关系库或对象存储归档历史会话。Redis 里每个会话存一个列表,元素是消息对象,包含角色、内容、时间戳、token 数。读取时按需截取,比如保留最近 N 轮加上系统提示词,总 token 数控制在模型窗口的 80% 以内,留出余量给模型输出。
上下文截断策略有好几种:按轮数截断、按 token 数截断、按重要性截断。我一般用“滑动窗口 + 摘要”的组合:最近几轮完整保留,更早的对话用模型生成摘要,把摘要作为系统提示的一部分。这样既控制了 token,又保留了长期记忆。摘要的生成可以异步做,不阻塞主流程。
提示:会话的过期时间要设置合理。太短用户觉得“失忆”,太长 Redis 内存吃不消。我一般设 2 小时活跃过期,同时每天凌晨归档前一天的不活跃会话。
4.4 权限与多租户:一套底座服务多个业务线
企业里底座往往要服务多个业务线,每个业务线的用户、角色、数据都要隔离,这就是多租户需求。QuickBlue 的权限模块要支持租户级别的数据隔离和权限控制。
数据隔离有三种常见方案:独立数据库、共享数据库独立 schema、共享数据库共享 schema 加租户字段。我推荐第三种,成本最低,扩展性最好。所有业务表加一个tenant_id字段,查询时自动带上租户条件。MyBatis 的拦截器可以自动拼接租户条件,业务代码不用手写。权限模型用 RBAC,用户关联角色,角色关联权限,权限粒度可以到菜单、按钮、接口。
这里有个坑要注意:租户隔离要在网关层就确定租户身份,不能等到业务层再判断。网关根据请求域名、header 或 token 里的租户标识,把tenant_id透传到下游,下游所有查询都基于这个tenant_id。否则一旦某个接口漏了租户条件,就会造成数据越权。
5. 从零搭建一个最小可用底座的实操过程
5.1 环境准备与依赖版本锁定
动手之前先把环境理清楚。我用的版本组合是:JDK 21、Spring Boot 3.2.x、Spring Cloud 2023.0.x、Spring Cloud Alibaba 2023.0.x、Nacos 2.3.x、Sentinel 1.8.x、Redis 7.x、MySQL 8.x。这个组合经过实测比较稳定,各组件兼容性没问题。
依赖管理上,用 Maven 的dependencyManagement统一锁定版本,避免子模块版本冲突。父 POM 里引入 Spring Cloud 和 Spring Cloud Alibaba 的 BOM,子模块只声明 groupId 和 artifactId,不写版本号。这样升级时只改父 POM 一处。
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>3.2.5</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2023.0.1</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2023.0.1.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>5.2 服务注册与配置中心接入
先启动 Nacos,然后每个微服务引入 Nacos 的注册和配置依赖。bootstrap.yml里配置 Nacos 地址和命名空间。命名空间按环境划分,dev、test、prod 各一个,避免配置串环境。
spring: application: name: quickblue-gateway cloud: nacos: discovery: server-addr: ${NACOS_ADDR:127.0.0.1:8848} namespace: ${NACOS_NAMESPACE:dev} config: server-addr: ${NACOS_ADDR:127.0.0.1:8848} namespace: ${NACOS_NAMESPACE:dev} file-extension: yaml配置动态刷新用@RefreshScope注解,改完 Nacos 里的配置,服务不用重启就能生效。模型接入层的超时时间、限流阈值这些经常调整的参数,都放配置中心,运维改配置即可,不用发版。
5.3 网关路由与过滤器配置
网关的路由配置我习惯放在 Nacos 配置中心,格式是 YAML,支持动态刷新。每条路由包含 id、uri、predicates、filters。uri 用lb://服务名走负载均衡。
spring: cloud: gateway: routes: - id: chat-service uri: lb://quickblue-chat predicates: - Path=/api/ai/chat/** filters: - StripPrefix=2 - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 100 redis-rate-limiter.burstCapacity: 200认证过滤器继承GlobalFilter和Ordered,在filter方法里校验 token,解析用户信息放入请求头。顺序设为 -100,确保在限流之前执行。限流过滤器用 Spring Cloud Gateway 自带的RequestRateLimiter,基于 Redis 做令牌桶,集群环境下也能准确限流。
5.4 模型接入层的抽象与实现
模型接入层的核心是接口抽象。我定义三个接口:ChatService、EmbeddingService、RerankService。每个接口有多个实现类,用@ConditionalOnProperty控制哪个实现生效。
public interface ChatService { ChatResponse chat(ChatRequest request); Flux<ChatResponse> streamChat(ChatRequest request); }实现类里封装具体模型的 HTTP 调用、参数组装、响应解析。超时用WebClient配置,重试用 Resilience4j 或 Sentinel 的熔断器。响应解析后统一转成ChatResponse,业务侧只认这个结构。
配置项设计上,每个模型实现对应一组配置,比如quickblue.model.chat.provider=openai、quickblue.model.chat.timeout=30000。切换模型时改provider值,配合@RefreshScope动态生效。
5.5 会话管理的 Redis 数据结构设计
会话数据用 Redis Hash 加 List 组合存储。Hash 存会话元信息,比如用户 ID、创建时间、最后活跃时间;List 存消息列表,每个元素是序列化后的消息对象。
HSET session:meta:{sessionId} userId 1001 createTime 1716000000 lastActive 1716003600 RPUSH session:msg:{sessionId} '{"role":"user","content":"你好","ts":1716000000}' EXPIRE session:meta:{sessionId} 7200 EXPIRE session:msg:{sessionId} 7200读取时用LRANGE取最近 N 条,反序列化后组装成模型需要的格式。消息对象里记录 token 数,方便做上下文窗口控制。归档任务用定时任务扫描过期会话,转存到 MySQL 后删除 Redis 数据。
6. 常见问题与排查技巧实录
6.1 服务注册不上或注册后调不通
这是微服务入门最常见的坑。排查顺序我一般是这样:先看 Nacos 控制台服务列表里有没有实例,没有的话检查spring.cloud.nacos.discovery.server-addr配置和网络连通性;有实例但调不通,检查服务提供者的端口是否对外暴露、防火墙是否放行、消费者是否用了正确的服务名。
还有一个隐蔽问题:服务注册的 IP 选错了网卡。机器有多张网卡时,Nacos 可能注册了内网 IP,但消费者在另一个网段访问不到。解决办法是在配置里指定spring.cloud.nacos.discovery.ip,显式指定正确的 IP。
6.2 模型调用超时与熔断配置不当
模型调用超时是高频问题。表现是请求偶尔卡住几十秒然后失败。排查时先看是连接超时还是读取超时,连接超时通常是网络问题,读取超时是模型响应慢。读取超时要根据模型类型设置,不能一刀切。
熔断配置上,Sentinel 的熔断策略有慢调用比例、异常比例、异常数三种。模型调用我推荐用慢调用比例,设置一个合理的 RT 阈值,比如 10 秒,慢调用比例超过 50% 就熔断。熔断后走降级逻辑,返回兜底话术或切换到备用模型。这里要注意熔断时间窗口,太短会频繁触发,太长恢复慢,一般设 10 到 30 秒。
6.3 多租户数据越权的排查
数据越权是安全问题,排查起来要仔细。先确认网关是否透传了tenant_id,再检查每个查询是否带了租户条件。MyBatis 拦截器自动拼接租户条件时,要确保所有涉及业务表的查询都走了拦截器,手写的原生 SQL 容易漏掉。
我一般会写一个测试用例,用租户 A 的 token 去查租户 B 的数据,看能不能查到。这个用例放进 CI,每次发版都跑,防止回归。
6.4 虚拟线程下的 ThreadLocal 陷阱
JDK 21 虚拟线程用起来爽,但有个坑:ThreadLocal 在虚拟线程里行为不同。传统线程池里 ThreadLocal 可以复用,虚拟线程每个请求新建,ThreadLocal 不会自动清理,容易内存泄漏。如果代码里用了 ThreadLocal 存用户上下文,记得在请求结束时手动remove。或者干脆改用ScopedValue(JDK 21 预览特性),作用域结束自动清理。
6.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决思路 |
|---|---|---|---|
| 服务注册不上 | 配置错误/网络不通 | 看 Nacos 控制台和日志 | 检查 server-addr 和网卡 IP |
| 模型调用超时 | 读取超时设置过短 | 看超时类型和模型 RT | 按模型类型调整超时 |
| 熔断频繁触发 | 阈值设置不合理 | 看 Sentinel 监控 | 调整 RT 阈值和熔断窗口 |
| 数据越权 | 租户条件缺失 | 检查 SQL 和拦截器 | 补租户条件,加测试用例 |
| 内存泄漏 | ThreadLocal 未清理 | 看堆内存和线程数 | 手动 remove 或改 ScopedValue |
| 配置不生效 | 未加 RefreshScope | 看配置是否刷新 | 加注解,检查配置格式 |
7. 底座后续扩展的几个方向
底座搭好只是开始,后面还有不少可以打磨的地方。我分享几个我实际在做的扩展方向。
第一个是模型成本归集。每个请求记录 token 消耗和对应模型单价,按租户、按应用、按用户维度汇总,月底出账单。这个功能对内部结算特别有用,谁用得多谁承担成本,避免资源滥用。
第二个是Prompt 版本管理。把 prompt 当成代码来管理,支持版本、灰度、回滚。A/B 测试不同 prompt 的效果,用数据说话。这个功能对提升 AI 应用效果帮助很大。
第三个是评测体系。建一套自动评测流程,新模型上线前先跑一遍标准测试集,对比准确率、响应时间、成本,达标才放量。避免拍脑袋换模型。
第四个是可观测性增强。除了常规的日志指标链路,再加模型维度的监控:调用量、成功率、平均 RT、token 消耗、缓存命中率。这些指标做成大盘,一眼看清底座健康度。
这些扩展不需要一次性做完,按业务优先级逐步迭代。底座的价值就在于它是个持续演进的平台,而不是一次性的项目。我个人的体会是,底座前期投入大,但一旦跑起来,后面每接一个 AI 应用的成本会越来越低,这就是复利的价值。