1. [从单体到微服务:大模型时代的必然选择](#1-从单体到微服务大模型时代的必然选择)
  2. [大模型微服务拆分的核心驱动因素](#2-大模型微服务拆分的核心驱动因素)
  3. [总体架构设计:分层与服务边界](#3-总体架构设计分层与服务边界)
  4. [服务拆分原则与度量指标](#4-服务拆分原则与度量指标)
  5. [技术选型:Spring Cloud Alibaba 大模型微服务栈](#5-技术选型spring-cloud-alibaba-大模型微服务栈)
  6. [核心服务清单与职责划分](#6-核心服务清单与职责划分)
  7. [跨服务通信与数据一致性策略](#7-跨服务通信与数据一致性策略)
  8. [演进路线:从绞杀者模式到完全解耦](#8-演进路线从绞杀者模式到完全解耦)
  9. [容量规划与弹性扩容设计](#9-容量规划与弹性扩容设计)
  10. [本章小结与最佳实践清单](#10-本章小结与最佳实践清单)

1.1 单体架构在大模型场景下的困境

在传统 Java Web 应用中,单体架构(Monolithic Architecture)因其开发简单、部署便捷,长期占据中小型项目的主流地位。一个典型的 Java 单体应用会把用户管理、订单处理、支付回调、报表统计等功能模块全部塞进同一个 WAR 或 JAR 包,共享同一个数据库连接池和同一个 JVM 进程。在没有引入大模型之前,这种架构通常能稳定支撑日活十万级的业务流量。

然而,当业务开始集成大语言模型(LLM)后,单体架构的矛盾会迅速暴露。大模型调用与传统业务调用在资源消耗特征上存在本质差异:一次普通的订单查询耗时通常在 50 毫秒以内、占用几 KB 内存;而一次大模型推理请求动辄耗时 3 到 30 秒、占用数百 MB 显存或内存,且并发能力受限于推理框架的批处理窗口。把这两种调用放在同一个 JVM 进程里,会出现三类典型问题。

第一类是**线程池争抢**。大模型调用通常走 HTTP 长连接,默认的 Tomcat 最大工作线程数是 200,当有 50 个请求同时等待大模型响应时,线程池就被占去四分之一,剩下的传统业务请求可用的并发槽位大幅减少,P99 延迟急剧上升。第二类是**内存压力**。大模型的流式响应需要在内存中累积 token 缓冲区,如果同时处理多个长上下文请求,堆内存会被迅速吃掉,触发频繁 Full GC,进而拖垮整个进程。第三类是**扩缩容错配**。传统业务流量有明显的高峰低谷,适合按 CPU 利用率自动扩缩容;而大模型推理服务更适合按 GPU 利用率或排队深度扩缩容,两者放在一起会导致扩容策略顾此失彼。

1.2 微服务拆分带来的核心收益

把大模型相关能力从单体中剥离,形成独立的微服务,能从根本上解决上述矛盾。拆分后的收益可以归纳为五个维度。

**独立扩缩容**是最直接的收益。AI 推理服务可以部署在带 GPU 的节点上,按请求队列深度独立扩容;Prompt 管理服务部署在普通 CPU 节点上,按 QPS 扩容;两者互不干扰,资源利用率都能做到最优化。在一次实际压测中,我们把推理服务从 4 实例扩到 16 实例,而业务网关保持 8 实例不变,整体成本比"整体扩容"方案节省了 37%。

**故障隔离**是第二个收益。大模型服务依赖外部推理框架或云厂商 API,网络抖动、限流、超时是家常便饭。如果推理逻辑和订单逻辑在同一个进程里,推理服务的超时可能通过线程阻塞传导到订单模块,造成"大模型挂了导致下单失败"的荒谬场景。拆分成独立服务后,通过熔断器和舱壁隔离,推理故障被限制在自身服务边界内,不影响核心交易链路。

**技术栈异构**成为可能。大模型推理层可能用 Python + vLLM 部署效果更好,而业务编排层用 Java 更稳妥。微服务架构允许每个服务选择最合适的技术栈,通过标准 HTTP/gRPC 接口通信,这在单体架构里是无法实现的。

**独立发布节奏**让团队迭代更快。Prompt 模板的调整、向量库索引的重建,都不需要跟着业务系统的发布窗口走,可以独立灰度、独立回滚。

**成本可追溯**是容易被忽视的收益。大模型调用是按 token 计费的,把推理服务独立出来后,可以精确统计每个业务线、每个接口的 token 消耗,为成本优化和内部结算提供数据基础。

1.3 拆分的代价:不能只看收益

当然,微服务拆分不是银弹,它也带来了显著的复杂度成本。服务间通信引入了网络延迟和失败模式;分布式事务比本地事务难处理得多;可观测性需要从"看一个日志文件"升级到"看全链路追踪";运维要从部署一个包变成编排十几个服务。因此拆分必须是有节奏的、有原则的,而不是为了拆而拆。本系列文章的核心目标,就是给出一套可落地的大模型微服务拆分方法论,让收益最大化、代价可控化。

2.1 资源异构驱动:CPU 与 GPU 的物理隔离

大模型微服务拆分的第一个驱动因素是资源异构。在一个完整的 AI 应用里,不同模块对硬件资源的需求差异极大,如下表所示。

模块

主要资源

典型耗时

并发特征

扩容依据

------

---------

---------

---------

---------

业务网关/编排

CPU

5-50ms

高并发

QPS

AI 推理服务

GPU/大内存

1-30s

低并发长耗时

队列深度

Prompt 管理

CPU/DB

5-20ms

中高并发

QPS

Embedding 向量服务

GPU/CPU

20-200ms

中并发

QPS+GPU利用率

向量检索服务

内存

5-30ms

高并发

QPS+内存

知识库管理

CPU/DB

50-500ms

低并发

业务量

如果把这些模块放在同一个进程里,最直接的后果是部署时无法选择合适的机型。你不可能给一台既跑业务网关又跑推理服务的机器同时配 GPU 和高主频 CPU——那样成本会爆炸。物理隔离后,推理服务跑在 GPU 机型上,网关跑在普通 CPU 机型上,各取所需。

2.2 故障域驱动:外部依赖的失败隔离

大模型应用通常依赖多个外部组件:云厂商 LLM API、开源推理框架、向量数据库、OCR 服务等。这些外部依赖的稳定性远低于内部数据库,失败概率可能高出一个数量级。把对这些外部依赖的调用封装在独立服务中,可以通过熔断、降级、重试等手段把故障限制在一个服务边界内,避免扩散。

举个真实案例:某团队把 LLM 调用直接写在订单服务里,某天 LLM 厂商 API 出现间歇性 503,由于没有设置合理超时,订单服务的线程池被逐渐耗尽,最终整个下单链路瘫痪 40 分钟。拆分成独立的推理服务后,类似故障只会触发推理服务的熔断降级,订单服务收到快速失败响应并走降级文案,核心交易不受影响。

2.3 演进驱动:业务模型的不确定性

大模型应用的业务模型还在快速演进中。今天的 Prompt 模板可能下周就要调整,当前的向量检索策略可能下个月就要换成混合检索。如果把所有逻辑耦合在单体里,每一次调整都要重新部署整个应用,风险高、周期长。微服务架构允许这些高频变化的模块独立演进,符合"高频变化的部分应该独立部署"这一架构原则。

2.4 团队协作驱动:康威定律的体现

康威定律指出,系统架构会映射团队的组织结构。大模型应用涉及算法工程师、Java 后端、前端、运维等多种角色。算法工程师更熟悉 Python 和模型推理细节,Java 后端更熟悉业务编排和稳定性保障。把推理逻辑和业务编排拆成不同服务,可以让不同团队各司其职、并行开发,减少跨角色协作的摩擦。

3.1 四层架构总览

大模型微服务的总体架构可以分为四层,从上到下依次是:接入层、编排层、能力层、数据层。下图展示了这一分层结构。

┌─────────────────────────────────────────────────┐
│  接入层:API网关、鉴权、限流、协议转换            │
├─────────────────────────────────────────────────┤
│  编排层:业务流程编排、会话管理、降级路由          │
├─────────────────────────────────────────────────┤
│  能力层:AI推理服务、Prompt管理、Embedding服务、  │
│         向量检索服务、知识库服务                  │
├─────────────────────────────────────────────────┤
│  数据层:向量数据库、关系数据库、对象存储、缓存    │
└─────────────────────────────────────────────────┘

每一层的职责边界清晰:接入层只负责协议和流量控制,不包含业务逻辑;编排层负责把多个能力服务组合成完整的业务流程;能力层提供原子化的 AI 能力;数据层提供持久化支撑。这种分层的好处是,任何一层的实现变化都不会扩散到其他层,例如把向量数据库从 Milvus 换成 Qdrant,只需要在能力层的向量检索服务内部调整,上层无感知。

3.2 接入层设计要点

接入层通常由 Spring Cloud Gateway 承担,主要职责包括统一鉴权、限流熔断、请求路由、协议转换。在大模型场景下,接入层还需要特别处理流式响应(SSE/WebSocket)的透传。下面是一段网关的流式响应配置示例。

@Configuration
public class GatewayConfig {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            // AI 推理路由:支持 SSE 流式
            .route("ai-inference-route", r -> r
                .path("/api/chat/**")
                .filters(f -> f
                    .filter(jwtAuthFilter())
                    .filter(rateLimitFilter(100))  // 每秒100请求
                    .requestSize(16L * 1024 * 1024) // 限制16MB
                    .circuitBreaker(c -> c
                        .setName("inference-cb")
                        .setFallbackUri("forward:/fallback/chat")))
                .uri("lb://ai-inference-service"))
            // Prompt 管理路由
            .route("prompt-route", r -> r
                .path("/api/prompt/**")
                .filters(f -> f.filter(jwtAuthFilter()))
                .uri("lb://prompt-service"))
            .build();
    }
    private GatewayFilter rateLimitFilter(int limit) {
        return (exchange, chain) -> {
            String key = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
            // 基于 Redis 的令牌桶限流
            return redisRateLimiter(key, limit)
                .flatMap(allowed -> {
                    if (allowed) {
                        return chain.filter(exchange);
                    }
                    exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
                    return exchange.getResponse().setComplete();
                });
        };
    }
}

需要注意的是,流式响应在网关层不能做缓冲,必须配置为透传模式,否则会破坏 token 逐字输出的用户体验。

3.3 编排层设计要点

编排层是整个系统的"大脑",负责把多个能力服务组合成业务流程。典型的编排场景包括:多轮对话管理(先查历史、再检索知识、最后调用推理)、RAG 流程编排(检索-重排-生成)、工具调用编排(推理-调用外部API-再推理)。编排层需要维护会话状态、处理中间结果、做降级路由,是业务逻辑最集中的地方。

3.4 能力层服务清单

能力层是本次拆分的重点,包含以下独立服务:

  • **AI 推理服务**:封装对大模型的调用,支持多模型路由、流式输出、重试降级。
  • **Prompt 管理服务**:统一管理 Prompt 模板的版本、变量渲染、AB 测试。
  • **Embedding 向量服务**:把文本转向量,支持多模型切换。
  • **向量检索服务**:封装向量数据库的增删改查,提供相似度检索能力。
  • **知识库管理服务**:管理文档的解析、切分、入库全流程。

后续文章会逐一展开每个服务的设计细节。

3.5 数据层选型

数据层需要支撑多种数据形态:向量数据用 Milvus 或 Qdrant,关系数据用 MySQL,缓存用 Redis,对象存储用 MinIO。数据层本身不构成微服务,而是被能力层的服务调用。这里的关键原则是:每个能力服务只访问自己专属的存储,不跨服务共享数据库,避免存储层耦合。

4.1 五大拆分原则

大模型微服务拆分应遵循以下五条原则,避免常见的拆分陷阱。

**原则一:单一职责且高内聚。** 一个服务只做一件事,且把完成这件事所需的所有逻辑内聚在服务内部。AI 推理服务只负责"把 Prompt 送进模型、把输出返回出来",不负责会话上下文拼接、不负责知识检索。会话拼接属于编排层职责,知识检索属于向量检索服务职责。

**原则二:按业务能力而非技术层切分。** 不要按"Controller 层一个服务、Service 层一个服务、DAO 层一个服务"这样横向切,那是"分布式单体"。正确的切法是按业务能力纵向切,每个服务包含自己完整的 Controller-Service-DAO 栈。

**原则三:数据独占,不共享数据库。** 服务之间不直接访问对方的数据库表,所有数据交换通过 API 进行。这条原则是微服务松耦合的底线,一旦共享数据库,拆分就名存实亡。

**原则四:接口先行,契约稳定。** 服务拆分前先定义接口契约(OpenAPI/Protobuf),契约确定后再各自实现。接口契约的变更需要走版本化,避免破坏性变更导致调用方连锁故障。

**原则五:可独立部署、可独立扩缩容。** 每个服务必须有独立的部署流水线和扩缩容策略,不能依赖其他服务的部署节奏。这是拆分能否带来实际收益的检验标准。

4.2 服务粒度的度量指标

拆分粒度太粗等于没拆,太细则运维成本爆炸。可以用以下指标来校验拆分粒度是否合理。

**服务内聚度(Cohesion):** 衡量一个服务内各模块的相关程度。可以用"功能内聚度"来定性判断——服务内的所有方法是否都在为同一个业务目标服务。如果发现一个服务里有两个完全不相关的功能模块,说明它该继续拆。

**服务耦合度(Coupling):** 衡量服务间依赖的强度。可以用"同步调用链路深度"来量化:A 调 B、B 调 C,深度为 3。理想情况下服务间同步调用深度不超过 2,超过 3 就要考虑用异步事件解耦。

**变更频率一致性:** 一个服务内的所有模块应该有相近的变更频率。如果某个模块每周改一次,其他模块半年才改一次,说明高频模块应该独立出去。

**数据边界清晰度:** 一个服务的数据应该能被一个明确的领域概念覆盖。如果说不清"这个服务的数据属于哪个领域",说明边界还没理清。

下面是一段用于统计服务间调用关系的埋点代码,可用于量化耦合度。

@Aspect
@Component
public class ServiceCallTraceAspect {
    private static final String TRACE_HEADER = "X-Trace-Id";
    private static final int MAX_DEPTH = 10;
    @Autowired
    private MeterRegistry meterRegistry;
    @Around("@annotation(feign.Client) || @within(feign.Client)")
    public Object traceFeignCall(ProceedingJoinPoint pjp) throws Throwable {
        String caller = resolveCallerService();
        String callee = resolveCalleeService(pjp);
        String method = pjp.getSignature().getName();
        Timer.Sample sample = Timer.start(meterRegistry);
        try {
            Object result = pjp.proceed();
            recordCall(caller, callee, method, "success", sample);
            return result;
        } catch (Throwable e) {
            recordCall(caller, callee, method, "error", sample);
            throw e;
        }
    }
    private void recordCall(String caller, String callee, String method,
                            String outcome, Timer.Sample sample) {
        sample.stop(Timer.builder("service.call")
            .tag("caller", caller)
            .tag("callee", callee)
            .tag("method", method)
            .tag("outcome", outcome)
            .register(meterRegistry));
    }
}

通过 Prometheus 采集这些指标,可以生成服务间调用关系图,直观看到哪些服务耦合过紧、哪些调用链路过深。

4.3 拆分时机的判断

不是所有系统一上来就该拆。当出现以下信号时,才考虑启动拆分:

  • 单体应用的发布频率被大模型相关变更拖累,核心业务发布受限。
  • 大模型相关请求的故障传导到核心业务,造成非预期不可用。
  • 大模型模块的扩容需求和业务模块不一致,被迫整体过度扩容。
  • 团队规模增长,多人在同一代码库冲突频繁。

如果以上信号都没有出现,保持单体是更务实的选择。

5.1 整体技术栈

本系列采用 Java 生态最成熟的 Spring Cloud Alibaba 作为微服务基座,结合大模型场景做必要扩展。核心组件选型如下。

关注点

选型

选型理由

--------

------

---------

服务注册发现

Nacos

配置中心一体化,运维简单

服务通信

OpenFeign + Resilience4j

声明式调用+成熟熔断

网关

Spring Cloud Gateway

原生支持 SSE 流式

配置中心

Nacos Config

支持热更新

链路追踪

SkyWalking

对 Java 侵入小

指标监控

Prometheus + Grafana

生态成熟

消息队列

RocketMQ

事务消息支持好

向量数据库

Milvus

开源、Java SDK 完善

缓存

Redis

通用

5.2 统一的依赖管理

为了确保各服务版本一致,建议用一个统一的 BOM 管理依赖版本。下面是父 pom 的关键片段。

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example.llm</groupId>
    <artifactId>llm-microservices-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <properties>
        <java.version>17</java.version>
        <spring-boot.version>3.2.0</spring-boot.version>
        <spring-cloud.version>2023.0.0</spring-cloud.version>
        <spring-cloud-alibaba.version>2023.0.1.0</spring-cloud-alibaba.version>
        <resilience4j.version>2.2.0</resilience4j.version>
        <milvus-sdk.version>2.3.4</milvus-sdk.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

5.3 公共 starter 抽取

各服务共享的通用能力(如统一异常处理、链路追踪上下文、大模型调用客户端)应抽取为一个公共 starter,避免代码重复。公共 starter 的命名规范是 `llm-common-starter`,各服务通过引入该 starter 获得统一的基础能力。下面是公共 starter 中统一响应封装的示例。

public class ApiResult<T> {
    private int code;
    private String message;
    private T data;
    private String traceId;
    public static <T> ApiResult<T> success(T data) {
        ApiResult<T> r = new ApiResult<>();
        r.code = 0;
        r.message = "ok";
        r.data = data;
        r.traceId = MDC.get("traceId");
        return r;
    }
    public static <T> ApiResult<T> error(int code, String message) {
        ApiResult<T> r = new ApiResult<>();
        r.code = code;
        r.message = message;
        r.traceId = MDC.get("traceId");
        return r;
    }
}

6.1 服务全景

完成拆分后,系统将包含以下服务,各自职责明确。

服务名

职责

主要技术

独立扩容依据

--------

------

---------

-------------

business-gateway

接入、鉴权、限流

Gateway

QPS

orchestration-service

业务流程编排

Spring Boot

QPS

ai-inference-service

大模型推理调用

Spring Boot + vLLM

队列深度

prompt-service

Prompt 模板管理

Spring Boot + MySQL

QPS

embedding-service

文本向量化

Spring Boot + ONNX

QPS+GPU

vector-search-service

向量检索

Spring Boot + Milvus

QPS

knowledge-service

知识库管理

Spring Boot + MySQL

业务量

6.2 服务间调用关系

服务间调用关系遵循"只允许上层调用下层、不允许同层互调"的规则。具体的调用链路为:网关 -> 编排服务 -> 能力层服务(推理/Prompt/Embedding/检索/知识库)。能力层服务之间不直接调用,如果确实需要组合(如检索后重排),由编排服务负责串联,避免能力层形成网状依赖。

6.3 典型链路:多轮对话

以一次多轮对话请求为例,完整的调用链路如下:

  1. 客户端请求到达网关,网关完成鉴权和限流。
  2. 网关路由到编排服务。
  3. 编排服务调用 Prompt 服务,渲染当前对话的 Prompt 模板。
  4. 编排服务调用向量检索服务,获取相关知识片段。
  5. 编排服务把知识片段拼入 Prompt,调用 AI 推理服务。
  6. 推理服务流式返回结果,编排服务透传给客户端。

这条链路涉及 5 个服务,链路深度为 3(网关->编排->能力层),符合前面提到的"深度不超过 2"的建议需要靠异步化优化——在后续章节会讨论如何用预处理和缓存把关键路径深度降下来。

7.1 同步通信与异步通信的选择

大模型微服务间的通信方式需要按场景区分。同步通信(HTTP/gRPC)适合需要实时结果的场景,如推理调用、Prompt 渲染;异步通信(消息队列)适合解耦和削峰的场景,如知识库文档入库、向量索引重建。

选择同步还是异步的核心判断是:**调用方是否需要立即拿到结果**。推理调用必须同步,因为用户在等回答;知识库文档入库可以异步,因为用户上传文档后不需要立即看到向量化结果。

7.2 同步通信的容错模式

同步调用必须配套容错模式,否则一个下游服务的故障会通过线程阻塞传导到整个链路。Resilience4j 提供了三种核心容错模式:熔断器(CircuitBreaker)、隔离舱(Bulkhead)、限流(RateLimiter)。下面是推理服务调用的容错配置示例。

@FeignClient(name = "ai-inference-service", fallback = InferenceFallback.class)
public interface InferenceClient {
    @PostMapping(value = "/v1/chat/completions",
                 consumes = MediaType.APPLICATION_JSON_VALUE)
    @CircuitBreaker(name = "inference", fallbackMethod = "fallbackChat")
    @Bulkhead(name = "inference", type = Bulkhead.Type.SEMAPHORE)
    @Retry(name = "inference")
    InferenceResponse chat(@RequestBody InferenceRequest request);
    default InferenceResponse fallbackChat(InferenceRequest req, Throwable t) {
        // 降级:返回缓存的历史回答或标准兜底文案
        return InferenceResponse.builder()
            .content("当前服务繁忙,请稍后重试")
            .fallback(true)
            .build();
    }
}

对应的 application.yml 配置:

resilience4j:
  circuitbreaker:
    instances:
      inference:
        sliding-window-size: 20
        minimum-number-of-calls: 10
        failure-rate-threshold: 50
        wait-duration-in-open-state: 10s
        permitted-number-of-calls-in-half-open-state: 5
  bulkhead:
    instances:
      inference:
        max-concurrent-calls: 30
        max-wait-duration: 0
  retry:
    instances:
      inference:
        max-attempts: 3
        wait-duration: 500ms

这里有几个关键参数需要根据大模型特性调整:熔断器的窗口大小不能太小,因为推理调用本身耗时长,窗口太小会导致统计样本不足;隔离舱的并发数要和推理服务的实际承载能力匹配,而不是和调用方线程池匹配。

7.3 数据一致性:最终一致 + 事件驱动

大模型微服务涉及的数据一致性场景主要有两类:知识库文档状态同步(文档入库后要通知检索服务更新索引)、Prompt 版本发布同步(新版本发布后要通知编排服务刷新缓存)。这类场景适合用"本地消息表 + 消息队列"的最终一致性方案。

核心思路是:业务操作和消息记录在同一个本地事务里写入,然后由一个后台任务轮询消息表、投递到 RocketMQ,消费方消费成功后回写状态。这样即使消息队列暂时不可用,消息也不会丢失,保证了最终一致性。下面是本地消息表的核心实现。

@Service
public class LocalMessagePublisher {
    @Autowired
    private MessageTableMapper messageMapper;
    @Autowired
    private RocketMQTemplate mqTemplate;
    @Transactional(rollbackFor = Exception.class)
    public void publishWithBusiness(String topic, Object payload,
                                    Runnable businessLogic) {
        // 1. 执行业务逻辑(同事务)
        businessLogic.run();
        // 2. 写入本地消息表(同事务)
        MessageEntity msg = new MessageEntity();
        msg.setTopic(topic);
        msg.setPayload(JsonUtils.toJson(payload));
        msg.setStatus("PENDING");
        msg.setRetryCount(0);
        msg.setCreateTime(LocalDateTime.now());
        messageMapper.insert(msg);
    }
    // 定时任务每秒扫描待发送消息
    @Scheduled(fixedDelay = 1000)
    public void scanAndSend() {
        List<MessageEntity> pending = messageMapper.selectPending(100);
        for (MessageEntity msg : pending) {
            try {
                mqTemplate.convertAndSend(msg.getTopic(), msg.getPayload());
                messageMapper.markSent(msg.getId());
            } catch (Exception e) {
                messageMapper.incrementRetry(msg.getId());
                if (msg.getRetryCount() >= 5) {
                    messageMapper.markFailed(msg.getId());
                }
            }
        }
    }
}

8.1 不要一步到位

微服务拆分最大的陷阱是"一步到位"式的大爆炸重写。正确的方式是采用绞杀者模式(Strangler Fig Pattern),逐步把能力从单体中剥离,每剥离一块就独立部署、独立验证,确保业务连续性。大模型微服务的演进通常分四个阶段。

8.2 阶段一:旁路验证

在不动现有单体的前提下,先旁路部署一个独立的 AI 推理服务,用少量流量灰度验证。这个阶段的目标是验证独立部署的推理服务在性能、稳定性上是否达标,以及网络通信的延迟是否可接受。验证通过后再进入下一阶段。

8.3 阶段二:能力剥离

把 Prompt 管理、Embedding 服务、向量检索等"纯能力"服务逐个从单体剥离。这些服务业务逻辑相对独立、对外依赖少,剥离风险最低。每剥离一个服务,单体里对应的代码就删除,改为调用新服务,这就是"绞杀"的过程。

8.4 阶段三:编排独立

当所有能力服务都剥离后,再把编排层从单体中独立出来。编排层涉及的业务逻辑最多、依赖最复杂,放在最后剥离风险最低。这一阶段要重点处理会话状态的迁移,把原来存在单体内存里的会话上下文迁到 Redis,实现无状态化。

8.5 阶段四:单体瘦身

最后,原来的单体只剩纯业务逻辑(用户、订单、支付),它本身可以继续保持单体,也可以按需进一步拆分。到这一步,大模型相关能力已经完全解耦,可以独立扩容维护了。

下面用一段代码展示阶段二中"绞杀"时的灰度路由:通过配置控制流量比例,逐步把推理调用从单体内部切换到独立服务。

@Service
public class InferenceRouter {
    @Value("${inference.external.enabled:false}")
    private boolean externalEnabled;
    @Value("${inference.external.ratio:0}")
    private double externalRatio;
    @Autowired
    private LegacyInferenceService legacyService;  // 单体内旧实现
    @Autowired
    private InferenceClient externalClient;        // 独立服务客户端
    public InferenceResponse infer(InferenceRequest req) {
        if (!externalEnabled) {
            return legacyService.infer(req);
        }
        // 按比例灰度
        if (ThreadLocalRandom.current().nextDouble() < externalRatio) {
            try {
                return externalClient.chat(req);
            } catch (Exception e) {
                log.warn("外部推理服务失败,回退旧实现", e);
                return legacyService.infer(req);
            }
        }
        return legacyService.infer(req);
    }
}

通过动态调整 `externalRatio` 从 0 逐步到 1,实现平滑切换,出问题随时回退。

9.1 大模型服务的容量特征

大模型微服务的容量规划和传统服务有显著差异。传统服务通常用"QPS × 平均耗时 ÷ 单实例并发数"估算实例数,但大模型推理服务的耗时分布是长尾的(短回答 1 秒、长回答 30 秒),用平均值估算会严重偏差。更合理的做法是用排队论模型:把推理服务看作 M/M/c 排队系统,根据到达率和服务率计算等待时长和所需实例数。

9.2 基于 GPU 利用率的扩容

AI 推理服务的扩容不能只看 CPU 和内存,必须引入 GPU 利用率指标。下面是一段基于自定义指标的扩容规则配置(Kubernetes HPA + 自定义指标)。

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: ai-inference-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: ai-inference-service
  minReplicas: 2
  maxReplicas: 20
  metrics:
  - type: Pods
    pods:
      metric:
        name: inference_queue_depth
      target:
        type: AverageValue
        averageValue: "4"   # 每实例队列深度超过4则扩容
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 75
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
      - type: Percent
        value: 100
        periodSeconds: 30
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 25
        periodSeconds: 60

关键设计点:扩容窗口短(30 秒),因为推理请求积压会快速恶化用户体验;缩容窗口长(300 秒),避免流量波动导致频繁缩扩。队列深度指标由推理服务通过 Prometheus 暴露,比 CPU 利用率更能反映真实负载。

9.3 预热与冷启动优化

大模型推理服务的新实例冷启动较慢(模型加载可能需要 30-60 秒),直接接入流量会导致首批请求超时。解决方案是在 readiness probe 中检查模型是否加载完成,并在实例启动后做一次预热推理,确保接入流量时已就绪。

10.1 总体设计要点回顾

本章确立了整个系列的方法论基础:大模型微服务拆分的核心驱动是资源异构、故障隔离和演进节奏;总体架构分接入层、编排层、能力层、数据层四层;拆分遵循单一职责、按业务能力切分、数据独占、接口先行、可独立部署五原则;演进采用绞杀者模式分四阶段推进。

10.2 最佳实践清单

  1. **先接口后实现**:拆分前先定义服务间接口契约,用 OpenAPI 描述,各方按契约开发。
  2. **流式响应透传**:网关和编排层对 SSE 流式响应必须透传,不能缓冲。
  3. **容错三件套**:每个同步调用都配熔断、隔离舱、重试,参数按大模型特性调优。
  4. **灰度绞杀**:用比例路由逐步切换流量,保留旧实现作为兜底,随时可回退。
  5. **队列深度扩容**:推理服务按队列深度而非 CPU 扩容,更贴合长耗时请求特征。
  6. **预热就绪**:新实例必须预热完成后才接流量,避免冷启动超时。
  7. **本地消息表**:跨服务数据一致性用本地消息表 + MQ 的最终一致方案,不用强一致事务。
  8. **指标埋点**:服务间调用关系必须埋点,用数据驱动拆分粒度优化。
  9. **不共享数据库**:这是底线,一旦共享数据库,拆分就失去意义。
  10. **异步化降低链路深度**:关键路径的同步调用深度超过 2 就要考虑异步化或预处理。

10.3 后续章节预告

接下来的四篇文章将分别深入拆分的具体环节:第 02 篇讲如何识别拆分边界、确定服务粒度;第 03 篇讲 AI 推理服务的独立化设计;第 04 篇讲 Prompt 管理服务的实现;第 05 篇讲 Embedding 向量服务的设计与部署。每一篇都会给出完整的代码示例和落地经验,帮助读者把方法论转化为可运行的系统。

> 本文是"Java 程序员第 44 阶段"系列的第 01 篇,聚焦大模型微服务拆分的总体设计思路。系列共 05 篇,建议按顺序阅读。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐