Java程序员自我修养之企业级限流学习
2026/5/13 10:10:58 网站建设 项目流程

❓什么是限流?

  • 控制单位时间请求数量

❓为什么需要限流?

  • 防止系统被打爆
  • 防止依赖服务(DB / Redis / LLM)被压垮

❓限流一般在哪几层?

  • 网关层(Nginx / Gateway)
  • 服务层(Controller / Service)
  • 依赖层(DB / LLM)

限流算法

1️⃣ 固定窗口(最简单)

特点:

  • 每秒最多 N 次
  • 实现简单

问题:

  • 边界突刺(瞬间翻倍):🔥在时间边界处,流量会“瞬间翻倍”

2️⃣ 滑动窗口(改进版)

👉不再按固定时间段切

特点:

  • 更平滑
  • 精度更高

问题:

  • 实现复杂

3️⃣ 令牌桶(最常用 ⭐)

特点:

  • 支持突发流量
  • 工程上最常用

4️⃣ 漏桶(平滑流量)

请求进入桶(队列) → 按固定速率处理(漏水)

特点:

  • 匀速输出
  • 不允许突发

为什么令牌桶更常用?

❗一句话结论

令牌桶 = 同时满足“抗突发 + 控速率 + 体验好”的最优平衡

1.网关层面的ip限流(gateway)

怎么使用IP限流

1.向 Spring 容器注册一个 KeyResolver 类型的 Bean

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

作用:获取用户的ip地址

2.配置文件中进行配置

spring: redis: host: localhost port: 6379 application: name: ratelimiter-example cloud: gateway: routes: - id: route1 uri: http://localhost:8080/hello predicates: - Path=/hello filters: - name: RequestRateLimiter args: # 每秒往令牌桶里补充 20 个令牌 # 可以理解为“长期平均每秒最多允许 20 次请求” redis-rate-limiter.replenishRate: 20 # 令牌桶最大容量是 40 # 可以理解为“最多允许积攒 40 个令牌”,用于应对突发流量 redis-rate-limiter.burstCapacity: 40 # 每个请求要消耗 1 个令牌 # 如果改成 2,就表示一次请求要拿走 2 个令牌 redis-rate-limiter.requestedTokens: 1

你可以把它想成这样:
1.请求先到网关
2.KeyResolver 先判断“这个请求属于谁”
3.限流器根据这个 key 去 Redis 里找对应的桶
4.有令牌就放行,没有令牌就拦截

IP限流的优点是什么

•实现简单,不依赖登录态。
•对匿名接口也能用。
•适合做第一层粗粒度防刷、防爬、防恶意流量。
•网关层很容易接入,不需要业务代码参与。

IP限流遇到的问题是什么

•IP 不稳定,用户切网络、重连后可能变化。
•多个人可能共用一个出口 IP,容易误伤。
•同一个用户可能有多个 IP,限制不够精确。

如果经过 Nginx / 网关代理,IP 会不会不准

会,而且这是按 IP 限流最常见的问题之一
因为 getRemoteAddress() 取到的是“直接连到你这个服务的那一跳”的地址。
如果请求路径是:用户 -> Nginx -> Spring Cloud Gateway
那你在 Gateway 里拿到的很可能是 Nginx 的 IP,而不是真实用户 IP。

如果一个系统的核心接口在高并发下出现下游资源(如数据库、缓存或外部服务)扛不住的情况,你会如何设计一套限流与资源保护方案?

第一层放在网关层,做入口限流。
我会按userId + 接口标识作为限流维度,对匿名流量按 IP 兜底。算法上优先用令牌桶,因为问答接口属于实时交互场景,需要允许一定程度的突发请求,同时控制长期平均速率。分布式实现上可以用Redis + Lua,保证令牌补充、扣减和放行判断的原子性。

第二层放在服务层,用Sentinel对 LLM 调用做资源保护。
因为即使网关做了限流,也不能完全依赖网关,真实场景里可能存在网关规则失效、内部调用绕过网关,或者短时间内通过网关的请求仍然过多,直接把 LLM 打爆。所以在真正调用 LLM 前,我会把这段逻辑接入 Sentinel,把 LLM 调用定义成受保护资源,对它配置QPS 阈值、线程数阈值,必要时再配合熔断降级。一旦达到阈值,就直接触发 block 逻辑,返回“系统繁忙,请稍后再试”,而不是继续把压力压给下游模型

Sentinel 的 QPS 限流和线程数限制有什么区别?

1️⃣ QPS 限流(速率控制)

每秒最多允许多少请求进入

特点:

  • 快速拦截请求
  • 不关心请求处理多久
  • 只看“进入速度”

2️⃣ 线程数限制(并发控制)

同时最多有多少请求正在执行

特点:

  • 关注“资源占用”
  • 强依赖请求耗时
  • 防止线程池 / 下游被打爆

为什么实际工程中“令牌桶”比滑动窗口更常用?

  • 滑动窗口虽然在限流精度上更高,可以避免固定窗口的边界突刺问题,但它的实现成本较高,比如基于 Redis 的 ZSET 需要在每次请求时删除过期数据、统计数量并插入新数据,在高并发场景下会带来较大的性能和内存开销。
  • 相比之下,令牌桶只需要维护当前令牌数量和时间信息,计算和存储成本都更低,同时还能允许一定程度的突发流量,更符合用户实际使用场景。因此在大多数工程实践中,令牌桶在性能、资源消耗和用户体验之间取得了更好的平衡,所以使用更广泛。

网关层为什么用 Redis + Lua,而不是本地限流

网关层之所以优先选择 Redis + Lua,而不是单纯本地限流,是因为网关通常是多实例部署,本地限流只能控制单机流量,无法在集群范围内统一控制总量。Redis 作为共享状态存储,可以让所有网关实例访问同一份限流数据,从而实现分布式一致限流。Lua 脚本则用于保证限流过程中的判断、扣减、过期设置等操作原子执行,避免高并发下状态不一致的问题。

Sentinel 的 block 和 fallback 分别是什么

block 是“被 Sentinel 规则拦住了”时的处理逻辑。
fallback 是“业务方法自己出错了”时的兜底逻辑。

@SentinelResource( value = "llmCall", blockHandler = "handleBlock", fallback = "handleFallback" ) public String callLLM(String prompt) { return llmService.call(prompt); }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询