2367 字
12 分钟
数据库流量过大:Redis + Lua 分布式限流方案

方案三:Redis + Lua 分布式限流#

1. 业务场景#

在报表查询业务中,多个用户可能同时发起查询请求。如果请求集中到达,业务服务会产生大量数据库查询,导致数据库 CPU 升高、连接池被占满,进而影响其他正常业务。

如果只在单个应用实例中使用本地计数器,各实例的限流状态相互独立,无法准确控制整个集群的请求总量。

因此,可以使用 Redis 统一维护限流状态,通过 Lua 脚本原子执行限流判断,使多个应用实例共享同一套限流额度。

2. 整体方案设计#

请求经过 Spring Cloud Gateway 时,网关先识别用户身份和目标接口,再调用 Redis 执行 Lua 脚本,判断当前请求是否允许通过。

整体链路如下:

客户端请求 → Spring Cloud Gateway → 获取用户 ID 和接口标识 → Redis 执行 Lua 限流脚本 → 根据结果放行或拒绝 → 业务服务 → 数据库。

其中:

  • Spring Cloud Gateway:负责识别请求、执行限流过滤器,以及放行或拒绝请求。
  • Redis:集中保存限流状态,供多个网关实例共享。
  • Lua 脚本:在 Redis 内完成额度检查和状态更新,避免并发请求之间出现计数竞争。
  • 业务服务:处理通过网关检查的请求,并访问数据库。

将限流放在网关入口,可以尽量避免超额请求进入业务服务,减少无效数据库查询。

3. 工程实施步骤#

第一步:梳理业务接口和限流目标#

首先分析数据库压力主要来自哪些接口,例如报表查询、数据导出和复杂统计接口。

结合接口的数据库开销、正常请求量和业务优先级,确定限流规则。

例如:

限流维度示例规则目的
用户级每个用户每秒最多请求 5 次防止单个用户持续占用资源
接口级报表查询接口每秒最多放行 500 次控制接口整体流量
并发级同时执行的重型报表查询不超过 30 个避免慢查询长期占用数据库连接

以上数值仅用于说明设计方式,实际阈值需要根据压测结果和数据库承载能力确定。

需要注意:请求速率限制与并发限制是两种不同的控制手段。每秒 500 次的请求限制,并不意味着同时执行的数据库查询只有 500 个或更少。

第二步:设计 Redis 限流 Key#

根据限流维度生成不同的 Key,例如:

rate:user:1001:report
rate:global:report

第一个 Key 记录用户 1001 对报表接口的限流状态,第二个 Key 记录报表接口的全局限流状态。

实际项目中,还需要考虑:

  • 为不同接口配置独立的限流规则。
  • 在 Key 中加入必要的业务标识,避免不同业务共享错误的计数。
  • 对限流状态设置合理的过期时间,避免无用数据长期堆积。
  • 如果采用滑动窗口或令牌桶,需要根据算法设计对应的数据结构和 Key。

第三步:编写 Lua 脚本,实现原子限流#

根据业务要求选择限流算法。例如,使用固定窗口时,可以通过 Redis 计数器记录指定时间窗口内的请求数量。

Lua 脚本在一次执行中完成计数更新、额度判断和结果返回,避免多个请求先后读取同一个旧计数、同时判断通过的问题。

执行逻辑可以概括为:

  1. 读取当前窗口的请求计数。
  2. 判断计数是否达到限额。
  3. 如果已经达到限额,返回拒绝结果。
  4. 如果尚未达到限额,更新计数并设置过期时间,然后返回放行结果。

如果同时实施用户级和接口全局级限流,应当在同一次脚本执行中检查相关额度,并确保只有所有必要的限流规则均通过时才扣减额度,避免其中一项失败却消耗另一项额度。

固定窗口实现简单,但在窗口交界处可能出现突发流量。如果业务对短时流量更加敏感,可以改用滑动窗口或令牌桶算法。

第四步:在 Gateway 中接入限流逻辑#

在 Spring Cloud Gateway 中,可以通过自定义 GlobalFilter 或 GatewayFilter 接入限流逻辑。

过滤器执行以下操作:

  1. 从经过认证的请求上下文中获取用户 ID。
  2. 根据请求路径或路由信息识别目标接口。
  3. 根据用户和接口生成限流参数。
  4. 调用 Redis 执行 Lua 脚本。
  5. 根据脚本结果决定是否继续执行过滤器链。

如果限流通过,则调用过滤器链继续转发请求;如果超过额度,则直接返回 HTTP 429 Too Many Requests,不再访问下游业务服务。

这里要注意,用户 ID 应当来自经过验证的身份信息,而不是直接信任客户端任意传入的请求头,否则用户可以伪造身份绕过用户级限流。

第五步:处理异常和降级#

Redis 限流属于请求入口的关键依赖,需要提前设计异常处理策略。

  • 超过限额:返回 HTTP 429,并提供适当的错误提示。
  • Redis 超时或不可用:根据接口重要性执行失败关闭或受控降级,避免所有请求不受限制地进入数据库。
  • 普通查询接口:可以根据业务容忍度设置短超时,并在必要时限制并发或临时拒绝部分请求。
  • 关键业务接口:优先保证业务正确性,不能为了可用性而直接跳过必要的保护规则。

同时,需要设置 Redis 调用超时、连接池参数和必要的监控,避免限流组件自身成为新的性能瓶颈。

第六步:增加数据库侧保护#

网关限流只能控制进入系统的请求量,不能保证每个请求的数据库开销相同。

因此,对于报表等重型查询接口,还需要结合数据库负载增加保护措施:

  • 限制重型查询的并发数量。
  • 合理配置数据库连接池,避免应用占满数据库连接。
  • 对慢查询进行分析,优化 SQL、索引和查询范围。
  • 对允许短暂陈旧的数据使用缓存,减少重复查询。
  • 对长时间运行的导出任务采用异步任务和排队处理。

通过入口限流与数据库侧保护配合,才能同时控制请求速率、执行并发和数据库资源占用。

4. 上线验证与监控#

上线前,先在测试环境进行压测,逐步增加请求量,观察限流是否生效,以及数据库负载是否处于可接受范围。

重点检查:

  • 限流前后的请求量和 HTTP 429 比例。
  • Redis 调用延迟、超时率和错误率。
  • 业务服务的并发请求数和响应时间。
  • 数据库 CPU、连接池使用率和慢查询数量。
  • Redis 故障时,系统是否按照预定策略降级。

如果压测发现数据库仍然过载,应进一步调整接口限额、查询并发限制或 SQL,而不是只提高 Redis 的处理能力。

5. 方案优缺点#

优点:

  • 多个网关实例共享限流状态,适用于分布式部署。
  • 可以根据用户、接口和业务类型配置不同规则。
  • 通过 Lua 原子执行限流判断,避免并发计数竞争。
  • 可以与网关入口保护、数据库并发控制和缓存结合使用。

缺点:

  • 需要维护 Redis、Lua 脚本和限流规则。
  • 请求需要访问 Redis,会增加一定的网络延迟。
  • Redis 故障可能影响入口请求处理,需要明确降级策略。
  • 速率限制无法完全解决慢 SQL 和数据库连接耗尽问题。

6. 方案总结#

Redis + Lua 适合需要在多个应用实例之间共享限流额度的场景。工程实施时,先分析高压力接口并确定限流维度,再设计 Redis Key 和 Lua 脚本,随后接入 Gateway,最后结合故障降级、数据库并发控制和压测监控形成完整的保护链路。

对于数据库流量过大的问题,核心不是单纯限制请求次数,而是尽可能在请求进入业务服务之前拦截超额流量,并进一步控制真正消耗数据库资源的查询并发和执行成本。

相关文章#

数据库流量过大:Redis + Lua 分布式限流方案
https://blog.xqcherry.top/posts/interview/scenarios/redis-lua-distributed-rate-limiting/
作者
xqcherry
发布于
2026-10-23
许可协议
CC BY-NC-SA 4.0