Spring Cloud Gateway 限流:别把所有请求都当成一个用户

Spring Cloud Gateway 限流:别把所有请求都当成一个用户

网关限流看起来简单:给接口设置 QPS,超过就拒绝。但生产环境里,限流维度如果设计不好,很容易误伤。所有用户共用一个限流桶,大客户被小客户影响;只按 IP 限流,NAT 后一群用户被当成一个人;只按用户限流,又挡不住某个接口被打爆。

Spring Cloud Gateway 限流要按业务维度设计,而不是只配一个全局阈值。

一、限流维度要分层

flowchart TD
  A[Request] --> B[Global Limit]
  B --> C[Tenant Limit]
  C --> D[User Limit]
  D --> E[Route Limit]
  E --> F[Backend Service]

全局限流保护平台,租户限流保证公平,用户限流防滥用,路由限流保护具体后端。不同层的目标不一样。

分层限流还要注意层级间的相互作用。如果全局限流设置得太低,会抵消掉租户级的配额;如果租户限流太松,单个大客户可能占满全局配额。一种做法是让请求依次通过各层限流,任何一层拒绝就返回;另一种做法是只在最细粒度限流,上层只做监控和告警。前者保护更严格,后者更灵活。选择哪种取决于你的业务模式和服务承诺。

二、KeyResolver 要贴近业务身份

Gateway 的限流通常需要 KeyResolver。不要默认只用 IP,最好根据租户、用户、接口组合生成 key。

@Bean
public KeyResolver tenantUserKeyResolver() {
    return exchange -> {
        String tenant = exchange.getRequest().getHeaders().getFirst("X-Tenant-Id");
        String user = exchange.getRequest().getHeaders().getFirst("X-User-Id");
        String path = exchange.getRequest().getPath().value();
        return Mono.just(tenant + ":" + user + ":" + path);
    };
}

真实系统里,租户和用户身份应该来自认证结果,不要信任外部随便传的 header。

如果请求来自微服务内部调用,可能没有用户身份。这时可以考虑用服务名、实例 ID 或调用链 traceId 作为限流 key。内部调用通常信任度更高,限流可以更宽松,但也要防止某个服务 bug 导致疯狂调用下游。内部限流的目标不是限制正常流量,而是快速发现异常。

三、不同接口要有不同策略

登录、查询、导出、AI 生成、支付回调,限流策略不可能一样。高成本接口要更严格,可缓存接口可以更宽松,核心链路要配合降级。

rate_limit_policy:
  /api/search:
    replenishRate: 50
    burstCapacity: 100
  /api/ai/generate:
    replenishRate: 5
    burstCapacity: 10
  /api/export:
    replenishRate: 1
    burstCapacity: 2

限流配置要能按环境和租户调整。企业客户、免费用户、内部系统调用,通常不该混用一套阈值。

四、限流响应要可观测

限流不是简单返回 429。要记录 route、key、租户、剩余额度和拒绝原因,方便排查误伤。

{
  "event": "rate_limited",
  "route": "/api/ai/generate",
  "tenant": "t_01",
  "key_type": "tenant_user_route",
  "limit": 5,
  "retry_after_ms": 12000
}

客户端也需要明确的 Retry-After,否则用户只会看到失败,不知道什么时候重试。

限流时的用户体验也很重要。对于 Web 页面,可以展示"当前访问人数较多,预计等待 X 秒";对于 API 调用,可以返回明确的错误码和建议重试时间;对于移动端,可以在客户端实现指数退避重试,避免用户手动反复刷新。好的限流系统不只是拒绝请求,而是在保护系统的同时尽量减少对用户的影响。

在多网关实例的部署场景中,基于内存的限流方案(如 Resilience4j RateLimiter)存在一个根本问题:每个实例独立计数,总限流额度 = 单实例额度 × 实例数。假设配置全局 QPS 为 1000,4 个实例意味着实际 QPS 上限是 4000,失去了限流的意义。解决方案是使用 Redis 作为分布式计数器,通过 Lua 脚本保证令牌获取的原子性。但 Redis 方案也有代价——每个请求增加一次 Redis 调用(约 1-2ms 延迟),在高 QPS 场景下需要对 Redis 做分片或使用本地预取令牌优化。我们的折中方案是:本地内存维护一个小的令牌缓冲(Redis 每次下发 100 个令牌到本地),实例在本地消耗令牌,耗尽时异步向 Redis 申请下一批。这样 Redis 调用频率从"每请求一次"降为"每 100 个请求一次",延迟开销可以忽略不计,同时误差控制在 10% 以内。

五、总结

Spring Cloud Gateway 限流要分层设计:全局、租户、用户、路由分别保护不同目标。KeyResolver 要基于可信业务身份,不同接口配置不同阈值,限流事件必须可观测。

限流不是为了拒绝用户,而是为了让系统在压力下保持秩序。维度设计对了,才不会把所有请求都当成一个用户。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值