网关与限流熔断
微服务一旦拆开,流量通常不会再直接进入某个具体服务,而是先经过统一入口。这个入口通常就是 API 网关。
与此同时,系统还要面对这些问题:
- 突发流量会不会把下游打垮
- 某个依赖抖动时会不会拖跨整条链路
- 恶意流量和异常请求怎么拦
- 某些功能出问题时能不能有序退让
所以在微服务体系里,网关、限流、熔断、降级并不是可有可无的"附加能力",而是稳定性治理的核心组成部分。
一、网关核心原理
1.1 为什么微服务需要统一入口
没有网关时的问题
如果没有网关,客户端可能需要直接面对大量后端服务:
客户端需要知道所有服务地址:
┌─────────┐
│ 客户端 │
└─────────┘
↓ 直接调用多个服务
├─→ 用户服务 (http://user-service:8081)
├─→ 订单服务 (http://order-service:8082)
├─→ 商品服务 (http://product-service:8083)
└─→ 支付服务 (http://payment-service:8084)问题:
- 地址管理混乱 - 客户端要感知多个服务地址
- 横切逻辑分散 - 鉴权、限流、灰度等逻辑散落在各个服务中
- 接口治理困难 - 接口治理规则难以统一
- 安全边界弱 - 外部流量直接接触内部服务
- 协议不统一 - 客户端需要适配不同服务的协议
网关的核心价值
网关的核心价值,不是"多一层转发",而是把横切能力集中治理。
核心价值:
- 统一入口 - 客户端只需要知道网关地址
- 横切能力集中 - 鉴权、限流、灰度等逻辑集中在网关
- 接口治理统一 - 统一的接口规范和治理规则
- 安全边界强化 - 外部流量只接触网关,内部服务隔离
- 协议适配 - 统一对外协议,内部可以使用不同协议
有网关的架构:
┌─────────────┐
│ 客户端 │
└─────────────┘
↓
┌─────────────┐
│ API 网关 │ ← 横切能力集中治理
│ - 路由转发 │
│ - 认证授权 │
│ - 限流熔断 │
│ - 灰度发布 │
│ - 日志监控 │
└─────────────┘
↓
┌─────────────────┼─────────────────┐
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 用户服务 │ │ 订单服务 │ │ 商品服务 │
└──────────┘ └──────────┘ └──────────┘1.2 网关通常承担哪些职责
核心职责
1. 路由转发 (Routing)
// 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)
// 网关鉴权过滤器
@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)
// 基于 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)
# 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-service5. 灰度发布 (Gray Release)
// 灰度路由配置
@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)
// 全局日志过滤器
@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)
// 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 网关架构模式
集中式网关
架构:
┌─────────────┐
│ 客户端 │
└─────────────┘
↓
┌─────────────┐
│ API 网关集群 │ ← 所有流量经过统一网关
└─────────────┘
↓
┌─────────────────┼─────────────────┐
↓ ↓ ↓
服务实例 服务实例 服务实例优点:
- 统一治理,配置集中
- 容易实现横切能力
- 便于监控和排障
缺点:
- 单点故障风险
- 性能瓶颈
- 团队耦合
分布式网关 (微网关)
架构:
┌─────────────┐
│ 客户端 │
└─────────────┘
↓
┌─────────────────┼─────────────────┐
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Sidecar │ │ Sidecar │ │ Sidecar │ ← 每个服务旁边都有网关代理
│ Gateway │ │ Gateway │ │ Gateway │
└──────────┘ └──────────┘ └──────────┘
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 用户服务 │ │ 订单服务 │ │ 商品服务 │
└──────────┘ └──────────┘ └──────────┘优点:
- 去中心化,无单点故障
- 性能好,就近转发
- 语言无关
缺点:
- 配置分散,治理复杂
- 运维成本高
- 需要服务网格支持
1.4 网关性能优化
响应式编程
Spring Cloud Gateway 基于 WebFlux,使用响应式编程:
// 响应式过滤器
@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();
}
});
}
}缓存策略
// 响应缓存
@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);
}
});
}
}连接池优化
# 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 Gateway | Java | Spring 生态、功能丰富、响应式 | Spring Cloud 微服务 | 低 |
| Kong | Lua/Nginx | 高性能、插件生态、云原生 | 大规模 API 管理 | 中 |
| APISIX | Lua/Nginx | 云原生、动态配置、高性能 | Kubernetes 环境 | 中 |
| Nginx | C | 极致性能、稳定可靠 | 基础反向代理、负载均衡 | 中 |
| Envoy | C++ | 服务网格、可观测性强 | Service Mesh | 高 |
| Zuul | Java | Netflix 开源、生态成熟 | 传统 Spring Cloud | 低 (已过时) |
2.2 Spring Cloud Gateway 详解
核心概念
1. Route (路由)
路由是网关的基本构建块,包含:
- ID: 路由唯一标识
- URI: 目标服务地址
- Predicates: 断言,匹配条件
- Filters: 过滤器,修改请求/响应
2. Predicate (断言)
// 内置断言工厂
@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 (过滤器)
// 内置过滤器
@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();
}完整配置示例
# 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. 全局过滤器
@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. 网关过滤器工厂
@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 架构
┌─────────────────────────────────────────────────────────┐
│ Kong Gateway │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Kong Core │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Router │ │ Plugin │ │ Admin │ │ │
│ │ │ Engine │ │ System │ │ API │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Data Store (PostgreSQL) │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘Kong 核心概念
1. Service (服务)
# 创建服务
curl -i -X POST http://localhost:8001/services \
-d "name=user-service" \
-d "url=http://user-service:8080"2. Route (路由)
# 创建路由
curl -i -X POST http://localhost:8001/services/user-service/routes \
-d "name=user-route" \
-d "paths[]=/api/users"3. Plugin (插件)
# 添加限流插件
curl -i -X POST http://localhost:8001/services/user-service/plugins \
-d "name=rate-limiting" \
-d "config.minute=100" \
-d "config.policy=local"Kong 插件生态
| 插件类别 | 插件名称 | 功能 |
|---|---|---|
| 安全 | JWT | JWT 认证 |
| OAuth2 | OAuth2 认证 | |
| API Key | API Key 认证 | |
| IP Restriction | IP 黑白名单 | |
| 流量控制 | Rate Limiting | 限流 |
| Request Termination | 请求终止 | |
| Bot Detection | 机器人检测 | |
| 转换 | Request Transformer | 请求转换 |
| Response Transformer | 响应转换 | |
| Correlation ID | 关联 ID | |
| 监控 | Prometheus | Prometheus 监控 |
| File Log | 文件日志 | |
| StatsD | StatsD 监控 |
2.4 APISIX 网关详解
APISIX 简介
Apache APISIX 是一个云原生、高性能、可扩展的 API 网关,核心特点:
- 云原生设计,支持 Kubernetes
- 动态配置,无需重启
- 高性能,基于 Nginx 和 OpenResty
- 丰富的插件生态
APISIX 架构
┌─────────────────────────────────────────────────────────┐
│ APISIX Gateway │
│ ┌────────────────────────────────────────────────────┐ │
│ │ APISIX Core │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Router │ │ Plugin │ │ Admin │ │ │
│ │ │ Matcher │ │ Runtime │ │ API │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ etcd (配置存储) │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘APISIX 配置示例
# 创建路由
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 微服务:
首选: Spring Cloud Gateway
理由:
- 与 Spring Cloud 生态无缝集成
- 响应式编程,性能好
- 功能丰富,易于扩展
- 学习曲线低Kubernetes 环境:
首选: APISIX 或 Kong
理由:
- 云原生设计
- 支持动态配置
- 与 Kubernetes 深度集成
- 高性能高性能场景:
首选: Kong 或 Nginx
理由:
- 基于 Nginx,极致性能
- 成熟稳定
- 生产验证充分服务网格:
首选: Envoy
理由:
- 服务网格标配
- 可观测性强
- 动态配置三、限流详解
3.1 限流的本质
为什么需要限流
限流的目标不是"把所有请求拦住",而是:
- 在系统承压过大时,明确拒绝一部分流量,保护整体系统还能继续工作
如果系统扛不住还强行全放行,结果通常不是"全部成功",而是:
- 线程池打满
- 连接池耗尽
- RT 飙升
- 错误率扩大
- 故障从边缘扩散到核心链路
所以限流本质上是一种容量保护和故障隔离手段。
限流的核心目标
1. 保护系统
正常情况:
请求量 < 系统容量 → 系统正常运行
限流场景:
请求量 > 系统容量 → 拒绝部分请求 → 系统仍能处理部分请求2. 防止雪崩
无限流:
请求暴增 → 系统过载 → 所有请求失败 → 雪崩
有限流:
请求暴增 → 限流保护 → 拒绝多余请求 → 部分请求成功 → 系统稳定3. 公平分配资源
无限流:
恶意流量占用所有资源 → 正常用户无法访问
有限流:
恶意流量被限制 → 正常用户可以正常访问3.2 限流算法详解
固定窗口算法 (Fixed Window)
原理:
在固定时间窗口内限制请求数量。
时间窗口: 1秒
限流阈值: 100次
|------- 窗口1 -------|------- 窗口2 -------|
0ms 1000ms 2000ms
请求数: 100 请求数: 50
√ 放行 √ 放行实现:
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;
}
}优点:
- 实现简单
- 内存占用小
缺点:
- 边界突刺问题
|------- 窗口1 -------|------- 窗口2 -------|
900ms 100ms
请求100次 + 请求100次
窗口边界处可能瞬间通过200次请求!滑动窗口算法 (Sliding Window)
原理:
动态滑动时间窗口,更精确地统计最近一段时间内的请求量。
当前时间: 1500ms
窗口大小: 1000ms
统计范围: 500ms - 1500ms
|--- 窗口1 ---|--- 窗口2 ---|--- 窗口3 ---|
0ms 1000ms 2000ms 3000ms
↑
当前统计窗口: 500ms - 1500ms实现:
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)
原理:
系统按固定速率往桶里放令牌,请求需要从桶里获取令牌才能通过。
令牌桶:
┌─────────────────────────┐
│ ┌───┐ ┌───┐ ┌───┐ │ ← 令牌(最大容量)
│ │ ● │ │ ● │ │ │ ... │
│ └───┘ └───┘ └───┘ │
└─────────────────────────┘
↑
按速率放令牌(如100个/秒)
请求到来时:
- 有令牌 → 取走令牌 → 放行
- 无令牌 → 拒绝实现:
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)
原理:
请求先进入桶,桶以固定速率漏水(处理请求)。
漏桶:
请求 → ┌──────────────┐ → 处理
│ ┌──────┐ │
│ │ ●●●● │ │ ← 请求排队
│ └──────┘ │
└──────────────┘
↓
固定速率流出
(如100个/秒)实现:
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. 用户维度
// 按用户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. 接口维度
// 按接口路径限流
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getPath().value()
);
}3. IP 维度
// 按IP限流
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}4. 租户维度
// 按租户限流
@Bean
public KeyResolver tenantKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getHeaders().getFirst("X-Tenant-Id")
);
}5. 组合维度
// 组合限流:用户+接口
@Bean
public KeyResolver compositeKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getHeaders().getFirst("X-User-Id") + ":" +
exchange.getRequest().getPath().value()
);
}3.4 分布式限流
为什么需要分布式限流
单机限流的问题:
集群环境:
服务实例1: 限流100/秒
服务实例2: 限流100/秒
服务实例3: 限流100/秒
总限流: 300/秒
问题:
- 负载不均时,某个实例可能先被打满
- 无法实现全局限流基于 Redis 的分布式限流
Redis + Lua 实现:
@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 限流配置:
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: 63793.5 限流配置最佳实践
核心接口 vs 非核心接口
# 核心交易接口 - 高限流阈值
核心接口:
- /api/orders/create: 1000/秒
- /api/payments/process: 500/秒
# 非核心查询接口 - 中限流阈值
非核心接口:
- /api/products/list: 500/秒
- /api/users/profile: 200/秒
# 边缘接口 - 低限流阈值
边缘接口:
- /api/comments/list: 100/秒
- /api/recommendations: 50/秒不同用户等级
# VIP用户 - 高限额
VIP用户:
- 查询: 200/秒
- 下单: 50/秒
# 普通用户 - 中等限额
普通用户:
- 查询: 100/秒
- 下单: 10/秒
# 匿名用户 - 低限额
匿名用户:
- 查询: 20/秒
- 下单: 禁止动态调整
@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 熔断的核心原理
为什么需要熔断
熔断面对的是这样一种场景:
- 下游依赖已经明显不稳定
- 继续请求只会更糟
例如:
- 错误率明显升高
- 超时率持续升高
- 响应时间远超正常范围
这时如果上游还继续大量请求,只会进一步放大故障。
熔断的核心思路:
- 当错误率或超时率达到阈值
- 熔断器打开,短期内快速失败
- 进入半开状态后少量探测恢复
- 恢复成功后关闭熔断
它本质上是在做: 故障隔离
熔断器状态机
失败率超过阈值
┌──────────────────────┐
│ │
▼ │
┌─────────┐ 探测成功 ┌─────────┐
│ 关闭 │────────────>│ 半开 │
│ (Closed)│ │ (Half-Open)│
└─────────┘ └─────────┘
▲ │
│ 探测失败 │
│ ┌──────────────────┘
│ │
│ ▼
┌─────────┐
│ 打开 │
│ (Open) │
└─────────┘
│
│ 超时后进入半开状态
└────────────┐
│
▼
┌─────────┐
│ 半开 │
└─────────┘
状态说明:
- Closed (关闭): 正常状态,请求正常通过
- Open (打开): 熔断状态,快速失败
- Half-Open (半开): 探测状态,少量请求通过探测下游是否恢复4.2 主流熔断框架对比
| 框架 | 状态 | 核心特点 | 集成难度 | 推荐度 |
|---|---|---|---|---|
| Resilience4j | 活跃 | 轻量级、模块化、响应式 | 低 | |
| Sentinel | 活跃 | 功能全面、控制台可视化 | 低 | |
| Hystrix | 维护模式 | Netflix 开源、生态成熟 | 中 | (不推荐) |
4.3 Resilience4j 详解
核心模块
| 模块 | 功能 |
|---|---|
| CircuitBreaker | 熔断器 |
| RateLimiter | 限流器 |
| Retry | 重试 |
| Bulkhead | 舱壁隔离 |
| TimeLimiter | 超时控制 |
| Cache | 缓存 |
熔断器配置
# 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熔断器使用
@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();
}
}熔断器监控
// 获取熔断器指标
@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)
// 资源定义
@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)
// 限流规则
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 控制台
# application.yml
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
port: 8719
eager: true # 立即初始化控制台功能:
- 实时监控
- 规则配置
- 集群流控
- 机器列表
4.5 降级策略设计
降级的本质
降级更偏向业务层面的主动让步。例如:
- 推荐服务异常时,首页暂时不显示个性化推荐
- 会员权益服务异常时,先返回默认权益
- 评论服务超时时,先只展示商品基础信息
可以简单理解为:
- 熔断更偏"保护调用链"
- 降级更偏"业务功能主动退让"
降级策略类型
1. 返回默认值
@SentinelResource(value = "getUser", fallback = "getUserFallback")
public User getUser(Long userId) {
return userClient.getUser(userId);
}
public User getUserFallback(Long userId) {
return User.defaultUser(userId); // 返回默认用户
}2. 返回缓存值
@SentinelResource(value = "getProduct", fallback = "getProductFallback")
public Product getProduct(Long productId) {
return productClient.getProduct(productId);
}
public Product getProductFallback(Long productId) {
return cacheService.getProductFromCache(productId); // 从缓存获取
}3. 返回简化数据
@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. 关闭非核心功能
@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 熔断降级最佳实践
阈值设置
# 熔断阈值设置建议
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 非核心链路
// 核心链路:严格熔断,必须有降级
@CircuitBreaker(name = "orderService", fallbackMethod = "createOrderFallback")
public Order createOrder(OrderRequest request) {
// 核心业务逻辑
}
// 非核心链路:可以熔断,降级可以为空
@CircuitBreaker(name = "recommendationService")
public List<Product> getRecommendations(Long userId) {
// 非核心业务逻辑
}降级策略选择
选择降级策略的决策树:
问题: 服务不可用时如何降级?
↓
是否有缓存数据?
├─ 是 → 返回缓存数据
└─ 否 ↓
是否可以返回默认值?
├─ 是 → 返回默认值
└─ 否 ↓
是否可以简化数据?
├─ 是 → 返回简化数据
└─ 否 ↓
是否可以关闭功能?
├─ 是 → 关闭功能
└─ 否 ↓
返回错误,触发告警五、重试与超时
5.1 为什么重试、限流、熔断必须一起设计
如果下游已经过载,而上游还在:
- 继续高并发重试
- 不做熔断
- 入口没有限流
那故障只会被放大。
三者联动设计:
┌─────────────────────────────────────────────────────┐
│ 客户端请求 │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 限流 (第一道防线) │
│ - 入口削峰 │
│ - 保护系统不超载 │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 熔断 (第二道防线) │
│ - 依赖隔离 │
│ - 快速失败 │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 重试 (第三道防线) │
│ - 只对可恢复错误生效 │
│ - 有边界限制 │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 目标服务 │
└─────────────────────────────────────────────────────┘5.2 超时设置
超时时间设置原则
超时时间设置公式:
超时时间 = 平均响应时间 × 系数(2-3) + 网络延迟
示例:
- 平均响应时间: 100ms
- 系数: 2
- 网络延迟: 50ms
- 超时时间 = 100 × 2 + 50 = 250ms超时配置
# application.yml
spring:
cloud:
gateway:
httpclient:
connect-timeout: 3000 # 连接超时: 3秒
response-timeout: 5000 # 响应超时: 5秒
feign:
client:
config:
default:
connectTimeout: 3000
readTimeout: 50005.3 重试策略
重试条件
可重试的错误:
- 网络超时
- 服务暂时不可用 (503)
- 连接失败
不可重试的错误:
- 业务异常 (400、401、403、404)
- 服务明确拒绝 (500)
- 幂等性问题
重试配置
// 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);
}
}重试边界
// 重试必须有边界限制
@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 压力会被瞬间打满
- 故障会从边缘一路扩散到核心链路
解决方案
# 多层限流配置
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 场景二:下游会员服务抖动
问题描述
如果订单服务依赖会员服务获取用户权益,而会员服务开始大面积超时:
- 继续同步请求只会拖慢订单主链路
- 线程池和连接池会被持续占满
解决方案
@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 场景三:恶意流量和热点接口
问题描述
开放接口经常会遇到:
- 爬虫
- 刷接口
- 恶意流量
- 非法重放请求
这时限流不仅是稳定性手段,也是安全边界的一部分。
解决方案
// 多维度限流
@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. 路由规则是否正确
# 检查路由配置
curl http://gateway:8080/actuator/gateway/routes2. 灰度规则是否误伤正常流量
// 检查灰度规则
@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. 鉴权逻辑是否异常放大
// 检查鉴权耗时
@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. 网关实例自身资源状态
# 检查网关健康状态
curl http://gateway:8080/actuator/health
# 检查网关指标
curl http://gateway:8080/actuator/metrics5. 限流规则是否配置错误
# 检查限流配置
curl http://gateway:8080/actuator/gateway/routes7.2 限流问题排查
限流问题排查先看什么
如果业务反馈"接口突然被拦",优先检查:
1. 命中的限流维度是什么
// 查看限流日志
@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. 当前阈值是否适合实际峰值流量
// 动态调整限流阈值
@Configuration
@RefreshScope
public class RateLimitConfig {
@Value("${rate.limit.threshold:100}")
private int threshold;
@Bean
@RefreshScope
public RateLimiter rateLimiter() {
return RateLimiter.create(threshold);
}
}3. 是否区分了核心和非核心流量
# 区分限流配置
rate:
limit:
core:
threshold: 1000
non-core:
threshold: 1004. 是否支持灰度调整和快速回滚
# 支持动态配置
spring:
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yaml
shared-configs:
- data-id: rate-limit.yaml
refresh: true7.3 熔断问题排查
熔断治理要注意什么
1. 熔断阈值不能拍脑袋设置
// 根据监控数据设置阈值
// 建议先观察一段时间的历史数据
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 根据实际错误率调整
.slowCallRateThreshold(100)
.slowCallDurationThreshold(Duration.ofSeconds(2))
.waitDurationInOpenState(Duration.ofSeconds(10))
.slidingWindowSize(100)
.minimumNumberOfCalls(10)
.build();2. 熔断打开后必须有恢复探测
// 配置半开状态
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.permittedNumberOfCallsInHalfOpenState(10) // 半开状态允许10次探测
.build();3. 熔断和重试不能互相打架
// 熔断和重试配合使用
@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. 核心链路必须有清晰的兜底策略
// 核心链路降级策略
@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 核心流量和边缘流量要区分治理
并不是所有流量都应该使用同一套限流和降级策略。通常要明确:
流量分类:
- 核心交易流量 - 高优先级,严格保护
- 非核心查询流量 - 中优先级,可降级
- 边缘流量 - 低优先级,可丢弃
差异化治理:
# 差异化限流配置
rate:
limit:
core:
threshold: 1000
strategy: strict
non-core:
threshold: 500
strategy: moderate
edge:
threshold: 100
strategy: loose8.3 降级一定要有业务语义
降级不是简单返回报错,而是要回答:
当前功能退到什么程度,业务还能继续运行?
降级策略选择:
业务功能 → 是否可降级?
├─ 是 → 是否有缓存?
│ ├─ 是 → 返回缓存
│ └─ 否 → 是否有默认值?
│ ├─ 是 → 返回默认值
│ └─ 否 → 是否可简化?
│ ├─ 是 → 返回简化数据
│ └─ 否 → 是否可关闭?
│ ├─ 是 → 关闭功能
│ └─ 否 → 返回错误
└─ 否 → 触发告警,人工介入九、常见误区
9.1 误区一:把网关只当转发层
× 错误理解:
网关只是把请求转发到后端服务,没有其他作用。
√ 正确理解:
网关的真正价值是把鉴权、路由、限流、灰度和链路标识注入等横切能力统一治理。
网关核心价值:
网关 ≠ 简单转发
网关 = 统一入口 + 横切能力集中治理
包括:
- 路由转发
- 认证授权
- 限流熔断
- 灰度发布
- 日志监控
- 协议转换
- 安全防护9.2 误区二:所有限流都做成全局阈值
× 错误理解:
所有限流都使用同一个全局阈值,简单粗暴。
√ 正确理解:
全局一刀切很容易误伤正常业务,限流应尽量按用户、接口、租户、服务等级等维度分层设计。
分层限流设计:
限流维度:
├─ 全局限流 - 保护整体系统
├─ 服务级限流 - 保护单个服务
├─ 接口级限流 - 保护核心接口
├─ 用户级限流 - 防止单个用户滥用
├─ IP级限流 - 防止恶意攻击
└─ 租户级限流 - B端系统租户隔离9.3 误区三:熔断打开后没有恢复探测
× 错误理解:
熔断打开后就一直打开,没有恢复机制。
√ 正确理解:
没有半开探测和恢复机制,就可能出现长时间误熔断。
正确的熔断恢复流程:
1. 熔断打开
↓
2. 等待超时 (如10秒)
↓
3. 进入半开状态
↓
4. 少量探测请求 (如10次)
↓
5. 判断探测结果:
├─ 成功率高 → 关闭熔断
└─ 成功率低 → 重新打开熔断9.4 误区四:下游已经过载,上游还在叠加重试
× 错误理解:
下游失败就不断重试,直到成功。
√ 正确理解:
无边界重试会把局部故障快速放大成全链路故障。
重试边界设计:
重试规则:
- 重试次数: 最多3次
- 重试条件: 只对可恢复错误重试
- 重试间隔: 指数退避 (100ms, 200ms, 400ms)
- 重试限制: 必须有熔断保护9.5 误区五:没有区分核心链路和边缘链路的降级策略
× 错误理解:
所有服务的降级策略都一样,没有区分核心和非核心。
√ 正确理解:
核心业务和非核心功能在故障时的让步策略应该完全不同。
差异化降级策略:
核心链路 (如订单、支付):
- 严格熔断保护
- 必须有兜底方案
- 降级后仍需保证核心功能可用
- 触发告警,人工介入
非核心链路 (如推荐、评论):
- 可以完全关闭
- 降级后返回空数据或默认值
- 不影响核心业务流程十、面试要点
10.1 网关相关问题
Q1: 网关为什么是微服务治理的重要入口?
答案:
因为很多横切能力最适合在统一入口收口,例如:
- 鉴权 - 统一身份认证
- 路由 - 统一流量入口
- 限流 - 统一流量控制
- 灰度 - 统一流量治理
- 日志 - 统一日志采集
展开:
优势:
- 避免横切逻辑在各个服务重复实现
- 统一接口治理规则
- 强化安全边界
- 简化客户端调用
挑战:
- 网关成为单点故障
- 性能瓶颈
- 需要高可用部署
10.2 限流相关问题
Q2: 限流的核心目的是什么?
答案:
保护系统,而不是追求"所有请求都必须放过"。
展开:
限流本质:
- 容量保护 - 防止系统过载
- 故障隔离 - 防止故障扩散
- 资源公平分配 - 保证正常用户访问
限流策略:
- 明确拒绝部分请求
- 保护整体系统稳定运行
- 避免雪崩效应
Q3: 令牌桶和漏桶算法的区别是什么?
答案:
令牌桶:
- 按固定速率放令牌
- 允许一定突发流量
- 控制平均速率
漏桶:
- 请求先进入桶
- 按固定速率流出
- 流量更平稳
选择建议:
- 令牌桶: 网关、API限流
- 漏桶: 数据库、第三方调用
10.3 熔断降级相关问题
Q4: 熔断和降级的区别是什么?
答案:
熔断:
- 更偏依赖隔离
- 保护调用链
- 快速失败
- 自动恢复
降级:
- 更偏业务退让
- 功能主动让步
- 返回兜底数据
- 业务语义明确
关系:
- 熔断触发后可以配合降级策略
- 降级可以独立于熔断使用
Q5: 为什么限流、重试、熔断必须联动设计?
答案:
因为它们共同决定了故障会被隔离,还是会被放大。
联动设计:
限流 (第一道防线):
- 入口削峰
- 保护系统不超载
熔断 (第二道防线):
- 依赖隔离
- 快速失败
重试 (第三道防线):
- 只对可恢复错误生效
- 有边界限制
三者配合:
- 限流防止系统过载
- 熔断防止依赖拖垮整体
- 重试只在合适场景使用10.4 实战场景问题
Q6: 如何设计秒杀场景的限流方案?
答案:
多层限流策略:
第一层: 网关全局限流
- 防止流量直接冲垮系统
- 阈值: 系统容量的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 8 | 2025.x + Boot 3.5.x + JDK 17+ |
| 云原生 | 初步支持 | 全面云原生(K8s、虚拟线程、GraalVM 支持) |
| 链路追踪 | Sleuth + Zipkin | Micrometer Tracing + Zipkin/OTel |
本文为通用微服务概念讲解,架构思想长期有效;落地时请采用 Spring Cloud 2025.x + Spring Boot 3.5.x 技术栈(JDK 17+)。