Day49-AI微服务化-将大模型能力封装为标准微服务
把大模型当作一个普通的HTTP微服务来对待是这一代Java工程师必须建立的工程思维。今天这篇我会用我们项目组的真实改造过程为例从架构分层、代码实现到模型路由一步步把AI能力封装成「别的业务系统愿意调用」的微服务。一、为什么大模型必须微服务化很多人第一次接触AI能力第一反应就是「业务代码里直接调一下ChatClient.call()不就完了」行但只在你这个项目就一个AI调用点的阶段。一旦出现下面任意一种情况你就该把它服务化了触发场景直接调用的痛点多个业务团队都要用AI每个团队都要接一遍API、申请一遍Key、重写一遍Prompt公司用了多家模型不同团队锁死在某个供应商议价权丢失要做AI用量计费用量分散在各业务系统表里月底对账对到哭出了幻觉/Prompt注入没有统一的输入净化层没有审计日志老板要看AI大屏没有统一的调用指标采集点核心原则AI能力就像数据库一样是企业级基础设施——你不会让每个业务系统直接连生产DB为什么要让它们直接调大模型二、AI微服务的四层架构把大模型封装成标准微服务我们采用四层架构。我把各层的职责划清楚关键原则业务团队只看到第②层他们不需要知道底下是GPT-4还是Qwen。这就是服务化的意义——对调用方隐藏复杂性。三、代码实现搭建一个AI能力微服务下面我用一个真实的「文档问答」能力为例把这套架构跑通。所有代码基于 Spring Boot 3.3 Spring AI 1.0 JDK 17。3.1 Maven依赖xml dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-zhipuai/artifactId /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency dependency groupIdio.micrometer/groupId artifactIdmicrometer-registry-prometheus/artifactId /dependency /dependencies3.2 业务能力服务对外的稳定接口这是给业务团队调的入口。我把所有业务字段、错误码、SLA都封装在这里下游完全不感知模型变更。/** * AI能力微服务的对外Controller * 业务团队只依赖这个接口与模型解耦 */ RestController RequestMapping(/api/v1/ai) RequiredArgsConstructor public class AiCapabilityController { private final DocumentQaService documentQaService; private final UsageRecorder usageRecorder; /** * 文档问答接口 * 业务系统只需要传文档ID 问题 用户ID */ PostMapping(/doc-qa) public ResponseEntityAiResponseDocAnswer docQa(Valid RequestBody DocQaRequest request, RequestHeader(X-Biz-Source) String bizSource) { long start System.currentTimeMillis(); try { // 1. 参数基础校验鉴权在外层网关这里只做业务校验 Document doc documentService.getById(request.getDocId()); if (doc null) { return AiResponse.fail(DOC_NOT_FOUND, 文档不存在); } // 2. 调用业务能力内部实现对调用方完全透明 DocAnswer answer documentQaService.answer( request.getDocId(), request.getQuestion(), request.getUserId() ); // 3. 记录用量这里是最关键的环节后面看 long cost System.currentTimeMillis() - start; usageRecorder.record(bizSource, doc-qa, answer.getPromptTokens(), answer.getCompletionTokens(), cost); return AiResponse.ok(answer); } catch (BusinessException e) { log.warn(业务异常 biz{} code{}, bizSource, e.getCode()); return AiResponse.fail(e.getCode(), e.getMessage()); } catch (Exception e) { log.error(AI调用异常, e); // 兜底降级返回预先准备好的标准答案或转人工 return AiResponse.fail(AI_UNAVAILABLE, AI服务暂时不可用请稍后重试); } } /** * 流式问答SSE—— 长对话场景必备 */ GetMapping(value /doc-qa/stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxServerSentEventString streamDocQa(RequestParam String docId, RequestParam String question, RequestParam String userId) { return documentQaService.streamAnswer(docId, question, userId) .map(chunk - ServerSentEvent.builder(chunk).build()) // 异常也要返回给前端不能让连接挂死 .onErrorResume(e - { log.error(SSE流式异常, e); return Flux.just(ServerSentEvent.builder([ERROR]AI服务异常).build()); }); } }这段代码的几个关键设计X-Biz-Source头部强制业务方标识来源用量统计、计费、限流的依据。AiResponse统一包装所有AI接口必须返回这个结构前端对接成本最低。业务校验与AI调用分离业务校验失败绝不打模型节省Token。兜底降级AI挂掉不能影响业务——这是微服务的基本素养。3.3 模型路由层按场景选择模型与降级业务能力层下面就是模型路由层。这里是AI微服务最体现工程能力的地方。/** * 模型路由根据场景动态选择模型 * 核心思想把调哪个模型的决策从业务代码里剥离出来 */ Service Slf4j public class ModelRouter { private final ChatModel openAiModel; // GPT-4o private final ChatModel qwenModel; // 通义千问 private final ChatModel deepseekModel; // DeepSeek private final TokenBucketLimiter limiter; // 限流器 /** * 路由策略封装 */ public ChatModel route(RouteContext ctx) { // 1. 限流检查最重要的护栏 if (!limiter.tryAcquire(ctx.getBizSource())) { throw new BusinessException(RATE_LIMIT, 调用频率超限); } // 2. 按业务来源路由 return switch (ctx.getBizSource()) { case customer-service - { // 客服场景中文友好、成本敏感 → 通义 log.debug(路由到通义千问); yield qwenModel; } case code-assistant - { // 代码场景英文能力强、JSON格式稳 → DeepSeek log.debug(路由到DeepSeek); yield deepseekModel; } case image-desc - { // 多模态 → GPT-4o yield openAiModel; } default - { // 兜底用主力模型 yield qwenModel; } }; } } /** * 路由上下文把决策依据集中管理 */ Data Builder public class RouteContext { private String bizSource; // 业务来源 private String userId; // 用户ID private int priority; // 优先级 0-99最高 private boolean isVip; // 是否VIP private int estimatedTokens; // 预估Token }为什么要做路由成本优化简单问题用便宜模型复杂问题用强模型风险分散一家模型API挂掉自动切到备胎差异化SLAVIP用户走专用通道。3.4 用量统计大模型版「拦路虎」这是把AI「真正当微服务」的关键一步——没有计费的微服务老板没法给你批预算。/** * 用量记录器 * 高频调用场景必须异步不能阻塞AI调用主链路 */ Component Slf4j public class UsageRecorder { private final UsageMapper usageMapper; private final ThreadPoolExecutor asyncPool; /** * 记录一次AI调用 * 用线程池异步落库不阻塞调用链路 */ public void record(String bizSource, String capability, int promptTokens, int completionTokens, long costMs) { asyncPool.execute(() - { try { UsageRecord record UsageRecord.builder() .bizSource(bizSource) .capability(capability) .promptTokens(promptTokens) .completionTokens(completionTokens) .totalTokens(promptTokens completionTokens) .costMs(costMs) .estimatedCost(calcCost(bizSource, promptTokens, completionTokens)) .timestamp(LocalDateTime.now()) .build(); usageMapper.insert(record); } catch (Exception e) { // 用量记录失败不能影响主流程记录到日志兜底 log.error(用量记录失败 biz{} capability{}, bizSource, capability, e); } }); } /** * 成本计算 * 真实生产场景这里应该读配置中心模型价格随时会变 */ private BigDecimal calcCost(String bizSource, int pt, int ct) { // 单位元/千Token BigDecimal ptRate switch (bizSource) { case customer-service - new BigDecimal(0.0008); // 通义 case code-assistant - new BigDecimal(0.001); // DeepSeek default - new BigDecimal(0.005); // GPT-4o }; return ptRate.multiply(new BigDecimal(pt ct)) .divide(new BigDecimal(1000), 6, RoundingMode.HALF_UP); } }到这里一个完整的AI微服务就出来了。再用 Spring Cloud Gateway 给它加个网关限流一个企业级的AI能力中台就成形了。四、AI调用链追踪把日志串起来微服务架构下AI调用会跨越多个内部服务。没有TraceId出问题排查能让你薅秃头发。下面是个轻量级的TraceId方案不依赖SkyWalking这种重武器但能解决80%的问题/** * AI调用上下文贯穿整个调用链 * 用ThreadLocal传跨服务通过Header传递 */ public class AiCallContext { private static final ThreadLocalCallContext CONTEXT new ThreadLocal(); public static void set(String traceId, String userId, String bizSource) { CONTEXT.set(new CallContext(traceId, userId, bizSource, System.currentTimeMillis())); } public static CallContext get() { return CONTEXT.get(); } public static void clear() { CONTEXT.remove(); } Data AllArgsConstructor public static class CallContext { private String traceId; private String userId; private String bizSource; private long startTime; } } /** * 在请求入口设置TraceId */ Component public class AiCallContextFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) { String traceId req.getHeader(X-Trace-Id); if (StringUtils.isEmpty(traceId)) { traceId UUID.randomUUID().toString().replace(-, ); } AiCallContext.set(traceId, req.getHeader(X-User-Id), req.getHeader(X-Biz-Source)); resp.setHeader(X-Trace-Id, traceId); try { chain.doFilter(req, resp); } finally { AiCallContext.clear(); // 必须清理否则线程复用会污染 } } }日志里带上traceId出问题时grep一下一个调用从进入到AI返回的全部日志就齐了{ traceId: a3f8c91b9e2d4f5c, bizSource: customer-service, capability: doc-qa, promptTokens: 425, completionTokens: 188, costMs: 1245, level: INFO, msg: AI调用完成 }五、生产环境的几个坑最后说几个我们踩过的坑给大家提个醒1. 输入净化是AI微服务的标配别以为你接的是「自己公司的模型」就不做Prompt注入防御。我见过业务方把用户原始输入直接拼到Prompt里被攻击后模型输出违规内容被监管处罚的案例。所有用户输入必须过一道InputSanitizer至少做长度限制 敏感词过滤 角色锁定。2. 超时一定要短熔断一定要狠大模型调用动辄5~30秒你如果不在网关层设个2秒超时把请求cut掉整个调用链都会被拖死。我们生产上AI网关默认超时1.5秒熔断阈值是20%错误率就开跳。3. Token限流比QPS限流更有意义普通微服务按QPS限流AI微服务要按Token限流。因为同样是「1次调用」问你好和问翻译一篇5000字文档消耗的资源差100倍。我们用 Redis 滑动窗口 Token 计数器1分钟窗口最多消耗100万Token超了就降级到便宜模型或直接拒绝。4. 用量计费一定要做实时看板别等月底拉Excel对账。我们搞了个Grafana看板按业务方/能力/时间段三维展示用量、成功率、平均Token、花费。老板每周看一次预算批得飞快。写到这里你会发现把大模型当一个普通微服务对待本质就是抽象和分层。这两件事是软件工程40年不变的底层能力。不会因为你把数据库换成了大模型就变得更高深了。它仍然是把变化的部分隔离出来把不变的部分稳定暴露出去。下篇预告第50篇《分布式一致性Raft协议动画式讲解》—— 微服务之后必须啃的分布式一致性硬骨头从Leader选举到日志复制一次讲透。系列文章回顾90篇JAVA高级工程师深度进阶文章从实践案例、项目代码出发用事实说话版权声明本文为「老梁」原创出品90天Java后端AI系列第48篇转载请注明出处。

相关新闻

【数据结构】二叉树的存储结构(顺序/链式)

【数据结构】二叉树的存储结构(顺序/链式)

考点频率:★★★★☆(选择题常考,是理解二叉树遍历和操作的基础) 难度:⭐⭐ 建议:重点掌握顺序存储的适用范围(完全二叉树)和链式存储的节点结构 1️⃣ 存储结构概述 在上一篇文章中…

2026/10/5 18:16:54 阅读更多 →
第6章 时域递归滤波:IIR滤波器在视频降噪中的应用

第6章 时域递归滤波:IIR滤波器在视频降噪中的应用

作者:一位在ISP领域摸爬滚打十余年的算法工程师 “纸上得来终觉浅,绝知此事要躬行。”——这门课的每一行代码,都希望你亲自跑一遍。 从原理到代码,从理论到工程落地 本课程共30章,系统讲解ISP核心算法——多帧降噪、HDR合成、夜景算法的原理、实现与调优。 每章包含:原理…

2026/9/29 17:17:31 阅读更多 →
线程中断-interrupted方法

线程中断-interrupted方法

一、没有中断机制,代码会出什么问题?假设 Java 没有 interrupt(),你要怎么让一个正在运行的线程停下来?方案 1:用一个 volatile boolean 标志位class Worker implements Runnable {private volatile boolean running …

2026/9/25 18:38:56 阅读更多 →

最新新闻

工厂数字化转型:MES是核心但不是全部

工厂数字化转型:MES是核心但不是全部

一、痛点背景:从一次真实的生产事故说起工厂数字化转型:MES是核心但不是全部这个问题,在FAB里不是一天两天了。我见过太多工程师踩坑:要么是方法用错导致数据误判,要么是工具选型失误导致项目延期,要么是流…

2026/10/16 6:13:50 阅读更多 →
HivisionIDPhotos 安装教程:15 分钟本地部署免费 AI 证件照工具

HivisionIDPhotos 安装教程:15 分钟本地部署免费 AI 证件照工具

HivisionIDPhotos 安装教程:15 分钟本地部署免费 AI 证件照工具 【免费下载链接】HivisionIDPhotos ⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。 项目地址: https://gitcode.com/GitHub_Trending/hiv…

2026/10/16 6:13:50 阅读更多 →
MySQL WHERE条件查询五类核心用法与性能优化实践

MySQL WHERE条件查询五类核心用法与性能优化实践

做MySQL开发这些年,我用过最多的语句就是SELECT,而SELECT里真正决定查询价值的就是WHERE条件。很多刚入行的同事写查询时习惯先把数据捞出来再在程序里过滤,这种写法在数据量小的时候看不出问题,等表里几百万行数据的时候就等着被…

2026/10/16 6:13:50 阅读更多 →
Znode详解:从节点类型到Watch机制,掌握ZooKeeper数据模型核心

Znode详解:从节点类型到Watch机制,掌握ZooKeeper数据模型核心

1. 先搞清楚 Znode 到底是什么1.1 从"文件系统"这个直觉说起很多第一次接触 ZooKeeper 的人,都会把它当成一个"分布式的配置中心"或者"注册中心"来理解,这个方向没错,但如果你只停留在"存 key-value 的 r…

2026/10/16 6:13:50 阅读更多 →
STM32CubeMX+FatFs:SD卡文件系统完整移植与避坑指南

STM32CubeMX+FatFs:SD卡文件系统完整移植与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 6:13:50 阅读更多 →
19 位纯数字和 v 开头的一串字母有什么区别?douyin-vue 视频播放原理全解

19 位纯数字和 v 开头的一串字母有什么区别?douyin-vue 视频播放原理全解

19 位纯数字和 v 开头的一串字母有什么区别?douyin-vue 视频播放原理全解 【免费下载链接】douyin Vue3 Pinia 仿抖音,Vue 在移动端的最佳实践 . Imitate TikTok ,Vue Best practices on Mobile 项目地址: https://gitcode.com/GitHub_Tr…

2026/10/16 6:12:49 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →