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

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

引言

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

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

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


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

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

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

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

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

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

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

相关推荐
SimonKing2 小时前
数据库又慢了?你需要一个像样的慢SQL报警系统
java·后端·程序员
唐叔在学习2 小时前
听说有老哥分不清Git branch和tag?这不看看嘛
git·后端
听风同学2 小时前
向量数据库---Chroma数据库入门到进阶教程
后端·架构
法欧特斯卡雷特2 小时前
Kotlin 2.2.20 现已发布!下个版本的特性抢先看!
android·前端·后端
Reboot2 小时前
寒武纪显卡命令
后端
码事漫谈2 小时前
为什么C++多态必须使用指针或引用?——从内存布局和对象身份的角度深入解析
后端
Reboot2 小时前
内网IDEA集成离线版DeepSeek指南
后端
惜鸟2 小时前
Python中@classmethod与@staticmethod区别
后端
hayson2 小时前
深入CSP:从设计哲学看Go并发的本质
后端·go