自定义限流案例

wen java案例 1

从零构建高可用系统的流量控制实战指南

目录导读

  1. 为什么需要自定义限流 — 剖析默认限流器的不足
  2. 核心算法选型 — 计数器、滑动窗口、令牌桶与漏桶的取舍
  3. 自定义限流案例实战 — 基于Redis+Lua的分布式限流实现
  4. 动态限流规则 — 热更新与多维度策略
  5. 限流与熔断、降级的协同 — 构建完整防护体系
  6. 常见问题与解答(FAQ) — 避坑指南

为什么需要自定义限流

在微服务架构中,限流是保护系统免于过载的第一道防线,虽然Spring Cloud Gateway、Sentinel等框架提供了内置限流器,但在真实场景中,默认实现往往面临三大痛点:规则不可动态调整(需重启)、无法按业务维度区分(如VIP用户与普通用户)、与自研监控体系割裂,自定义限流成为高并发系统的必备技能。

自定义限流案例

核心算法选型

算法 原理 优点 缺点 适用场景
固定窗口计数器 时间窗内计数 实现简单 临界突发流量 非核心接口
滑动窗口日志 记录时间戳数组 精准 内存占用高 大规模集群不适用
令牌桶 匀速放令牌 允许突发流量 需维护令牌生成器 推荐用于业务接口
漏桶 匀速流出 强限流效果 无法应对突发 流量整形场景

实战建议:优先选择令牌桶算法,它天然允许短时突发(如秒杀开启的瞬间),同时保护下游系统。

自定义限流案例实战 — Redis+Lua实现分布式限流

场景定义

某电商平台需要针对 /order/submit 接口实现每个用户ID每分钟最多10次请求,同时全局限流1000 QPS

核心代码逻辑(Lua脚本原子性保证)

-- 用户级限流键
local user_key = KEYS[1] .. ARGV[1]
local global_key = KEYS[2]
-- 令牌桶参数(用hash存储当前令牌数+最后更新时间)
local tokens_key = user_key .. ":tokens"
local timestamp_key = user_key .. ":ts"
local rate = tonumber(ARGV[2]) -- 填充速率(每秒1个)
local capacity = tonumber(ARGV[3]) -- 桶容量(10个)
local now = tonumber(ARGV[4])
local last_tokens = tonumber(redis.call('get', tokens_key) or capacity)
local last_refreshed = tonumber(redis.call('get', timestamp_key) or now)
local delta = math.max(0, (now - last_refreshed) / 1000)
local current_tokens = math.min(capacity, last_tokens + delta * rate)
local allowed = 0
if current_tokens >= 1 then
  current_tokens = current_tokens - 1
  allowed = 1
end
redis.call('set', tokens_key, current_tokens)
redis.call('set', timestamp_key, now)
redis.call('expire', tokens_key, 60) -- 设置过期
-- 全局限流(使用滑动窗口计数器)
local global_count = redis.call('incr', global_key)
if global_count == 1 then
  redis.call('expire', global_key, 1)
end
if global_count > tonumber(ARGV[5]) then
  allowed = 0
end
return allowed

关键设计点说明

  • 原子性:通过Lua脚本将判断、扣减、过期逻辑打包执行,规避并发竞争。
  • 两级限流:用户级用令牌桶(容忍小突发),全局用计数器(严格限制整体流量)。
  • 内存优化:每个用户键设置TTL,防止Redis内存膨胀。

动态限流规则

固定写死的限流值在流量突增时仍需人工干预,自定义限流的优势在于:

  • 配置中心化:将限流阈值存放在Nacos/Etcd,监听变更并重新加载Lua脚本参数。
  • 多维度组合:IP+用户ID+设备指纹进行联合限流。
  • 自动降级机制:当监控系统发现错误率>5%时,自动将限流阈值从100%调整至80%。

限流与熔断、降级的协同

限流器拒绝请求后,不能简单返回错误,而应:

  • 快速失败:返回HTTP 429 + 可重试时间提醒。
  • 触发降级:将请求转发至兜底缓存服务,或返回默认预热数据。
  • 记录失败样本:为熔断器(如Resilience4j)提供输入,当限流触发频率超过阈值时打开熔断。

常见问题与解答(FAQ)

Q1:Redis单点故障会导致整个系统不可用,如何解决? A:使用Redis Cluster,并在Lua脚本中采用“无锁读+最终一致性”,更稳健的做法是本地内存限流(如Guava RateLimiter)+Redis分布式限流形成双层防护,本地层失败时自动降级为全局限流。

Q2:滑窗算法与令牌桶在真实场景中,哪个更容易出现“误杀”? A:令牌桶更容易出现“漏网之鱼”而非误杀,令牌桶允许桶内保留10个预存令牌,短时突发会被放行,若业务要求零突刺(如支付回调),建议改用漏桶或滑动窗口。

Q3:自定义限流与原生网关限流,如何选择? A:原生网关(如Kong)适合入口流量控制;自定义限流适合业务代码内部逻辑(如某用户创建订单),两者可叠加:网关粗粒度限制总数,业务代码细粒度限制用户级。

Q4:如何压测验证自定义限流的准确性? A:使用Jmeter并发200线程循环请求,观察被拦截请求数为“总请求数-限流上限”,重点测试临界时间窗口(如第59秒与第61秒的请求),确认无边界溢出。

抱歉,评论功能暂时关闭!