高并发场景下限流算法实践与性能优化指南

发布于:2025-09-15 ⋅ 阅读:(28) ⋅ 点赞:(0)

限流指南封面

高并发场景下限流算法实践与性能优化指南

在大规模并发访问环境中,合理的限流策略能保护后端服务稳定运行,避免系统因瞬时高并发导致资源耗尽或崩溃。本文将从原理出发,深入解析几种主流限流算法,并结合Java和Redis给出完整可运行的代码示例,最后分享在生产环境中的性能优化建议。


一、技术背景与应用场景

随着业务流量激增,API网关、微服务接口等处于高并发流量的第一线。常见场景包括:

  • 短期秒级爆发流量,如秒杀、抢购场景
  • 持续大规模监控上报、日志埋点
  • 第三方系统突发调用

此时若无限流保护,系统可能出现线程池耗尽、数据库连接池耗尽、Redis阻塞等问题,导致服务异常甚至宕机。

二、核心原理深入分析

1. 固定窗口计数(Fixed Window)

思路:将时间切分为大小相同的固定窗口(如1秒),记录窗口内的请求计数,超过阈值拒绝。

优点:实现简单,计数开销低。

缺点:临界点易出现短时间内双倍阈值的突发量。

2. 滑动窗口计数(Sliding Window Counter)

思路:利用两个固定窗口,以及当前窗口的权重,平滑地计算限流。

实现:

  • 记录上一个窗口的计数 count_prev 和当前窗口的计数 count_cur
  • 按时间比例计算:total = count_prev * (1 - t/T) + count_cur

3. 滑动窗口日志(Sliding Window Log)

思路:记录每次请求的时间戳,通过检查日志中有效时间段的请求数判断是否超过阈值。

优点:精确;缺点:存储和遍历开销大,不适合超高频场景。

4. 令牌桶(Token Bucket)

思路:以固定速率往桶中添加令牌,请求到来先尝试取令牌,若有则放行,否则拒绝或等待。

优点:支持突发流量,可平滑输出。

5. 漏桶(Leaky Bucket)

思路:将请求排入“漏桶”队列,以固定速率处理队列中的请求;队列满则拒绝新请求。

与令牌桶的区别在于:漏桶保证输出速率固定,而令牌桶更灵活。


三、关键源码解读与示例

1. Guava RateLimiter(令牌桶实现)

import com.google.common.util.concurrent.RateLimiter;

public class GuavaLimiterDemo {
    // 创建每秒产生 100 个令牌的令牌桶
    private static final RateLimiter limiter = RateLimiter.create(100);

    public boolean tryAcquire() {
        // 非阻塞立即获取令牌,返回是否获取成功
        return limiter.tryAcquire();
    }

    public static void main(String[] args) {
        GuavaLimiterDemo demo = new GuavaLimiterDemo();
        if (demo.tryAcquire()) {
            // 业务处理
            System.out.println("请求通过");
        } else {
            System.out.println("限流处理");
        }
    }
}

2. Redis 滑动窗口计数(Lua 原子操作)

文件:scripts/sliding_window.lua

-- KEYS[1] 主键,ARGV[1]=当前时间戳毫秒,ARGV[2]=窗口大小(毫秒),ARGV[3]=阈值
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])

-- 移除过期记录
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
-- 统计当前窗口请求数
local count = redis.call('ZCARD', key)
if count < limit then
  -- 记录本次请求
  redis.call('ZADD', key, now, now)
  -- 设置过期防止持久化
  redis.call('PEXPIRE', key, window)
  return 1
end
return 0

Java 调用示例:

public class RedisSlidingWindowLimiter {
    private final JedisPool jedisPool;
    private final String scriptSha1;
    
    public RedisSlidingWindowLimiter(JedisPool pool) {
        this.jedisPool = pool;
        try (Jedis jedis = jedisPool.getResource()) {
            scriptSha1 = jedis.scriptLoad(
                new String(Files.readAllBytes(Paths.get("scripts/sliding_window.lua")))
            );
        }
    }

    public boolean tryAcquire(String key, long windowMs, long limit) {
        try (Jedis jedis = jedisPool.getResource()) {
            Object res = jedis.evalsha(
                scriptSha1,
                Collections.singletonList(key),
                Arrays.asList(String.valueOf(System.currentTimeMillis()), String.valueOf(windowMs), String.valueOf(limit))
            );
            return Integer.valueOf(1).equals(res);
        }
    }
}

3. Spring Cloud Gateway 全局限流过滤器

@Configuration
public class GatewayRateLimiterConfig {

    @Bean
    public GlobalFilter rateLimiterFilter(RedisSlidingWindowLimiter limiter) {
        return (exchange, chain) -> {
            String requestKey = "gateway:" + exchange.getRequest().getPath();
            boolean pass = limiter.tryAcquire(requestKey, 1000, 200); // 1秒200次
            if (!pass) {
                exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
                return exchange.getResponse().setComplete();
            }
            return chain.filter(exchange);
        };
    }
}

项目结构示例:

src/
├─ main/
│  ├─ java/
│  │  └─ com.example.limiter/
│  │     ├─ GuavaLimiterDemo.java
│  │     ├─ RedisSlidingWindowLimiter.java
│  │     └─ GatewayRateLimiterConfig.java
│  └─ resources/
│     └─ scripts/
│        └─ sliding_window.lua

四、实际应用示例

在电商秒杀场景,采用 Redis 滑动窗口限流:

  1. 用户请求进入API网关,先通过限流过滤器;
  2. 限流通过后,执行业务逻辑下单;
  3. 请求高峰时可动态调整阈值,或采用多级限流(API 网关、微服务内部双层)策略。

五、性能特点与优化建议

  1. 单机 vs 分布式:Guava 限流仅适用于单实例,多实例需借助 Redis 或 ZooKeeper 实现全局限流。
  2. 数据清理:滑动窗口日志方式需定期清理过期数据,否则内存/Redis 会堆积。
  3. Lua 原子性:使用 Redis + Lua 能保证高并发下的限流原子操作。
  4. 批量令牌:令牌桶算法可一次性发放一定数量令牌,减少系统调用开销。
  5. 阈值动态调整:结合监控(Prometheus)动态调整限流策略,避免过严或过松。
  6. 多级限流:前端网关 + 后端服务双层限流方案能增强系统鲁棒性。

总结与最佳实践

限流是高并发系统的核心防护手段。本文从固定窗口、滑动窗口到令牌桶、漏桶算法,结合 Java/Redis 和 Spring Cloud Gateway 给出了完整实现示例,并提出了多级限流、动态阈值、性能优化等实战建议。希望对构建稳定、高可用的后端限流体系有所帮助。