当前位置: 首页 > news >正文

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> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-zhipuai</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency> </dependencies>

3.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 ResponseEntity<AiResponse<DocAnswer>> 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 Flux<ServerSentEvent<String>> 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()); }); } }

这段代码的几个关键设计

  1. X-Biz-Source头部:强制业务方标识来源,用量统计、计费、限流的依据。
  1. AiResponse统一包装:所有AI接口必须返回这个结构,前端对接成本最低。
  1. 业务校验与AI调用分离:业务校验失败绝不打模型,节省Token。
  1. 兜底降级: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-9,9最高 private boolean isVip; // 是否VIP private int estimatedTokens; // 预估Token }

为什么要做路由?

  • 成本优化:简单问题用便宜模型,复杂问题用强模型;
  • 风险分散:一家模型API挂掉,自动切到备胎;
  • 差异化SLA:VIP用户走专用通道。

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 ThreadLocal<CallContext> 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篇,转载请注明出处。

http://www.jsqmd.com/news/1392523/

相关文章:

  • 2026年构建现代化芯产业体系,国内半导体博览会哪家好? - 2027品牌AI展
  • 破解物理AI技术困局(43):TVA攻克长周期稀疏奖励难题
  • AI的「梦」-龍德明宇
  • 筑牢高端装备“度量基石”:笛灵科技以自主创新赋能精密测量产业 - 甄选测评馆
  • 生命涌现的小龙虾技能之【Feed Intake Estimation | 畜禽采食量估算】简介
  • 工商服务小程序开发公司有哪些?哪个更适合零基础新手?
  • 元初混沌体系架构 第二卷 第四十二篇 深空无遮挡频谱纯净利用范式
  • 2026剧场文旅演出音响厂家选型指南:技术资质与交付能力解析 - 汇聚至此
  • C#与常用数据结构源码剖析-全篇导览
  • 适合初创团队的DevOps软件怎么选?低成本快上手方案
  • 逛遍广州本地二奢市场,摸清大牌包包变现门道,奢二网分享普通人的出包思路 - 每日小知识
  • ABAP 里有没有 RxJS Marble Diagram,真正的差别不在画法,而在时间模型
  • Linux学习14-logstash插件,日志采集及可视化,ES数据备份与集群监控
  • RabbitMQ异步下单架构设计
  • 2026年不锈钢飘带雕塑厂家选型及行业全景分析 - 曲阳嘉华园林
  • 2026年工程直供文旅演出音响推荐:原厂直供文旅演出音响品牌选择指南 - 汇聚至此
  • 2026小程序商城做的比较好的品牌,商城系统品牌怎么判断
  • 江苏南通灌浆料怎么选?3家本地品牌实力对比解析 - 兔兔不是荼荼
  • Win11Debloat 使用指南:3分钟如何免费清理 Windows 11 预装软件与系统臃肿
  • 2026年注塑工艺头盔安全插扣源头厂家推荐:精密成型供应商选择指南 - 汇聚至此
  • Linux-----进程管理面试题
  • 2026上海科创园区GEO服务商适配度榜单报告
  • 2026年8月怀化屋顶漏水维修哪家好?正规防水修缮科普指南 - 聪居到家
  • KMS_VL_ALL_AIO使用指南:一个开源脚本解决Windows与Office激活的7个关键疑问
  • 餐饮供应链行业企业获客软件精准拓客方法论:基于客户类型的多维筛选策略
  • 面向对象-接口
  • 2026云南电大中专**报名入口!报考流程+专业解析 - 小张zc
  • 2026成都钻石钻戒回收诚信商家一览:无损鉴定当场结算指南 - 资讯快报员
  • 普通人要Codex有什么用?
  • Dism++ 完整上手指南:免费开源系统优化工具,10 分钟跑通清理、更新与备份全流程