{T}

网关与限流熔断

微服务一旦拆开,流量通常不会再直接进入某个具体服务,而是先经过统一入口。这个入口通常就是 API 网关。

与此同时,系统还要面对这些问题:

  • 突发流量会不会把下游打垮
  • 某个依赖抖动时会不会拖跨整条链路
  • 恶意流量和异常请求怎么拦
  • 某些功能出问题时能不能有序退让

所以在微服务体系里,网关、限流、熔断、降级并不是可有可无的"附加能力",而是稳定性治理的核心组成部分。

一、网关核心原理

1.1 为什么微服务需要统一入口

没有网关时的问题

如果没有网关,客户端可能需要直接面对大量后端服务:

code
客户端需要知道所有服务地址:
┌─────────┐
│ 客户端   │
└─────────┘
    ↓ 直接调用多个服务
    ├─→ 用户服务 (http://user-service:8081)
    ├─→ 订单服务 (http://order-service:8082)
    ├─→ 商品服务 (http://product-service:8083)
    └─→ 支付服务 (http://payment-service:8084)

问题:

  • 地址管理混乱 - 客户端要感知多个服务地址
  • 横切逻辑分散 - 鉴权、限流、灰度等逻辑散落在各个服务中
  • 接口治理困难 - 接口治理规则难以统一
  • 安全边界弱 - 外部流量直接接触内部服务
  • 协议不统一 - 客户端需要适配不同服务的协议

网关的核心价值

网关的核心价值,不是"多一层转发",而是把横切能力集中治理。

核心价值:

  • 统一入口 - 客户端只需要知道网关地址
  • 横切能力集中 - 鉴权、限流、灰度等逻辑集中在网关
  • 接口治理统一 - 统一的接口规范和治理规则
  • 安全边界强化 - 外部流量只接触网关,内部服务隔离
  • 协议适配 - 统一对外协议,内部可以使用不同协议

有网关的架构:

code
                    ┌─────────────┐
                    │   客户端     │
                    └─────────────┘
                          ↓
                    ┌─────────────┐
                    │  API 网关    │ ← 横切能力集中治理
                    │ - 路由转发   │
                    │ - 认证授权   │
                    │ - 限流熔断   │
                    │ - 灰度发布   │
                    │ - 日志监控   │
                    └─────────────┘
                          ↓
        ┌─────────────────┼─────────────────┐
        ↓                 ↓                 ↓
  ┌──────────┐      ┌──────────┐      ┌──────────┐
  │ 用户服务  │      │ 订单服务  │      │ 商品服务  │
  └──────────┘      └──────────┘      └──────────┘

1.2 网关通常承担哪些职责

核心职责

1. 路由转发 (Routing)

java
// Spring Cloud Gateway 路由配置
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("user-service", r -> r.path("/api/users/**")
            .filters(f -> f.stripPrefix(1))
            .uri("lb://user-service"))
        .route("order-service", r -> r.path("/api/orders/**")
            .filters(f -> f.stripPrefix(1))
            .uri("lb://order-service"))
        .build();
}

2. 认证授权 (Authentication & Authorization)

java
// 网关鉴权过滤器
@Component
public class AuthFilter implements GlobalFilter, Ordered {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        
        if (token == null || !validateToken(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        
        // 解析用户信息并透传
        UserInfo user = parseToken(token);
        ServerHttpRequest request = exchange.getRequest().mutate()
            .header("X-User-Id", user.getId())
            .header("X-User-Name", user.getName())
            .build();
        
        return chain.filter(exchange.mutate().request(request).build());
    }
    
    @Override
    public int getOrder() {
        return -100;
    }
}

3. 限流 (Rate Limiting)

java
// 基于 Redis 的限流配置
@Bean
public RedisRateLimiter redisRateLimiter() {
    return new RedisRateLimiter(100, 200); // replenishRate=100, burstCapacity=200
}

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("user-service", r -> r.path("/api/users/**")
            .filters(f -> f
                .stripPrefix(1)
                .requestRateLimiter(c -> c
                    .setRateLimiter(redisRateLimiter())
                    .setKeyResolver(ipKeyResolver())
                )
            )
            .uri("lb://user-service"))
        .build();
}

@Bean
public KeyResolver ipKeyResolver() {
    return exchange -> Mono.just(
        exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
    );
}

4. 熔断降级 (Circuit Breaker)

yaml
# application.yml
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - name: CircuitBreaker
              args:
                name: userServiceCircuitBreaker
                fallbackUri: forward:/fallback/user-service

5. 灰度发布 (Gray Release)

java
// 灰度路由配置
@Component
public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        // 从请求头获取灰度标识
        String grayVersion = request.getContext()
            .getServerWebExchange()
            .getRequest()
            .getHeaders()
            .getFirst("X-Gray-Version");
        
        // 选择对应版本的实例
        List<ServiceInstance> instances = serviceInstanceListSupplier.get();
        ServiceInstance instance = selectInstance(instances, grayVersion);
        
        return Mono.just(new DefaultResponse(instance));
    }
}

6. 日志监控 (Logging & Monitoring)

java
// 全局日志过滤器
@Component
public class AccessLogFilter implements GlobalFilter, Ordered {
    
    private static final Logger log = LoggerFactory.getLogger(AccessLogFilter.class);
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        long startTime = System.currentTimeMillis();
        
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            long duration = System.currentTimeMillis() - startTime;
            
            log.info("[网关访问日志] method={}, path={}, status={}, duration={}ms, ip={}",
                exchange.getRequest().getMethod(),
                exchange.getRequest().getPath(),
                exchange.getResponse().getStatusCode(),
                duration,
                exchange.getRequest().getRemoteAddress()
            );
        }));
    }
    
    @Override
    public int getOrder() {
        return -200;
    }
}

7. 协议转换 (Protocol Conversion)

java
// HTTP 转 gRPC
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("grpc-service", r -> r.path("/api/grpc/**")
            .filters(f -> f
                .stripPrefix(2)
                .grpc("grpc-service", 50051)
            )
            .uri("lb://grpc-service"))
        .build();
}

1.3 网关架构模式

集中式网关

架构:

code
                    ┌─────────────┐
                    │   客户端     │
                    └─────────────┘
                          ↓
                    ┌─────────────┐
                    │ API 网关集群 │ ← 所有流量经过统一网关
                    └─────────────┘
                          ↓
        ┌─────────────────┼─────────────────┐
        ↓                 ↓                 ↓
    服务实例          服务实例          服务实例

优点:

  • 统一治理,配置集中
  • 容易实现横切能力
  • 便于监控和排障

缺点:

  • 单点故障风险
  • 性能瓶颈
  • 团队耦合

分布式网关 (微网关)

架构:

code
                    ┌─────────────┐
                    │   客户端     │
                    └─────────────┘
                          ↓
        ┌─────────────────┼─────────────────┐
        ↓                 ↓                 ↓
  ┌──────────┐      ┌──────────┐      ┌──────────┐
  │ Sidecar  │      │ Sidecar  │      │ Sidecar  │ ← 每个服务旁边都有网关代理
  │ Gateway  │      │ Gateway  │      │ Gateway  │
  └──────────┘      └──────────┘      └──────────┘
        ↓                 ↓                 ↓
  ┌──────────┐      ┌──────────┐      ┌──────────┐
  │ 用户服务  │      │ 订单服务  │      │ 商品服务  │
  └──────────┘      └──────────┘      └──────────┘

优点:

  • 去中心化,无单点故障
  • 性能好,就近转发
  • 语言无关

缺点:

  • 配置分散,治理复杂
  • 运维成本高
  • 需要服务网格支持

1.4 网关性能优化

响应式编程

Spring Cloud Gateway 基于 WebFlux,使用响应式编程:

java
// 响应式过滤器
@Component
public class ReactiveAuthFilter implements GlobalFilter {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return Mono.fromCallable(() -> validateToken(exchange))
            .flatMap(valid -> {
                if (valid) {
                    return chain.filter(exchange);
                } else {
                    exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                    return exchange.getResponse().setComplete();
                }
            });
    }
}

缓存策略

java
// 响应缓存
@Component
public class CacheFilter implements GlobalFilter {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String cacheKey = generateCacheKey(exchange.getRequest());
        
        // 尝试从缓存获取
        return Mono.fromCallable(() -> redisTemplate.opsForValue().get(cacheKey))
            .flatMap(cached -> {
                if (cached != null) {
                    // 返回缓存内容
                    exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
                    DataBuffer buffer = exchange.getResponse().bufferFactory()
                        .wrap(cached.getBytes(StandardCharsets.UTF_8));
                    return exchange.getResponse().writeWith(Mono.just(buffer));
                } else {
                    // 执行请求并缓存结果
                    return cacheResponse(exchange, chain, cacheKey);
                }
            });
    }
}

连接池优化

yaml
# application.yml
spring:
  cloud:
    gateway:
      httpclient:
        pool:
          type: ELASTIC
          max-idle-time: 15000
          max-connections: 500
        connect-timeout: 3000
        response-timeout: 5000

二、主流网关对比

2.1 网关技术选型

网关语言核心特点适用场景学习曲线
Spring Cloud GatewayJavaSpring 生态、功能丰富、响应式Spring Cloud 微服务
KongLua/Nginx高性能、插件生态、云原生大规模 API 管理
APISIXLua/Nginx云原生、动态配置、高性能Kubernetes 环境
NginxC极致性能、稳定可靠基础反向代理、负载均衡
EnvoyC++服务网格、可观测性强Service Mesh
ZuulJavaNetflix 开源、生态成熟传统 Spring Cloud低 (已过时)

2.2 Spring Cloud Gateway 详解

核心概念

1. Route (路由)

路由是网关的基本构建块,包含:

  • ID: 路由唯一标识
  • URI: 目标服务地址
  • Predicates: 断言,匹配条件
  • Filters: 过滤器,修改请求/响应

2. Predicate (断言)

java
// 内置断言工厂
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        // 路径匹配
        .route("path-route", r -> r.path("/api/users/**")
            .uri("lb://user-service"))
        
        // 方法匹配
        .route("method-route", r -> r.method("GET")
            .uri("lb://user-service"))
        
        // 请求头匹配
        .route("header-route", r -> r.header("X-Request-Id", "\\d+")
            .uri("lb://user-service"))
        
        // 时间匹配
        .route("time-route", r -> r.between(
            LocalDateTime.of(2024, 1, 1, 0, 0),
            LocalDateTime.of(2024, 12, 31, 23, 59))
            .uri("lb://user-service"))
        
        // Cookie 匹配
        .route("cookie-route", r -> r.cookie("session", "^[a-z]+$")
            .uri("lb://user-service"))
        
        .build();
}

3. Filter (过滤器)

java
// 内置过滤器
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("user-service", r -> r.path("/api/users/**")
            .filters(f -> f
                // 添加请求头
                .addRequestHeader("X-Request-Foo", "Bar")
                
                // 添加请求参数
                .addRequestParameter("foo", "bar")
                
                // 添加响应头
                .addResponseHeader("X-Response-Foo", "Bar")
                
                // 去掉路径前缀
                .stripPrefix(1)
                
                // 添加路径前缀
                .prefixPath("/api")
                
                // 重定向
                .redirect(HttpStatus.MOVED_PERMANENTLY, "https://example.com")
                
                // 重试
                .retry(config -> config
                    .setRetries(3)
                    .setStatuses(HttpStatus.INTERNAL_SERVER_ERROR)
                    .setMethods(HttpMethod.GET)
                )
            )
            .uri("lb://user-service"))
        .build();
}

完整配置示例

yaml
# application.yml
spring:
  cloud:
    gateway:
      # 全局跨域配置
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - PUT
              - DELETE
            allowedHeaders: "*"
            allowCredentials: true
            maxAge: 3600
      
      # 路由配置
      routes:
        # 用户服务
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 100
                redis-rate-limiter.burstCapacity: 200
                key-resolver: "#{@ipKeyResolver}"
        
        # 订单服务
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=1
            - name: CircuitBreaker
              args:
                name: orderServiceCircuitBreaker
                fallbackUri: forward:/fallback/order-service
        
        # 商品服务
        - id: product-service
          uri: lb://product-service
          predicates:
            - Path=/api/products/**
            - Method=GET
          filters:
            - StripPrefix=1
            - AddRequestHeader=X-Request-Source, gateway
      
      # 默认过滤器
      default-filters:
        - AddResponseHeader=X-Response-Time, ${spring.cloud.gateway.response-timeout}
      
      # 全局配置
      httpclient:
        connect-timeout: 3000
        response-timeout: 5000
        pool:
          type: elastic
          max-idle-time: 15000

自定义过滤器

1. 全局过滤器

java
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    
    private static final Logger log = LoggerFactory.getLogger(AuthGlobalFilter.class);
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        
        if (token == null) {
            log.warn("请求缺少认证token, path={}", exchange.getRequest().getPath());
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        
        try {
            // 验证 token
            UserInfo user = JwtUtil.parseToken(token);
            
            // 透传用户信息
            ServerHttpRequest request = exchange.getRequest().mutate()
                .header("X-User-Id", user.getId().toString())
                .header("X-User-Name", user.getUsername())
                .header("X-User-Roles", String.join(",", user.getRoles()))
                .build();
            
            return chain.filter(exchange.mutate().request(request).build());
            
        } catch (Exception e) {
            log.error("Token验证失败: {}", e.getMessage());
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
    }
    
    @Override
    public int getOrder() {
        return -100; // 优先级高
    }
}

2. 网关过滤器工厂

java
@Component
public class RequestTimeGatewayFilterFactory 
        extends AbstractGatewayFilterFactory<RequestTimeGatewayFilterFactory.Config> {
    
    public RequestTimeGatewayFilterFactory() {
        super(Config.class);
    }
    
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            long startTime = System.currentTimeMillis();
            
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                long duration = System.currentTimeMillis() - startTime;
                
                if (config.isShowHeader()) {
                    exchange.getResponse().getHeaders()
                        .add("X-Response-Time", duration + "ms");
                }
                
                log.info("请求耗时: {}ms, path={}", 
                    duration, 
                    exchange.getRequest().getPath()
                );
            }));
        };
    }
    
    public static class Config {
        private boolean showHeader = true;
        
        public boolean isShowHeader() {
            return showHeader;
        }
        
        public void setShowHeader(boolean showHeader) {
            this.showHeader = showHeader;
        }
    }
}

2.3 Kong 网关详解

Kong 简介

Kong 是基于 Nginx 和 OpenResty 的高性能 API 网关,核心特点:

  • 高性能,基于 Nginx
  • 插件生态丰富
  • 支持多种数据库 (PostgreSQL、Cassandra)
  • 提供管理 API 和 Dashboard

Kong 架构

code
┌─────────────────────────────────────────────────────────┐
│                    Kong Gateway                          │
│  ┌────────────────────────────────────────────────────┐ │
│  │                 Kong Core                           │ │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐        │ │
│  │  │ Router   │  │ Plugin   │  │ Admin    │        │ │
│  │  │ Engine   │  │ System   │  │ API      │        │ │
│  │  └──────────┘  └──────────┘  └──────────┘        │ │
│  └────────────────────────────────────────────────────┘ │
│  ┌────────────────────────────────────────────────────┐ │
│  │              Data Store (PostgreSQL)               │ │
│  └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘

Kong 核心概念

1. Service (服务)

bash
# 创建服务
curl -i -X POST http://localhost:8001/services \
  -d "name=user-service" \
  -d "url=http://user-service:8080"

2. Route (路由)

bash
# 创建路由
curl -i -X POST http://localhost:8001/services/user-service/routes \
  -d "name=user-route" \
  -d "paths[]=/api/users"

3. Plugin (插件)

bash
# 添加限流插件
curl -i -X POST http://localhost:8001/services/user-service/plugins \
  -d "name=rate-limiting" \
  -d "config.minute=100" \
  -d "config.policy=local"

Kong 插件生态

插件类别插件名称功能
安全JWTJWT 认证
OAuth2OAuth2 认证
API KeyAPI Key 认证
IP RestrictionIP 黑白名单
流量控制Rate Limiting限流
Request Termination请求终止
Bot Detection机器人检测
转换Request Transformer请求转换
Response Transformer响应转换
Correlation ID关联 ID
监控PrometheusPrometheus 监控
File Log文件日志
StatsDStatsD 监控

2.4 APISIX 网关详解

APISIX 简介

Apache APISIX 是一个云原生、高性能、可扩展的 API 网关,核心特点:

  • 云原生设计,支持 Kubernetes
  • 动态配置,无需重启
  • 高性能,基于 Nginx 和 OpenResty
  • 丰富的插件生态

APISIX 架构

code
┌─────────────────────────────────────────────────────────┐
│                    APISIX Gateway                        │
│  ┌────────────────────────────────────────────────────┐ │
│  │               APISIX Core                           │ │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐        │ │
│  │  │ Router   │  │ Plugin   │  │ Admin    │        │ │
│  │  │ Matcher  │  │ Runtime  │  │ API      │        │ │
│  │  └──────────┘  └──────────┘  └──────────┘        │ │
│  └────────────────────────────────────────────────────┘ │
│  ┌────────────────────────────────────────────────────┐ │
│  │          etcd (配置存储)                            │ │
│  └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘

APISIX 配置示例

yaml
# 创建路由
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "uri": "/api/users/*",
    "plugins": {
        "limit-req": {
            "rate": 100,
            "burst": 200,
            "rejected_code": 429
        },
        "jwt-auth": {}
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "user-service:8080": 1
        }
    }
}'

2.5 网关选型建议

场景化推荐

Spring Cloud 微服务:

code
首选: Spring Cloud Gateway
理由:
- 与 Spring Cloud 生态无缝集成
- 响应式编程,性能好
- 功能丰富,易于扩展
- 学习曲线低

Kubernetes 环境:

code
首选: APISIX 或 Kong
理由:
- 云原生设计
- 支持动态配置
- 与 Kubernetes 深度集成
- 高性能

高性能场景:

code
首选: Kong 或 Nginx
理由:
- 基于 Nginx,极致性能
- 成熟稳定
- 生产验证充分

服务网格:

code
首选: Envoy
理由:
- 服务网格标配
- 可观测性强
- 动态配置

三、限流详解

3.1 限流的本质

为什么需要限流

限流的目标不是"把所有请求拦住",而是:

  • 在系统承压过大时,明确拒绝一部分流量,保护整体系统还能继续工作

如果系统扛不住还强行全放行,结果通常不是"全部成功",而是:

  • 线程池打满
  • 连接池耗尽
  • RT 飙升
  • 错误率扩大
  • 故障从边缘扩散到核心链路

所以限流本质上是一种容量保护和故障隔离手段

限流的核心目标

1. 保护系统

code
正常情况:
请求量 < 系统容量 → 系统正常运行

限流场景:
请求量 > 系统容量 → 拒绝部分请求 → 系统仍能处理部分请求

2. 防止雪崩

code
无限流:
请求暴增 → 系统过载 → 所有请求失败 → 雪崩

有限流:
请求暴增 → 限流保护 → 拒绝多余请求 → 部分请求成功 → 系统稳定

3. 公平分配资源

code
无限流:
恶意流量占用所有资源 → 正常用户无法访问

有限流:
恶意流量被限制 → 正常用户可以正常访问

3.2 限流算法详解

固定窗口算法 (Fixed Window)

原理:

在固定时间窗口内限制请求数量。

code
时间窗口: 1秒
限流阈值: 100次

|------- 窗口1 -------|------- 窗口2 -------|
0ms               1000ms              2000ms
请求数: 100          请求数: 50
√ 放行              √ 放行

实现:

java
public class FixedWindowRateLimiter {
    
    private final int limit; // 窗口内最大请求数
    private final long windowSize; // 窗口大小(毫秒)
    
    private AtomicInteger counter = new AtomicInteger(0);
    private AtomicLong lastWindowStart = new AtomicLong(System.currentTimeMillis());
    
    public FixedWindowRateLimiter(int limit, long windowSize) {
        this.limit = limit;
        this.windowSize = windowSize;
    }
    
    public boolean tryAcquire() {
        long currentTime = System.currentTimeMillis();
        long windowStart = lastWindowStart.get();
        
        // 检查是否需要重置窗口
        if (currentTime - windowStart >= windowSize) {
            synchronized (this) {
                windowStart = lastWindowStart.get();
                if (currentTime - windowStart >= windowSize) {
                    counter.set(0);
                    lastWindowStart.set(currentTime);
                }
            }
        }
        
        // 检查是否超过限制
        return counter.incrementAndGet() <= limit;
    }
}

优点:

  • 实现简单
  • 内存占用小

缺点:

  • 边界突刺问题
code
|------- 窗口1 -------|------- 窗口2 -------|
                   900ms 100ms
                   请求100次 + 请求100次
                   
窗口边界处可能瞬间通过200次请求!

滑动窗口算法 (Sliding Window)

原理:

动态滑动时间窗口,更精确地统计最近一段时间内的请求量。

code
当前时间: 1500ms
窗口大小: 1000ms
统计范围: 500ms - 1500ms

|--- 窗口1 ---|--- 窗口2 ---|--- 窗口3 ---|
0ms        1000ms       2000ms       3000ms
          ↑
        当前统计窗口: 500ms - 1500ms

实现:

java
public class SlidingWindowRateLimiter {
    
    private final int limit; // 窗口内最大请求数
    private final long windowSize; // 窗口大小(毫秒)
    
    private final ConcurrentLinkedQueue<Long> timestamps = new ConcurrentLinkedQueue<>();
    
    public SlidingWindowRateLimiter(int limit, long windowSize) {
        this.limit = limit;
        this.windowSize = windowSize;
    }
    
    public boolean tryAcquire() {
        long currentTime = System.currentTimeMillis();
        long windowStart = currentTime - windowSize;
        
        // 移除窗口外的请求
        while (!timestamps.isEmpty() && timestamps.peek() < windowStart) {
            timestamps.poll();
        }
        
        // 检查是否超过限制
        if (timestamps.size() < limit) {
            timestamps.offer(currentTime);
            return true;
        }
        
        return false;
    }
}

优点:

  • 精度高,无边界突刺问题
  • 流量平滑

缺点:

  • 实现复杂
  • 内存占用较大

令牌桶算法 (Token Bucket)

原理:

系统按固定速率往桶里放令牌,请求需要从桶里获取令牌才能通过。

code
令牌桶:
┌─────────────────────────┐
│  ┌───┐ ┌───┐ ┌───┐     │ ← 令牌(最大容量)
│  │ ● │ │ ● │ │   │ ... │
│  └───┘ └───┘ └───┘     │
└─────────────────────────┘
        ↑
    按速率放令牌(如100个/秒)

请求到来时:
- 有令牌 → 取走令牌 → 放行
- 无令牌 → 拒绝

实现:

java
public class TokenBucketRateLimiter {
    
    private final long capacity; // 桶容量
    private final long rate; // 放令牌速率(个/秒)
    
    private final AtomicLong tokens = new AtomicLong(0);
    private final AtomicLong lastRefillTime = new AtomicLong(System.currentTimeMillis());
    
    public TokenBucketRateLimiter(long capacity, long rate) {
        this.capacity = capacity;
        this.rate = rate;
    }
    
    public boolean tryAcquire() {
        return tryAcquire(1);
    }
    
    public boolean tryAcquire(long permits) {
        refill();
        
        while (true) {
            long currentTokens = tokens.get();
            if (currentTokens < permits) {
                return false;
            }
            
            if (tokens.compareAndSet(currentTokens, currentTokens - permits)) {
                return true;
            }
        }
    }
    
    private void refill() {
        long currentTime = System.currentTimeMillis();
        long lastTime = lastRefillTime.get();
        
        if (currentTime > lastTime) {
            long elapsed = currentTime - lastTime;
            long newTokens = (elapsed * rate) / 1000;
            
            if (newTokens > 0) {
                while (true) {
                    long currentTokens = tokens.get();
                    long newTokenCount = Math.min(currentTokens + newTokens, capacity);
                    
                    if (tokens.compareAndSet(currentTokens, newTokenCount)) {
                        lastRefillTime.compareAndSet(lastTime, currentTime);
                        break;
                    }
                }
            }
        }
    }
}

优点:

  • 允许一定突发流量
  • 控制平均速率
  • 实现相对简单

缺点:

  • 需要精确的时钟同步

适用场景:

  • 网关限流
  • 接口限流
  • API 限流

漏桶算法 (Leaky Bucket)

原理:

请求先进入桶,桶以固定速率漏水(处理请求)。

code
漏桶:
请求 → ┌──────────────┐ → 处理
       │   ┌──────┐   │
       │   │ ●●●● │   │ ← 请求排队
       │   └──────┘   │
       └──────────────┘
              ↓
         固定速率流出
         (如100个/秒)

实现:

java
public class LeakyBucketRateLimiter {
    
    private final long capacity; // 桶容量
    private final long rate; // 流出速率(个/秒)
    
    private final ConcurrentLinkedQueue<Long> queue = new ConcurrentLinkedQueue<>();
    private final AtomicLong lastLeakTime = new AtomicLong(System.currentTimeMillis());
    
    public LeakyBucketRateLimiter(long capacity, long rate) {
        this.capacity = capacity;
        this.rate = rate;
    }
    
    public boolean tryAcquire() {
        leak();
        
        if (queue.size() < capacity) {
            queue.offer(System.currentTimeMillis());
            return true;
        }
        
        return false;
    }
    
    private void leak() {
        long currentTime = System.currentTimeMillis();
        long lastTime = lastLeakTime.get();
        
        if (currentTime > lastTime) {
            long elapsed = currentTime - lastTime;
            long leaks = (elapsed * rate) / 1000;
            
            if (leaks > 0) {
                // 移除已处理的请求
                for (long i = 0; i < leaks && !queue.isEmpty(); i++) {
                    queue.poll();
                }
                
                lastLeakTime.compareAndSet(lastTime, currentTime);
            }
        }
    }
}

优点:

  • 流量平滑
  • 削峰填谷

缺点:

  • 无法应对突发流量
  • 实现相对复杂

适用场景:

  • 数据库访问限流
  • 第三方 API 调用限流

算法对比

算法思路特点优点缺点适用场景
固定窗口固定时间窗口内限制请求数实现简单,但边界可能突刺实现简单,内存占用小边界突刺问题基础限流
滑动窗口更平滑地统计最近一段时间请求量精度高,实现更复杂精度高,无突刺实现复杂,内存大需要精确控制
令牌桶按固定速率放令牌,请求拿令牌才可通过兼顾平均速率和突发能力允许突发,控制平均速率需要时钟同步网关、接口限流
漏桶请求先进入桶,按固定速率流出流量更平稳流量平滑,削峰填谷无法应对突发数据库、第三方调用

3.3 限流维度

限流不能只做成全局一刀切,业务里更常见的是按不同维度分层限流:

常见限流维度

1. 用户维度

java
// 按用户ID限流
@Bean
public KeyResolver userKeyResolver() {
    return exchange -> Mono.just(
        exchange.getRequest().getHeaders().getFirst("X-User-Id")
    );
}

// 配置
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
                key-resolver: "#{@userKeyResolver}"

2. 接口维度

java
// 按接口路径限流
@Bean
public KeyResolver apiKeyResolver() {
    return exchange -> Mono.just(
        exchange.getRequest().getPath().value()
    );
}

3. IP 维度

java
// 按IP限流
@Bean
public KeyResolver ipKeyResolver() {
    return exchange -> Mono.just(
        exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
    );
}

4. 租户维度

java
// 按租户限流
@Bean
public KeyResolver tenantKeyResolver() {
    return exchange -> Mono.just(
        exchange.getRequest().getHeaders().getFirst("X-Tenant-Id")
    );
}

5. 组合维度

java
// 组合限流:用户+接口
@Bean
public KeyResolver compositeKeyResolver() {
    return exchange -> Mono.just(
        exchange.getRequest().getHeaders().getFirst("X-User-Id") + ":" +
        exchange.getRequest().getPath().value()
    );
}

3.4 分布式限流

为什么需要分布式限流

单机限流的问题:

code
集群环境:
服务实例1: 限流100/秒
服务实例2: 限流100/秒
服务实例3: 限流100/秒

总限流: 300/秒

问题:
- 负载不均时,某个实例可能先被打满
- 无法实现全局限流

基于 Redis 的分布式限流

Redis + Lua 实现:

java
@Component
public class RedisRateLimiter {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String SCRIPT =
        "local key = KEYS[1]\n" +
        "local limit = tonumber(ARGV[1])\n" +
        "local window = tonumber(ARGV[2])\n" +
        "local current = tonumber(ARGV[3])\n" +
        "\n" +
        "redis.call('zremrangebyscore', key, 0, current - window * 1000)\n" +
        "local count = redis.call('zcard', key)\n" +
        "\n" +
        "if count < limit then\n" +
        "    redis.call('zadd', key, current, current .. '-' .. math.random())\n" +
        "    redis.call('expire', key, window)\n" +
        "    return 1\n" +
        "else\n" +
        "    return 0\n" +
        "end";
    
    public boolean tryAcquire(String key, int limit, int windowSeconds) {
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(SCRIPT, Long.class),
            Collections.singletonList(key),
            String.valueOf(limit),
            String.valueOf(windowSeconds),
            String.valueOf(System.currentTimeMillis())
        );
        
        return result != null && result == 1;
    }
}

Spring Cloud Gateway 限流配置:

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 100
                redis-rate-limiter.burstCapacity: 200
                key-resolver: "#{@ipKeyResolver}"
  
  redis:
    host: localhost
    port: 6379

3.5 限流配置最佳实践

核心接口 vs 非核心接口

yaml
# 核心交易接口 - 高限流阈值
核心接口:
  - /api/orders/create: 1000/秒
  - /api/payments/process: 500/秒

# 非核心查询接口 - 中限流阈值
非核心接口:
  - /api/products/list: 500/秒
  - /api/users/profile: 200/秒

# 边缘接口 - 低限流阈值
边缘接口:
  - /api/comments/list: 100/秒
  - /api/recommendations: 50/秒

不同用户等级

yaml
# VIP用户 - 高限额
VIP用户:
  - 查询: 200/秒
  - 下单: 50/秒

# 普通用户 - 中等限额
普通用户:
  - 查询: 100/秒
  - 下单: 10/秒

# 匿名用户 - 低限额
匿名用户:
  - 查询: 20/秒
  - 下单: 禁止

动态调整

java
@Configuration
@RefreshScope
public class RateLimitConfig {
    
    @Value("${rate.limit.user-query:100}")
    private int userQueryLimit;
    
    @Value("${rate.limit.order-create:10}")
    private int orderCreateLimit;
    
    @Bean
    @RefreshScope
    public RateLimiter userQueryLimiter() {
        return RateLimiter.create(userQueryLimit);
    }
    
    @Bean
    @RefreshScope
    public RateLimiter orderCreateLimiter() {
        return RateLimiter.create(orderCreateLimit);
    }
}

四、熔断与降级详解

4.1 熔断的核心原理

为什么需要熔断

熔断面对的是这样一种场景:

  • 下游依赖已经明显不稳定
  • 继续请求只会更糟

例如:

  • 错误率明显升高
  • 超时率持续升高
  • 响应时间远超正常范围

这时如果上游还继续大量请求,只会进一步放大故障。

熔断的核心思路:

  1. 当错误率或超时率达到阈值
  2. 熔断器打开,短期内快速失败
  3. 进入半开状态后少量探测恢复
  4. 恢复成功后关闭熔断

它本质上是在做: 故障隔离

熔断器状态机

code
         失败率超过阈值
    ┌──────────────────────┐
    │                      │
    ▼                      │
┌─────────┐  探测成功  ┌─────────┐
│  关闭   │────────────>│  半开   │
│ (Closed)│            │ (Half-Open)│
└─────────┘            └─────────┘
    ▲                      │
    │      探测失败        │
    │  ┌──────────────────┘
    │  │
    │  ▼
┌─────────┐
│  打开   │
│ (Open)  │
└─────────┘
    │
    │ 超时后进入半开状态
    └────────────┐
                 │
                 ▼
            ┌─────────┐
            │  半开   │
            └─────────┘

状态说明:
- Closed (关闭): 正常状态,请求正常通过
- Open (打开): 熔断状态,快速失败
- Half-Open (半开): 探测状态,少量请求通过探测下游是否恢复

4.2 主流熔断框架对比

框架状态核心特点集成难度推荐度
Resilience4j活跃轻量级、模块化、响应式
Sentinel活跃功能全面、控制台可视化
Hystrix维护模式Netflix 开源、生态成熟(不推荐)

4.3 Resilience4j 详解

核心模块

模块功能
CircuitBreaker熔断器
RateLimiter限流器
Retry重试
Bulkhead舱壁隔离
TimeLimiter超时控制
Cache缓存

熔断器配置

yaml
# application.yml
resilience4j:
  circuitbreaker:
    configs:
      default:
        sliding-window-size: 100
        failure-rate-threshold: 50
        wait-duration-in-open-state: 10s
        permitted-number-of-calls-in-half-open-state: 10
        slow-call-duration-threshold: 2s
        slow-call-rate-threshold: 100
        minimum-number-of-calls: 10
    instances:
      userService:
        base-config: default
      orderService:
        sliding-window-size: 50
        failure-rate-threshold: 30

熔断器使用

java
@Service
public class OrderService {
    
    @Autowired
    private UserClient userClient;
    
    // 方式1: 注解方式
    @CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
    public User getUser(Long userId) {
        return userClient.getUser(userId);
    }
    
    // 降级方法
    public User getUserFallback(Long userId, Exception e) {
        log.warn("获取用户信息失败,使用降级数据: userId={}, error={}", userId, e.getMessage());
        return User.defaultUser(userId);
    }
    
    // 方式2: 编程方式
    public User getUserByCode(Long userId) {
        CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("userService");
        
        Supplier<User> supplier = CircuitBreaker.decorateSupplier(
            circuitBreaker,
            () -> userClient.getUser(userId)
        );
        
        return Try.ofSupplier(supplier)
            .recover(throwable -> User.defaultUser(userId))
            .get();
    }
}

熔断器监控

java
// 获取熔断器指标
@Component
public class CircuitBreakerMetrics {
    
    @EventListener
    public void onCircuitBreakerEvent(CircuitBreakerEvent event) {
        CircuitBreaker circuitBreaker = event.getCircuitBreaker();
        
        log.info("熔断器状态变更: name={}, state={}, failureRate={}, metrics={}",
            circuitBreaker.getName(),
            circuitBreaker.getState(),
            circuitBreaker.getMetrics().getFailureRate(),
            circuitBreaker.getMetrics()
        );
    }
}

4.4 Sentinel 详解

Sentinel 简介

Sentinel 是阿里巴巴开源的流量控制组件,核心特点:

  • 丰富的应用场景:限流、熔断、降级、系统保护
  • 完备的实时监控
  • 广泛的开源生态
  • 完善的 SPI 扩展机制

Sentinel 核心概念

1. 资源 (Resource)

java
// 资源定义
@GetMapping("/users/{id}")
@SentinelResource(value = "getUser", blockHandler = "handleBlock")
public User getUser(@PathVariable Long id) {
    return userService.getUser(id);
}

// 限流降级处理
public User handleBlock(Long id, BlockException ex) {
    return User.defaultUser(id);
}

2. 规则 (Rule)

java
// 限流规则
FlowRule flowRule = new FlowRule();
flowRule.setResource("getUser");
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
flowRule.setCount(100);

// 熔断规则
DegradeRule degradeRule = new DegradeRule();
degradeRule.setResource("getUser");
degradeRule.setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType());
degradeRule.setCount(0.5); // 错误率50%
degradeRule.setTimeWindow(10); // 熔断时长10秒
degradeRule.setMinRequestAmount(10); // 最小请求数
degradeRule.setStatIntervalMs(1000); // 统计时长

// 加载规则
List<FlowRule> flowRules = new ArrayList<>();
flowRules.add(flowRule);
FlowRuleManager.loadRules(flowRules);

List<DegradeRule> degradeRules = new ArrayList<>();
degradeRules.add(degradeRule);
DegradeRuleManager.loadRules(degradeRules);

Sentinel 控制台

yaml
# application.yml
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719
      eager: true # 立即初始化

控制台功能:

  • 实时监控
  • 规则配置
  • 集群流控
  • 机器列表

4.5 降级策略设计

降级的本质

降级更偏向业务层面的主动让步。例如:

  • 推荐服务异常时,首页暂时不显示个性化推荐
  • 会员权益服务异常时,先返回默认权益
  • 评论服务超时时,先只展示商品基础信息

可以简单理解为:

  • 熔断更偏"保护调用链"
  • 降级更偏"业务功能主动退让"

降级策略类型

1. 返回默认值

java
@SentinelResource(value = "getUser", fallback = "getUserFallback")
public User getUser(Long userId) {
    return userClient.getUser(userId);
}

public User getUserFallback(Long userId) {
    return User.defaultUser(userId); // 返回默认用户
}

2. 返回缓存值

java
@SentinelResource(value = "getProduct", fallback = "getProductFallback")
public Product getProduct(Long productId) {
    return productClient.getProduct(productId);
}

public Product getProductFallback(Long productId) {
    return cacheService.getProductFromCache(productId); // 从缓存获取
}

3. 返回简化数据

java
@SentinelResource(value = "getProductDetail", fallback = "getProductDetailFallback")
public ProductDetail getProductDetail(Long productId) {
    return productClient.getProductDetail(productId);
}

public ProductDetail getProductDetailFallback(Long productId) {
    ProductDetail detail = new ProductDetail();
    detail.setId(productId);
    detail.setName("商品信息加载中");
    detail.setPrice(BigDecimal.ZERO);
    return detail; // 返回简化版商品信息
}

4. 关闭非核心功能

java
@GetMapping("/index")
public IndexPage getIndexPage() {
    IndexPage page = new IndexPage();
    
    // 核心功能:必须获取
    page.setProducts(productService.getProducts());
    
    // 非核心功能:失败时可以关闭
    try {
        page.setRecommendations(recommendationService.getRecommendations());
    } catch (Exception e) {
        log.warn("推荐服务不可用,跳过推荐模块");
        page.setRecommendations(Collections.emptyList());
    }
    
    return page;
}

4.6 熔断降级最佳实践

阈值设置

yaml
# 熔断阈值设置建议
resilience4j:
  circuitbreaker:
    configs:
      default:
        # 错误率阈值: 50%
        failure-rate-threshold: 50
        
        # 慢调用比例阈值: 100%
        slow-call-rate-threshold: 100
        
        # 慢调用时间阈值: 2秒
        slow-call-duration-threshold: 2s
        
        # 滑动窗口大小: 100次调用
        sliding-window-size: 100
        
        # 最小调用次数: 10次
        minimum-number-of-calls: 10
        
        # 熔断持续时间: 10秒
        wait-duration-in-open-state: 10s
        
        # 半开状态允许的调用次数: 10次
        permitted-number-of-calls-in-half-open-state: 10

核心链路 vs 非核心链路

java
// 核心链路:严格熔断,必须有降级
@CircuitBreaker(name = "orderService", fallbackMethod = "createOrderFallback")
public Order createOrder(OrderRequest request) {
    // 核心业务逻辑
}

// 非核心链路:可以熔断,降级可以为空
@CircuitBreaker(name = "recommendationService")
public List<Product> getRecommendations(Long userId) {
    // 非核心业务逻辑
}

降级策略选择

code
选择降级策略的决策树:

问题: 服务不可用时如何降级?
    ↓
是否有缓存数据?
    ├─ 是 → 返回缓存数据
    └─ 否 ↓
是否可以返回默认值?
    ├─ 是 → 返回默认值
    └─ 否 ↓
是否可以简化数据?
    ├─ 是 → 返回简化数据
    └─ 否 ↓
是否可以关闭功能?
    ├─ 是 → 关闭功能
    └─ 否 ↓
返回错误,触发告警

五、重试与超时

5.1 为什么重试、限流、熔断必须一起设计

如果下游已经过载,而上游还在:

  • 继续高并发重试
  • 不做熔断
  • 入口没有限流

那故障只会被放大。

三者联动设计:

code
┌─────────────────────────────────────────────────────┐
│                    客户端请求                        │
└─────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│  限流 (第一道防线)                                   │
│  - 入口削峰                                          │
│  - 保护系统不超载                                    │
└─────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│  熔断 (第二道防线)                                   │
│  - 依赖隔离                                          │
│  - 快速失败                                          │
└─────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│  重试 (第三道防线)                                   │
│  - 只对可恢复错误生效                                │
│  - 有边界限制                                        │
└─────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│                  目标服务                            │
└─────────────────────────────────────────────────────┘

5.2 超时设置

超时时间设置原则

code
超时时间设置公式:

超时时间 = 平均响应时间 × 系数(2-3) + 网络延迟

示例:
- 平均响应时间: 100ms
- 系数: 2
- 网络延迟: 50ms
- 超时时间 = 100 × 2 + 50 = 250ms

超时配置

yaml
# application.yml
spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 3000 # 连接超时: 3秒
        response-timeout: 5000 # 响应超时: 5秒
    
feign:
  client:
    config:
      default:
        connectTimeout: 3000
        readTimeout: 5000

5.3 重试策略

重试条件

可重试的错误:

  • 网络超时
  • 服务暂时不可用 (503)
  • 连接失败

不可重试的错误:

  • 业务异常 (400、401、403、404)
  • 服务明确拒绝 (500)
  • 幂等性问题

重试配置

java
// Spring Cloud Gateway 重试配置
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("user-service", r -> r.path("/api/users/**")
            .filters(f -> f
                .retry(config -> config
                    .setRetries(3) // 重试3次
                    .setStatuses(HttpStatus.INTERNAL_SERVER_ERROR) // 对500错误重试
                    .setMethods(HttpMethod.GET) // 只对GET请求重试
                    .setBackoff(Duration.ofMillis(100), Duration.ofMillis(1000), 2, true)
                )
            )
            .uri("lb://user-service"))
        .build();
}

// Feign 重试配置
@Configuration
public class FeignConfig {
    
    @Bean
    public Retryer feignRetryer() {
        // 最大重试次数: 3
        // 初始间隔: 100ms
        // 最大间隔: 1s
        return new Retryer.Default(100, TimeUnit.SECONDS.toMillis(1), 3);
    }
}

重试边界

java
// 重试必须有边界限制
@Service
public class OrderService {
    
    @Autowired
    private UserClient userClient;
    
    // √ 正确: 有重试次数限制
    @Retryable(
        value = {ConnectException.class, TimeoutException.class},
        maxAttempts = 3,
        backoff = @Backoff(delay = 100, multiplier = 2)
    )
    public User getUser(Long userId) {
        return userClient.getUser(userId);
    }
    
    // × 错误: 无限重试
    public User getUserWrong(Long userId) {
        while (true) {
            try {
                return userClient.getUser(userId);
            } catch (Exception e) {
                // 无限重试,可能导致死循环
            }
        }
    }
}

六、实战场景

6.1 场景一:秒杀流量入口保护

问题描述

大促或秒杀场景里,如果没有网关限流:

  • 请求会直接冲垮下游服务
  • 数据库和 Redis 压力会被瞬间打满
  • 故障会从边缘一路扩散到核心链路

解决方案

yaml
# 多层限流配置
spring:
  cloud:
    gateway:
      routes:
        - id: seckill-service
          uri: lb://seckill-service
          predicates:
            - Path=/api/seckill/**
          filters:
            # 第一层:全局限流
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 1000
                redis-rate-limiter.burstCapacity: 5000
                key-resolver: "#{@ipKeyResolver}"
            
            # 第二层:用户级限流
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
                key-resolver: "#{@userKeyResolver}"
            
            # 第三层:熔断保护
            - name: CircuitBreaker
              args:
                name: seckillCircuitBreaker
                fallbackUri: forward:/fallback/seckill

更稳妥的做法:

  • 网关先做入口削峰
  • 核心接口按用户、活动维度限流
  • 非核心接口主动降级
  • 核心链路只保留最必要操作

6.2 场景二:下游会员服务抖动

问题描述

如果订单服务依赖会员服务获取用户权益,而会员服务开始大面积超时:

  • 继续同步请求只会拖慢订单主链路
  • 线程池和连接池会被持续占满

解决方案

java
@Service
public class OrderService {
    
    @Autowired
    private MemberClient memberClient;
    
    // 熔断+降级
    @CircuitBreaker(name = "memberService", fallbackMethod = "getMemberBenefitFallback")
    public MemberBenefit getMemberBenefit(Long userId) {
        return memberClient.getBenefit(userId);
    }
    
    // 降级策略:返回默认权益
    public MemberBenefit getMemberBenefitFallback(Long userId, Exception ex) {
        log.warn("会员服务不可用,使用默认权益: userId={}", userId);
        
        MemberBenefit benefit = new MemberBenefit();
        benefit.setUserId(userId);
        benefit.setLevel(1); // 默认等级
        benefit.setDiscount(BigDecimal.ONE); // 无折扣
        benefit.setPoints(0); // 无积分
        
        return benefit;
    }
}

这时更合理的处理是:

  • 触发熔断快速失败
  • 返回兜底权益策略或默认值
  • 持续告警并观察依赖恢复情况

6.3 场景三:恶意流量和热点接口

问题描述

开放接口经常会遇到:

  • 爬虫
  • 刷接口
  • 恶意流量
  • 非法重放请求

这时限流不仅是稳定性手段,也是安全边界的一部分。

解决方案

java
// 多维度限流
@Configuration
public class RateLimitConfig {
    
    // IP维度限流
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
        );
    }
    
    // 用户维度限流
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getHeaders().getFirst("X-User-Id")
        );
    }
    
    // 接口维度限流
    @Bean
    public KeyResolver apiKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getPath().value()
        );
    }
    
    // 组合维度限流
    @Bean
    public KeyResolver compositeKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getRemoteAddress().getAddress().getHostAddress() + ":" +
            exchange.getRequest().getPath().value()
        );
    }
}

// IP 黑名单
@Component
public class IpBlacklistFilter implements GlobalFilter, Ordered {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
        
        // 检查黑名单
        Boolean isBlacklisted = redisTemplate.opsForSet().isMember("ip:blacklist", ip);
        if (isBlacklisted != null && isBlacklisted) {
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
            return exchange.getResponse().setComplete();
        }
        
        return chain.filter(exchange);
    }
    
    @Override
    public int getOrder() {
        return -300;
    }
}

七、常见问题与排查

7.1 网关问题排查

网关问题排查先看什么

如果大量请求在入口就失败,优先检查:

1. 路由规则是否正确

bash
# 检查路由配置
curl http://gateway:8080/actuator/gateway/routes

2. 灰度规则是否误伤正常流量

java
// 检查灰度规则
@Component
public class GrayRouteFilter implements GlobalFilter {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String grayVersion = exchange.getRequest().getHeaders().getFirst("X-Gray-Version");
        
        log.info("灰度规则: path={}, grayVersion={}", 
            exchange.getRequest().getPath(), grayVersion);
        
        return chain.filter(exchange);
    }
}

3. 鉴权逻辑是否异常放大

java
// 检查鉴权耗时
@Component
public class AuthFilter implements GlobalFilter {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        long startTime = System.currentTimeMillis();
        
        // 鉴权逻辑
        boolean authenticated = authenticate(exchange);
        
        long duration = System.currentTimeMillis() - startTime;
        if (duration > 100) {
            log.warn("鉴权耗时过长: {}ms, path={}", 
                duration, exchange.getRequest().getPath());
        }
        
        if (!authenticated) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        
        return chain.filter(exchange);
    }
}

4. 网关实例自身资源状态

bash
# 检查网关健康状态
curl http://gateway:8080/actuator/health

# 检查网关指标
curl http://gateway:8080/actuator/metrics

5. 限流规则是否配置错误

bash
# 检查限流配置
curl http://gateway:8080/actuator/gateway/routes

7.2 限流问题排查

限流问题排查先看什么

如果业务反馈"接口突然被拦",优先检查:

1. 命中的限流维度是什么

java
// 查看限流日志
@Component
public class RateLimitFilter implements GlobalFilter {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
        String path = exchange.getRequest().getPath().value();
        
        log.info("限流检查: ip={}, path={}", ip, path);
        
        return chain.filter(exchange);
    }
}

2. 当前阈值是否适合实际峰值流量

java
// 动态调整限流阈值
@Configuration
@RefreshScope
public class RateLimitConfig {
    
    @Value("${rate.limit.threshold:100}")
    private int threshold;
    
    @Bean
    @RefreshScope
    public RateLimiter rateLimiter() {
        return RateLimiter.create(threshold);
    }
}

3. 是否区分了核心和非核心流量

yaml
# 区分限流配置
rate:
  limit:
    core:
      threshold: 1000
    non-core:
      threshold: 100

4. 是否支持灰度调整和快速回滚

yaml
# 支持动态配置
spring:
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
        file-extension: yaml
        shared-configs:
          - data-id: rate-limit.yaml
            refresh: true

7.3 熔断问题排查

熔断治理要注意什么

1. 熔断阈值不能拍脑袋设置

java
// 根据监控数据设置阈值
// 建议先观察一段时间的历史数据
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
    .failureRateThreshold(50) // 根据实际错误率调整
    .slowCallRateThreshold(100)
    .slowCallDurationThreshold(Duration.ofSeconds(2))
    .waitDurationInOpenState(Duration.ofSeconds(10))
    .slidingWindowSize(100)
    .minimumNumberOfCalls(10)
    .build();

2. 熔断打开后必须有恢复探测

java
// 配置半开状态
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
    .permittedNumberOfCallsInHalfOpenState(10) // 半开状态允许10次探测
    .build();

3. 熔断和重试不能互相打架

java
// 熔断和重试配合使用
@Service
public class OrderService {
    
    @CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
    @Retry(name = "userService", fallbackMethod = "getUserFallback")
    public User getUser(Long userId) {
        return userClient.getUser(userId);
    }
    
    public User getUserFallback(Long userId, Exception e) {
        return User.defaultUser(userId);
    }
}

4. 核心链路必须有清晰的兜底策略

java
// 核心链路降级策略
@Service
public class OrderService {
    
    @CircuitBreaker(name = "paymentService", fallbackMethod = "processPaymentFallback")
    public PaymentResult processPayment(PaymentRequest request) {
        return paymentClient.process(request);
    }
    
    // 核心链路降级:记录日志,人工处理
    public PaymentResult processPaymentFallback(PaymentRequest request, Exception e) {
        log.error("支付服务不可用,需要人工处理: orderId={}", request.getOrderId());
        
        // 记录到数据库,后续人工处理
        pendingPaymentRepository.save(new PendingPayment(request));
        
        // 返回处理中状态
        PaymentResult result = new PaymentResult();
        result.setStatus("PENDING");
        result.setMessage("支付处理中,请稍后查询");
        return result;
    }
}

八、设计与落地注意事项

8.1 网关不要承载过重业务逻辑

网关适合做横切治理,但不适合承载复杂业务编排。否则容易把网关变成新的"超级单体入口"。

不适合在网关做的事:

  • 复杂的业务逻辑编排
  • 大量数据聚合
  • 频繁的数据库操作
  • 复杂的计算逻辑

适合在网关做的事:

  • 路由转发
  • 认证授权
  • 限流熔断
  • 灰度发布
  • 日志监控

8.2 核心流量和边缘流量要区分治理

并不是所有流量都应该使用同一套限流和降级策略。通常要明确:

流量分类:

  • 核心交易流量 - 高优先级,严格保护
  • 非核心查询流量 - 中优先级,可降级
  • 边缘流量 - 低优先级,可丢弃

差异化治理:

yaml
# 差异化限流配置
rate:
  limit:
    core:
      threshold: 1000
      strategy: strict
    non-core:
      threshold: 500
      strategy: moderate
    edge:
      threshold: 100
      strategy: loose

8.3 降级一定要有业务语义

降级不是简单返回报错,而是要回答:

当前功能退到什么程度,业务还能继续运行?

降级策略选择:

code
业务功能 → 是否可降级?
    ├─ 是 → 是否有缓存?
    │   ├─ 是 → 返回缓存
    │   └─ 否 → 是否有默认值?
    │       ├─ 是 → 返回默认值
    │       └─ 否 → 是否可简化?
    │           ├─ 是 → 返回简化数据
    │           └─ 否 → 是否可关闭?
    │               ├─ 是 → 关闭功能
    │               └─ 否 → 返回错误
    └─ 否 → 触发告警,人工介入

九、常见误区

9.1 误区一:把网关只当转发层

× 错误理解:

网关只是把请求转发到后端服务,没有其他作用。

正确理解:

网关的真正价值是把鉴权、路由、限流、灰度和链路标识注入等横切能力统一治理。

网关核心价值:

code
网关 ≠ 简单转发

网关 = 统一入口 + 横切能力集中治理

包括:
- 路由转发
- 认证授权
- 限流熔断
- 灰度发布
- 日志监控
- 协议转换
- 安全防护

9.2 误区二:所有限流都做成全局阈值

× 错误理解:

所有限流都使用同一个全局阈值,简单粗暴。

正确理解:

全局一刀切很容易误伤正常业务,限流应尽量按用户、接口、租户、服务等级等维度分层设计。

分层限流设计:

code
限流维度:
├─ 全局限流 - 保护整体系统
├─ 服务级限流 - 保护单个服务
├─ 接口级限流 - 保护核心接口
├─ 用户级限流 - 防止单个用户滥用
├─ IP级限流 - 防止恶意攻击
└─ 租户级限流 - B端系统租户隔离

9.3 误区三:熔断打开后没有恢复探测

× 错误理解:

熔断打开后就一直打开,没有恢复机制。

正确理解:

没有半开探测和恢复机制,就可能出现长时间误熔断。

正确的熔断恢复流程:

code
1. 熔断打开
    ↓
2. 等待超时 (如10秒)
    ↓
3. 进入半开状态
    ↓
4. 少量探测请求 (如10次)
    ↓
5. 判断探测结果:
    ├─ 成功率高 → 关闭熔断
    └─ 成功率低 → 重新打开熔断

9.4 误区四:下游已经过载,上游还在叠加重试

× 错误理解:

下游失败就不断重试,直到成功。

正确理解:

无边界重试会把局部故障快速放大成全链路故障。

重试边界设计:

code
重试规则:
- 重试次数: 最多3次
- 重试条件: 只对可恢复错误重试
- 重试间隔: 指数退避 (100ms, 200ms, 400ms)
- 重试限制: 必须有熔断保护

9.5 误区五:没有区分核心链路和边缘链路的降级策略

× 错误理解:

所有服务的降级策略都一样,没有区分核心和非核心。

正确理解:

核心业务和非核心功能在故障时的让步策略应该完全不同。

差异化降级策略:

code
核心链路 (如订单、支付):
- 严格熔断保护
- 必须有兜底方案
- 降级后仍需保证核心功能可用
- 触发告警,人工介入

非核心链路 (如推荐、评论):
- 可以完全关闭
- 降级后返回空数据或默认值
- 不影响核心业务流程

十、面试要点

10.1 网关相关问题

Q1: 网关为什么是微服务治理的重要入口?

答案:

因为很多横切能力最适合在统一入口收口,例如:

  • 鉴权 - 统一身份认证
  • 路由 - 统一流量入口
  • 限流 - 统一流量控制
  • 灰度 - 统一流量治理
  • 日志 - 统一日志采集

展开:

优势:

  • 避免横切逻辑在各个服务重复实现
  • 统一接口治理规则
  • 强化安全边界
  • 简化客户端调用

挑战:

  • 网关成为单点故障
  • 性能瓶颈
  • 需要高可用部署

10.2 限流相关问题

Q2: 限流的核心目的是什么?

答案:

保护系统,而不是追求"所有请求都必须放过"。

展开:

限流本质:

  • 容量保护 - 防止系统过载
  • 故障隔离 - 防止故障扩散
  • 资源公平分配 - 保证正常用户访问

限流策略:

  • 明确拒绝部分请求
  • 保护整体系统稳定运行
  • 避免雪崩效应

Q3: 令牌桶和漏桶算法的区别是什么?

答案:

令牌桶:

  • 按固定速率放令牌
  • 允许一定突发流量
  • 控制平均速率

漏桶:

  • 请求先进入桶
  • 按固定速率流出
  • 流量更平稳

选择建议:

  • 令牌桶: 网关、API限流
  • 漏桶: 数据库、第三方调用

10.3 熔断降级相关问题

Q4: 熔断和降级的区别是什么?

答案:

熔断:

  • 更偏依赖隔离
  • 保护调用链
  • 快速失败
  • 自动恢复

降级:

  • 更偏业务退让
  • 功能主动让步
  • 返回兜底数据
  • 业务语义明确

关系:

  • 熔断触发后可以配合降级策略
  • 降级可以独立于熔断使用

Q5: 为什么限流、重试、熔断必须联动设计?

答案:

因为它们共同决定了故障会被隔离,还是会被放大。

联动设计:

code
限流 (第一道防线):
- 入口削峰
- 保护系统不超载

熔断 (第二道防线):
- 依赖隔离
- 快速失败

重试 (第三道防线):
- 只对可恢复错误生效
- 有边界限制

三者配合:
- 限流防止系统过载
- 熔断防止依赖拖垮整体
- 重试只在合适场景使用

10.4 实战场景问题

Q6: 如何设计秒杀场景的限流方案?

答案:

多层限流策略:

code
第一层: 网关全局限流
- 防止流量直接冲垮系统
- 阈值: 系统容量的1.5倍

第二层: 用户级限流
- 防止单个用户刷单
- 阈值: 每用户1次/秒

第三层: 商品级限流
- 保护库存扣减接口
- 阈值: 库存数量

第四层: 熔断保护
- 下游服务异常时快速失败
- 返回友好提示

十一、小结

理解网关与限流熔断,至少要掌握这些点:

  • 网关的核心价值 - 统一收口横切治理能力
  • 限流的本质 - 容量保护和有序退化
  • 熔断的核心 - 隔离不稳定依赖
  • 降级的核心 - 业务功能主动让步
  • 稳定性治理 - 从来不是单个组件的问题,而是入口、调用链和业务兜底一起设计的结果

核心技术栈:

  • 网关: Spring Cloud Gateway、Kong、APISIX
  • 限流: Redis + Lua、Guava RateLimiter、Sentinel
  • 熔断: Resilience4j、Sentinel
  • 降级: 业务兜底策略

核心能力:

  • 网关路由与治理
  • 多维度限流
  • 熔断状态管理
  • 降级策略设计
  • 高可用部署

持续演进:

  • 服务网格 (Istio)
  • 云原生网关
  • 智能限流
  • 自适应熔断

版本差异(Spring Cloud 旧版 → 2025.x)

特性旧版(本文编写时)当前(Spring Cloud 2025.x / Boot 3.5.x)
架构组件Eureka/Ribbon/Hystrix/Zuul(已停止维护)LoadBalancer/Gateway/Resilience4j/Nacos
版本对应Hoxton/2020.x + Boot 2.x + JDK 82025.x + Boot 3.5.x + JDK 17+
云原生初步支持全面云原生(K8s、虚拟线程、GraalVM 支持)
链路追踪Sleuth + ZipkinMicrometer Tracing + Zipkin/OTel

本文为通用微服务概念讲解,架构思想长期有效;落地时请采用 Spring Cloud 2025.x + Spring Boot 3.5.x 技术栈(JDK 17+)。