固定窗口限流实现

基于Redis的固定窗口计数器算法,简单粗暴但存在临界问题。核心是利用Redis的incr和过期时间控制单位时间内的请求量。

@RestController
public class FixedWindowController {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String LIMIT_KEY_PREFIX = "limit:fixed:";
    private static final int MAX_REQUESTS = 100;
    private static final int WINDOW_SECONDS = 60;

    @GetMapping("/api")
    public ResponseEntity<String> api(@RequestHeader String clientIp) {
        String key = LIMIT_KEY_PREFIX + clientIp;
        Long count = redisTemplate.opsForValue().increment(key);
        
        if (count == 1) {
            redisTemplate.expire(key, WINDOW_SECONDS, TimeUnit.SECONDS);
        }
        
        if (count > MAX_REQUESTS) {
            return ResponseEntity.status(429).body("Too many requests");
        }
        
        return ResponseEntity.ok("Success");
    }
}

滑动窗口限流实现

通过Redis的ZSet实现精确的滑动窗口控制,解决固定窗口的临界突刺问题,但消耗更多内存。

@RestController
public class SlidingWindowController {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String LIMIT_KEY_PREFIX = "limit:sliding:";
    private static final int MAX_REQUESTS = 100;
    private static final int WINDOW_SECONDS = 60;

    @GetMapping("/api")
    public ResponseEntity<String> api(@RequestHeader String clientIp) {
        long now = System.currentTimeMillis();
        long windowStart = now - WINDOW_SECONDS * 1000L;
        
        String key = LIMIT_KEY_PREFIX + clientIp;
        redisTemplate.opsForZSet().removeRangeByScore(key, 0, windowStart);
        
        long currentCount = redisTemplate.opsForZSet().zCard(key);
        if (currentCount >= MAX_REQUESTS) {
            return ResponseEntity.status(429).body("Too many requests");
        }
        
        redisTemplate.opsForZSet().add(key, String.valueOf(now), now);
        redisTemplate.expire(key, WINDOW_SECONDS, TimeUnit.SECONDS);
        
        return ResponseEntity.ok("Success");
    }
}

分布式令牌桶实现

结合Lua脚本保证原子性,实现平滑的流量控制。适合需要平稳处理突发流量的场景。

@RestController
public class TokenBucketController {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String LIMIT_SCRIPT =
        "local key = KEYS[1]\n" +
        "local rate = tonumber(ARGV[1])\n" +
        "local capacity = tonumber(ARGV[2])\n" +
        "local now = tonumber(ARGV[3])\n" +
        "local requested = tonumber(ARGV[4])\n" +
        "local last_tokens = tonumber(redis.call('get', key) or capacity)\n" +
        "local last_refreshed = tonumber(redis.call('get', key..':ts') or now)\n" +
        "local delta = math.max(0, now - last_refreshed)\n" +
        "local new_tokens = math.min(capacity, last_tokens + delta * rate)\n" +
        "local allowed = new_tokens >= requested\n" +
        "local result = 0\n" +
        "if allowed then\n" +
        "    new_tokens = new_tokens - requested\n" +
        "    redis.call('set', key, new_tokens)\n" +
        "    redis.call('set', key..':ts', now)\n" +
        "    result = 1\n" +
        "end\n" +
        "return result";

    @GetMapping("/api")
    public ResponseEntity<String> api(@RequestHeader String clientIp) {
        String key = "limit:token:" + clientIp;
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(LIMIT_SCRIPT, Long.class),
            Collections.singletonList(key),
            "10", "100", String.valueOf(System.currentTimeMillis() / 1000), "1"
        );
        
        if (result == 0) {
            return ResponseEntity.status(429).body("Too many requests");
        }
        
        return ResponseEntity.ok("Success");
    }
}

生产环境注意事项

  1. Redis集群模式:所有方案在集群环境下需要确保相同IP的请求路由到同一节点,可通过hash tag实现,如{limit}:user1

  2. 限流维度扩展:实际业务中可能需要组合用户ID+接口URI作为key,例如limit:/order:user123

  3. 性能监控:建议通过AOP记录限流触发情况,关键指标包括:

    • 请求总数
    • 被限流数
    • 限流key分布
  4. 动态配置:将规则配置存储在数据库或配置中心,实现运行时调整:

@RefreshScope
@Configuration
public class RateLimitConfig {
    @Value("${rate.limit.max:100}")
    private int maxRequests;
    
    // getter省略
}
  1. 降级策略:达到限流阈值时可考虑返回缓存数据或排队机制,避免简单返回429

  2. 多级限流:结合Nginx层的基础限流和业务层的精细限流,形成多级防护

  3. 白名单机制:对内部IP或特殊用户免除限流

private boolean isWhitelist(String ip) {
    return Arrays.asList("10.0.0.1", "192.168.1.100").contains(ip);
}
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐