拓冰建站拓冰建站
首页 / 资讯中心 / 正文

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篇转载请注明出处。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门