接口限流Part 1:固定窗口

在微服务架构中,接口限流是保障系统稳定性的重要手段。当某个接口突然收到大量请求时,如果没有限流保护,可能导致数据库连接池耗尽、内存溢出,甚至整个服务崩溃。
**接口限流(Rate Limiting)**的核心目标很明确:控制每个用户或IP的请求频率,防止恶意攻击和异常流量对系统造成冲击。

一、固定窗口限流原理

固定窗口算法的核心思想很简单:将时间划分为固定长度的窗口,在每个窗口内统计请求次数,超过阈值就拒绝服务

工作原理

  • 时间窗口:比如设置1分钟为一个窗口
  • 计数规则:每个窗口内,同一IP对同一接口的请求次数不能超过设定值
  • 重置机制:新窗口开始时,计数器清零重新计数

具体示例

假设限制为每分钟10次请求:

  • 10:00-10:01窗口:用户请求8次 → 允许
  • 10:01-10:02窗口:用户请求5次 → 允许(新窗口重新计数)
  • 10:00-10:01窗口:用户请求12次 → 第11次开始返回429错误

技术实现

  • 使用Redis存储计数器
  • Key格式:rate_limit:IP:接口路径
  • 通过EXPIRE实现窗口重置

二、Redis + Lua 实现方案

为什么选择Redis + Lua?因为Lua脚本在Redis中是原子性执行 的,可以避免并发问题,确保计数器的准确性。

1. 设计限流Key

String key = String.format("rate_limit:%s:%s", ipAddress, apiPath);
// 例如:rate_limit:192.168.1.100:/api/login

设计思路

  • 使用IP + 接口路径作为复合Key,实现细粒度限流
  • 不同接口可以设置不同的限流策略
  • Key命名规范,便于监控和调试

2. 核心Lua脚本

-- KEYS[1] = 限流key(如 rate_limit:127.0.0.1:/api/login)
-- ARGV[1] = 限制次数(如 20)
-- ARGV[2] = 窗口时间(如 60秒)

local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])

-- 步骤1:计数器+1
local current = redis.call("INCR", key)

-- 步骤2:如果是第一次访问,设置过期时间
if current == 1 then
    redis.call("EXPIRE", key, window)
end

-- 步骤3:判断是否超过限制
if current > limit then
    return 0  -- 拒绝请求
else
    return 1  -- 允许请求
end

脚本执行流程

  1. INCR操作 :每次请求都将计数器+1
  2. EXPIRE设置 :首次访问时设置Key过期时间,实现窗口重置
  3. 阈值判断 :比较当前计数与限制阈值,决定是否放行

为什么用Lua? 整个流程在Redis中原子执行,避免并发竞态条件。

3. Java集成调用

@Service
public class RateLimitService {
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    /**
     * 检查是否允许请求
     * @param ip 客户端IP
     * @param path 接口路径
     * @return true-允许,false-拒绝
     */
    public boolean isAllowed(String ip, String path) {
        String key = String.format("rate_limit:%s:%s", ip, path);
        Long result = stringRedisTemplate.execute(
            redisScript,
            Collections.singletonList(key),
            "20", "60" // 限制:20次/60秒
        );
        return result != null && result == 1;
    }
}

使用建议

  • 可以结合Spring AOP实现注解驱动限流:@RateLimit(limit=20, window=60)
  • 建议将限流配置外部化,支持动态调整
  • 生产环境建议增加监控告警,关注限流触发情况

至此,一个分布式固定窗口限流器 就完成了!

三、优缺点分析

优点

  1. 实现简单 :逻辑清晰,易于理解和维护
  2. 性能优秀 :Redis + Lua 原子操作,响应速度快
  3. 内存友好 :Key自动过期,不会造成内存泄漏
  4. 分布式支持 :天然支持多实例部署

缺点

  1. 边界突刺问题
    时间轴:|----窗口1----|----窗口2----|
    请求: 10:00:59 10:01:00
    结果: 第20次 第1次(新窗口)
    问题: 在边界时刻可能出现2倍阈值的突发流量
  2. 限流不够平滑 :窗口重置时计数器清零,缺乏连续性

适用场景

适合

  • 对精度要求不高的场景
  • 需要快速实现的限流需求
  • 低频接口的简单限流

不适合

  • 高精度限流要求
  • 需要平滑限流的场景
  • 对边界突刺敏感的业务

改进方案

如果遇到边界突刺问题,可以考虑:

  • 滑动窗口算法 :平滑限流,消除边界问题
  • 令牌桶算法 :支持突发流量,更加灵活
  • 漏桶算法 :严格控制输出速率

后续文章将详细介绍这些改进方案,敬请期待!