如何建高可用系统:接口限流

你好,我是风一样的树懒,一个工作十多年的后端专家,曾就职京东、阿里等多家互联网头部企业。公众号"吴计可师",已经更新了过百篇高质量的面试相关文章,喜欢的朋友欢迎关注点赞

引言

凌晨两点,促销活动刚开始3秒,核心服务接口因瞬间流量过载而瘫痪,导致订单大面积失败。

复盘结论:缺乏有效的接口限流 防护,让整个系统在流量洪峰前"裸奔"。

本文将带你从零开始,深入浅出,构建一套从算法原理到分布式落地的完整限流方案。


**一、为什么必须限流?

限流(Rate Limiting)不是为了让系统跑得更快,而是为了不让它死掉 。它是系统稳定性建设的基石,与缓存降级并称为高可用系统的"三驾马车"。

  1. 防止资源耗尽:避免突发流量打满CPU、内存、数据库连接等资源,导致服务雪崩。
  2. 应对恶意攻击**:防止CC攻击、刷单、爬虫等恶意行为,保护后端资源。
  3. 实现业务分级:为不同用户(如VIP、普通用户)或不同API分配不同的配额,保障核心业务。
  4. 削峰填谷:将突发流量整形为匀速请求,避免下游服务被压垮。
flowchart TD A[海量用户请求] --> B[负载均衡层] B --> C[限流<br>第一道防线<br>拒绝过量请求] C --> D[服务层] D --> E[降级<br>第二道防线<br>弃卒保帅] E --> F[缓存/数据库] F --> G[熔断<br>第三道防线<br>快速失败<br>防止雪崩]

二、核心限流算法:原理、场景与抉择

1. 固定窗口计数器(Fixed Window)
  • 原理:将时间划分为固定窗口(如1分钟),统计窗口内请求数,超限则拒绝。
  • 实现RedisINCREXPIRE 命令。
  • 优点:实现简单,内存占用少。
  • 缺点临界突变问题 。在两个窗口临界点,可能瞬间通过 2 * limit 个请求。

适用场景:对精度要求不高的简单场景,如限制短信验证码发送频率。

2. 滑动窗口(Sliding Window)
  • 原理:将固定窗口细分为更小的时间片(如1分钟的窗口分为60个1秒的片),滑动地统计最近一个窗口内的请求数。
  • 实现 :使用 RedisZSET(有序集合)或 List 结构,存储时间戳并清理过期数据。
  • 优点:有效解决临界问题,精度较高。
  • 缺点:比固定窗口更耗内存。

适用场景:大多数业务场景,是精度和复杂度之间的良好平衡。

3. 漏桶算法(Leaky Bucket)
  • 原理 :请求先进入一个容量固定的"桶"中,桶以恒定的速率漏水(处理请求)。桶满则丢弃新请求。
  • 优点平滑突发流量,输出流量始终是恒定的,非常适合保护下游系统。
  • 缺点:无法应对突发流量,即使系统有空闲资源,请求也必须排队等待。

适用场景:保护数据库、第三方API等下游系统,确保请求速率绝对均匀。

4. 令牌桶算法(Token Bucket)
  • 原理 :系统以恒定的速率向一个容量固定的"桶"中添加令牌。请求到达时,必须获取到一个令牌才能被处理,否则被拒绝。
  • 优点既允许突发流量(桶中有令牌时可一次性取用),又能限制长期平均速率。
  • 缺点:实现相对复杂。

适用场景绝大多数API限流场景 。它是弹性应对突发流量和保护系统的最佳平衡方案,也是 Google GuavaAmazon 等主流方案的选择。

quadrantChart title 四大限流算法选型象限 x-axis "处理简单" --> "处理复杂" y-axis "限制突发" --> "允许突发" "固定窗口": [0.1, 0.1] "滑动窗口": [0.4, 0.3] "漏桶": [0.6, 0.8] "令牌桶": [0.9, 0.9]

三、分布式限流实战:基于Redis的令牌桶实现

单机限流可用 Guava,但分布式系统必须依赖中间件,如 Redis

lua 复制代码
-- Redis Lua 脚本实现令牌桶(原子操作)
local key = KEYS[1] -- 限流的键(如: user:123:api_order)
local limit = tonumber(ARGV[1]) -- 桶容量
local rate = tonumber(ARGV[2]) -- 令牌生成速率(个/秒)
local now = tonumber(ARGV[3]) -- 当前时间戳
local requested = tonumber(ARGV[4]) -- 本次请求的令牌数(默认为1)

local last_time_key = key .. ':time'
local tokens_key = key .. ':tokens'

-- 1. 获取当前桶中的令牌数和最后更新时间
local last_tokens = tonumber(redis.call('get', tokens_key)) or limit
local last_time = tonumber(redis.call('get', last_time_key)) or now

-- 2. 计算时间差,并生成新令牌
local time_passed = now - last_time
local new_tokens = time_passed * rate -- 期间应生成的令牌数
local current_tokens = math.min(limit, last_tokens + new_tokens)

-- 3. 判断是否有足够令牌
if current_tokens < requested then
    -- 令牌不足,拒绝
    return 0
else
    -- 4. 扣减令牌,更新时间和令牌数
    current_tokens = current_tokens - requested
    redis.call('set', tokens_key, current_tokens)
    redis.call('set', last_time_key, now)
    -- 设置过期时间,避免冷数据长期占用内存(设置为窗口时间的2倍)
    redis.call('expire', tokens_key, math.ceil(limit/rate) * 2)
    redis.call('expire', last_time_key, math.ceil(limit/rate) * 2)
    return 1 -- 允许通过
end

调用示例(Spring Boot)

java 复制代码
@Component
public class RedisRateLimiter {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public boolean tryAcquire(String key, int limit, int rate) {
        long now = System.currentTimeMillis() / 1000;
        String luaScript = "..."; // 上面的Lua脚本
        RedisScript<Long> script = RedisScript.of(luaScript, Long.class);
        Long result = redisTemplate.execute(script, 
                Collections.singletonList(key), 
                String.valueOf(limit), 
                String.valueOf(rate), 
                String.valueOf(now),
                "1");
        return result == 1;
    }
}

四、生产环境最佳实践与架构设计

1. 限流维度要灵活
  • 全局限流:限制整个集群对某个接口的总QPS。
  • 用户限流user_id:api_path,防止单用户滥用。
  • IP限流ip:api_path,防止单IP攻击。
  • 参数限流:如对同一个商品ID的查询进行限流。
2. 限流规则动态化

不要将规则硬编码在代码中!应配置在 NacosApolloZooKeeper 等配置中心,支持实时推送和修改。

3. 优雅响应与降级

被限流的请求不应粗暴返回 429 Too Many Requests,应提供友好 fallback:

  • 返回队列位置:告知用户预计等待时间。
  • 返回默认值:如商品详情页限流,返回缓存中的静态信息。
  • 请求排队:将请求转入消息队列,异步处理。
4. 分层限流与集群限流
  • 分层:在网关(Nginx/Spring Cloud Gateway)、应用层、DB层分别设置限流。
  • 集群同步 :对于严格全局限流,可使用 Redis 或专门的限流中间件(如 Sentinel)来同步集群状态。

总结与展望

关键点 核心方案 工具选型
算法核心 令牌桶(允许突发)、漏桶(平滑流量) Guava, Redis + Lua
分布式实现 Redis(原子操作)、中间件 Redis, Sentinel, Nginx
规则管理 动态配置 Apollo, Nacos
架构部署 网关层 + 应用层分层限流 Spring Cloud Gateway, Zuul

限流设计没有银弹,只有最适合业务场景的方案。从理解算法原理开始,到选择合适的工具落地,再到构建全方位的监控体系,每一步都是构建高可用系统不可或缺的环节。

今天文章就分享到这儿,喜欢的朋友可以关注我的公众号,回复"进群",可进免费技术交流群。博主不定时回复大家的问题。 公众号:吴计可师

相关推荐
IT_陈寒38 分钟前
Vite项目build后路由404了?你可能漏了这个小配置
前端·人工智能·后端
宸津-代码粉碎机1 小时前
Spring AI企业级实战|从RAG优化到Agent多工具调度
java·大数据·人工智能·后端·python·spring
吴佳浩1 小时前
AI Infra 的真相:Go 没输,rust也不是取代
后端·rust·go
喵个咪1 小时前
实时游戏网络协议深度对比:KCP vs WebRTC vs WebSocket
后端·websocket·webrtc
普通网友2 小时前
springboot之集成Elasticsearch
spring boot·后端·elasticsearch
QuZero2 小时前
Guava Cache Deep Dive
java·后端·算法·guava
leeyi2 小时前
SSE 实时推流 —— Token 怎么一个个蹦出来
后端·agent
leeyi2 小时前
ReAct 循环的 50 行 Go 实现,逐行拆解
后端·agent
leeyi2 小时前
HITL:让人类随时叫停 AI,并且能优雅地继续
后端·agent
用户34232323763172 小时前
采集网关的离线缓存与断点续传——当网络不可靠时,数据一条都不能丢
后端