Redis 限流失效致服务雪崩?阿里架构师亲授分布式限流实战指南

B站影视 内地电影 2025-09-27 11:01 1

摘要:你是不是也踩过这样的坑?花 3 天用 Redis 搭好分布式限流,压测时 QPS 稳稳控在阈值内,可大促一上线,服务直接崩成 “兵马俑”—— 日志里满是Request rejected, limit exceeded,但监控显示 Redis 计数根本没到上限。

你是不是也踩过这样的坑?花 3 天用 Redis 搭好分布式限流,压测时 QPS 稳稳控在阈值内,可大促一上线,服务直接崩成 “兵马俑”—— 日志里满是Request rejected, limit exceeded,但监控显示 Redis 计数根本没到上限。上周我帮某电商团队排查问题时,就遇到了一模一样的情况,深挖下去才发现,90% 的开发者都没搞懂分布式限流的 “隐形规则”。

大促当天,Redis 限流为何成了 “纸老虎”?

某生鲜电商在 “618 早鸟场” 推出 9.9 元抢车厘子活动,技术团队提前做了三重防护:

网关层用 Nginx 做 IP 限流,单 IP 每秒限 10 次请求应用层基于 Redis 固定窗口算法,订单接口限 1000QPS数据库层设置最大连接数 200,加了读写分离

可活动开始 12 分钟后,监控大屏突然 “变红预警”:

订单接口响应时间从 80ms 飙升至 3.2s,超时率达 47%Redis 集群 CPU 使用率骤升 60%,出现大量command rejected数据库连接数突破 300,锁等待超时日志刷屏

紧急止损后复盘,才发现两个致命漏洞:Redis 计数用了非原子操作,且没考虑 Redis 主从同步延迟—— 这正是多数团队会踩的坑。

翻日志时,这段代码引起了我的注意(简化版):

// 错误示范:非原子操作的限流逻辑Long count = redisTemplate.opsForValue.get("order_limit");if (count == null) { redisTemplate.opsForValue.set("order_limit", 1, 1, TimeUnit.SECONDS); return true; // 允许请求}if (count

高并发下,线程 A 查到count=999后,线程 B 抢先完成自增,线程 A 再自增就变成 1001,直接击穿阈值。更坑的是,他们用了 Redis 主从架构,写请求走主库,读请求走从库,主从同步延迟 50ms,导致从库返回的count比实际值小,进一步放大了计数偏差。

窗口大小 1 秒,阈值 1000QPS第 0.9 秒涌入 1000 个请求,全部通过第 1.1 秒又涌入 1000 个请求,新窗口计数从零开始,再次全部通过

短短 0.2 秒内实际处理 2000 个请求,直接压垮服务。这就是我们常说的 “窗口切换风暴”,而多数人直到出问题才知道有这回事。

不同算法的适配场景其实有明确边界,盲目选型等于 “自杀”:

Redis 限流的核心是 “计数准确”,但高并发下 3 个细节会导致计数不准:

非原子操作:前面案例中 “get+increment” 分离,会出现并发安全问题过期时间未刷新:如果 key 在判断后过期,新的自增会创建无过期时间的 key,导致限流永久失效主从同步延迟:读从库时可能拿到旧数据,导致超限额请求通过

Redis 会保证 Lua 脚本内的操作原子执行,这是解决一致性问题的最优方案。以下是滑动窗口算法的 Lua 脚本(可直接复制使用):

-- 滑动窗口限流:key=限流标识,window=窗口大小(ms),limit=阈值local key = KEYS[1]local window = tonumber(ARGV[1])local limit = tonumber(ARGV[2])local now = tonumber(ARGV[3])-- 1. 移除窗口外的旧数据redis.call("ZREMRANGEBYSCORE", key, 0, now - window)-- 2. 统计当前窗口内的请求数local count = redis.call("ZCARD", key)-- 3. 判断是否超过阈值if count

Java 调用示例:

// 正确示范:基于Lua脚本的分布式限流public boolean isAllowed(String key, long windowMs, int limit) { String luaScript = "local key = KEYS[1]..."; // 上面的Lua脚本 Long result = (Long) redisTemplate.execute( new DefaultRedisScript(luaScript, Long.class), Collections.singletonList(key), String.valueOf(windowMs), String.valueOf(limit), String.valueOf(System.currentTimeMillis) ); return result == 1;}

很多团队只在应用层做限流,却忽略了全链路防护—— 微服务架构下,请求要经过 “网关→应用→数据库→缓存” 多个节点,任何一个环节过载都会崩。

就像前面的案例,虽然应用层挡了部分请求,但数据库没做限流,大量通过应用层的请求还是把 DB 连接池撑爆了。更致命的是,他们的限流器没有降级策略,Redis 一卡,整个限流逻辑瘫痪,反而成了系统的 “阿喀琉斯之踵”。

以令牌桶算法为例,压测时要重点看两个指标:

突发流量下的拒绝率:比如阈值 1000QPS,桶容量 2000,压测 2000QPS 突发流量,拒绝率应低于 5%长期高负载下的稳定性:持续 10 分钟 1000QPS 压测,Redis CPU 使用率应低于 30%用户请求 → 网关层(Nginx限流)→ 应用层(Redis集群限流)→ 数据层(DB限流) ↓ ↓ ↓ 降级策略 降级策略 降级策略 (返回默认值) (切换本地限流) (拒绝非核心请求)

关键实现细节

Redis 集群:采用 “主从 + 哨兵” 架构,主库宕机时 3 秒内切换到从库,避免单点故障本地限流兜底:用 Guava RateLimiter 做降级方案,当 Redis 不可用时自动切换,配置示例:// 降级兜底:本地令牌桶限流private final RateLimiter localLimiter = RateLimiter.create(800.0); // 800QPSpublic boolean fallbackLimit { return localLimiter.tryAcquire;}

限流标识设计:采用 “接口名 + 用户 ID/IP” 的复合 key,支持精细化限流,比如:

普通用户:order_create:user_123,限 10QPS管理员:order_create:admin_456,限 100QPS

用 Spring Cloud Gateway 做入口防护,核心配置:

spring: cloud: gateway: routes: - id: order-service uri: lb://order-service predicates: - Path=/api/order/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 5000 # 基础速率 redis-rate-limiter.burstCapacity: 10000 # 突发容量 key-resolver: "#{@ipKeyResolver}" # 按IP限流

作用:拦截爬虫、恶意刷接口等流量,减轻应用层压力。

用注解方式嵌入业务代码,解耦限流逻辑,示例:

// 订单创建接口限流:1000QPS,令牌桶算法@RateLimit(key = "order_create", algorithm = Algorithm.TOKEN_BUCKET, limit = 1000)@RequestMapping("/create")public Result createOrder(@RequestBody OrderDTO order) { // 业务逻辑}

实现思路:自定义注解 + AOP,在切面中调用前面的 Lua 脚本限流逻辑。

接入 Prometheus 监控 3 个核心指标:

应用 CPU 使用率(阈值:80%)接口响应时间(阈值:500ms)Redis 拒绝率(阈值:5%)

用 Spring Cloud Config 做动态配置,当指标超标时:

CPU>80% → 限流阈值下调 20%响应时间 > 500ms → 限流阈值下调 30%拒绝率 > 5% → 保持当前阈值并报警工具选型对比:Redis、Sentinel、Resilience4j 怎么选?

很多开发者纠结于工具选择,其实不同工具适配不同场景:

工具核心优势劣势推荐场景Redis+Lua分布式支持好、灵活度高需自己实现算法,运维成本高复杂业务场景、自定义限流需求Sentinel开箱即用、监控完善分布式限流需依赖注册中心微服务架构、Spring Cloud 生态Resilience4j轻量级、支持响应式编程中文文档少,社区支持一般Spring Boot 2.x、响应式项目Nginx性能强、适合入口限流精细化控制弱,配置修改麻烦网关层、IP 级限流

实战建议:网关用 Nginx,应用层用 Redis+Lua,微服务架构优先加 Sentinel 做监控兜底。

做技术这么多年,我发现分布式限流从来没有 “万能方案”,只有 “适配方案”。比如:

有个朋友用 Sentinel 做分布式限流,结果注册中心挂了,限流直接失效,你遇到过吗?跨地域部署的系统,Redis 集群延迟高,怎么保证计数准确?低 QPS 但高重要性的接口(比如支付回调),用哪种限流算法更合适?

欢迎在评论区分享你的实战经验。

来源:从程序员到架构师

相关推荐