微服务并发增加后,先守住哪条线

并发上来后,最先要守住的是入口的容量边界、排队策略和降级条件,而不是急着增加线程。本文的压测数字只用于说明观测方法,实际阈值应由服务容量测试确定。

Prometheus 监控监控曲线上,reactor-http-nio 线程全被堵在等待大模型推理服务的 Response 上。紧接着,下游订单微服务和风控微服务的 RPC 调用开始大面积超时,整条 Spring Cloud 调用链发生级联雪崩。

在大模型与预测建模接入 Spring Cloud 微服务体系后,系统的瓶颈不再是 CPU 和 DB,而是长尾等待延时极高的大模型服务

传统微服务里每秒处理 5000 个 HTTP 请求轻轻松松,但只要其中有 100 个请求需要调用 LLM 预测接口,连接就会在 Spring Cloud Gateway 挂起数秒。

当并发陡增时,第一条必须守住的防线就是:网关层的背压(Backpressure)与隔离闸门

# 启动 10000 QPS 持续 30 秒的压测击打 Spring Cloud Gateway
echo "GET ${GATEWAY_BASE_URL}/api/v1/ai/predict" | vegeta attack -rate=<rate> -duration=<duration> | vegeta report

# 查看 Spring Cloud Gateway 线程状态,排查是否有大量 NIO 线程处于 Blocked / Waiting 状态
jstack 88210 | grep -A 10 "reactor-http-nio" | grep "State:" | sort | uniq -c

# 查看 Actuator 暴露的 Resilience4j 熔断器实时指标
curl -s "http://localhost:8080/actuator/metrics/resilience4j.circuitbreakers.calls?tag=state:successful"

流量冲击下的双重背压隔离架构

在 Spring Cloud 体系中引入长耗时 AI 推理服务时,物理上必须将“高频轻量业务”与“长耗时 AI 业务”进行线程与信号量级别的物理隔离。

防护体系建立在两个关键原则上:

  1. 舱壁隔离(Bulkhead):强行给 AI 推理接口设定独立的最大并发连接数(如 200)。即便 200 个连接全部卡死在 LLM 响应上,另外 14800 个标准微服务请求依然能在 10ms 内快速处理。
  2. 响应式背压(Reactive Backpressure):利用 Project Reactor 的 onBackpressureDroprequest(n) 机制,当下游消费跟不上上游推送时,网关直接拒绝新连接,而不是把请求堆在 JVM 内存队列里。

生产级 Reactive 网关背压与舱壁过滤器实现

基于 Spring Cloud Gateway 的 AbstractGatewayFilterFactory 实现一套兼具限流、信号量隔离与背压控制的生产级 Filter。

package com.company.cloud.gateway.filter;

import io.github.resilience4j.bulkhead.BulkheadFullException;
import io.github.resilience4j.reactor.bulkhead.operator.BulkheadOperator;
import io.github.resilience4j.bulkhead.ReactiveBulkhead;
import io.github.resilience4j.bulkhead.ReactiveBulkheadRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

@Component
public class AIBackpressureGatewayFilterFactory extends AbstractGatewayFilterFactory<AIBackpressureGatewayFilterFactory.Config> {

    private static final Logger log = LoggerFactory.getLogger(AIBackpressureGatewayFilterFactory.class);
    private final ReactiveBulkhead aiServiceBulkhead;

    public AIBackpressureGatewayFilterFactory(ReactiveBulkheadRegistry bulkheadRegistry) {
        super(Config.class);
        // 初始化针对 AI 服务的响应式舱壁,限定最大并发数为 200
        this.aiServiceBulkhead = bulkheadRegistry.bulkhead("aiInferenceService");
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            // 拦截 AI 推理路由
            return chain.filter(exchange)
                    .transformDeferred(BulkheadOperator.of(aiServiceBulkhead))
                    .onErrorResume(BulkheadFullException.class, ex -> handleOverload(exchange, "AI 推理通道并发过载,已触发快速拒绝"))
                    .onErrorResume(Throwable.class, ex -> handleGenericError(exchange, ex));
        };
    }

    private Mono<Void> handleOverload(ServerWebExchange exchange, String reason) {
        log.warn("网关背压触顶: Path={}, Reason={}", exchange.getRequest().getPath(), reason);
        exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
        exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);

        String jsonFallback = """
            {
                "code": 429,
                "message": "AI 服务繁忙,已触发服务背压防护",
                "fallback": true
            }
            """;
        byte[] bytes = jsonFallback.getBytes(StandardCharsets.UTF_8);
        return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(bytes)));
    }

    private Mono<Void> handleGenericError(ServerWebExchange exchange, Throwable ex) {
        log.error("网关异常: {}", ex.getMessage());
        exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
        return exchange.getResponse().setComplete();
    }

    public static class Config {
        // 可扩充配置项
    }
}

并在 application.yml 里面进行微服务网关路由配置:

spring:
  cloud:
    gateway:
      routes:
        - id: ai_inference_route
          uri: lb://ai-predict-service
          predicates:
            - Path=/api/v1/ai/**
          filters:
            - name: AIBackpressureGatewayFilter
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 100
                redis-rate-limiter.burstCapacity: 200

流量突发时的容量估算公式与防线部署

在生产环境部署 Spring Cloud 微服务时,防线的容量估算必须遵循严密的物理公式,不能凭感觉设置并发数。

1. AI 接口并发容量估算公式

$$ConcurrentLimit = \frac{ClusterTargetQPS \times P99Latency(s)}{InstanceCount}$$

假设生产环境预计承受的 AI 预测流量峰值为 $2000\text{ QPS}$,当前 AI 后端模型推理的 P99 延迟为 $1.5\text{ 秒}$,部署了 $10$ 台 Spring Cloud Gateway 实例。那么每台网关实例分配给 AI 路由的舱壁上限为:
$$Limit = \frac{2000 \times 1.5}{10} = 300\text{ 并发连接}$$

2. 线程池与 Netty 堆外内存防线

在 Spring Cloud Gateway 中,由于使用了 Netty 响应式网络通信,如果大量的请求在等待 AI 服务返回 Body,每个连接都会占用一定量的 Direct Memory(堆外内存)。

# 检查 Spring Cloud Gateway 进程的堆外内存占用情况
jcmd 88210 VM.native_memory baseline
jcmd 88210 VM.native_memory detail.diff | grep "Other"

如果堆外内存随并发飙升,必须在 JVM 启动参数中显式设置 -XX:MaxDirectMemorySize=2G,防止因为 Reactive 背压失效导致操作系统触发 OOM Killer 杀掉网关进程。

当并发洪峰涌入时,守住舱壁隔离和响应式背压这两条底线,Spring Cloud 微服务集群才不会因为某个慢服务的挂起而瘫痪。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐