写点什么

瞧瞧别人家的限流,那叫一个优雅!

  • 2025-04-10
    福建
  • 本文字数:1944 字

    阅读完需:约 6 分钟

1 常用限流方案


1.1 固定窗口计数器


核心原理:以固定时间窗口(如 1 秒)为周期,统计周期内请求数,超过阈值则拒绝后续请求。



具体代码实现如下:


// 线程安全实现(AtomicLong优化版)public class FixedWindowCounter {    private final AtomicLong counter = new AtomicLong(0);    private volatile long windowStart = System.currentTimeMillis();    private final int maxRequests;    private final long windowMillis;
public boolean tryAcquire() { long now = System.currentTimeMillis(); if (now - windowStart > windowMillis) { if (counter.compareAndSet(counter.get(), 0)) { windowStart = now; } } return counter.incrementAndGet() <= maxRequests; }}
复制代码


致命缺陷:假设设置 1 秒 100 次限制,0.9 秒时突发 100 次请求,下一秒 0.1 秒又放行 100 次,实际两秒内通过 200 次。


就像红绿灯切换时车辆抢行,容易引发"临界点突刺"。


适用场景:日志采集、非关键性接口的粗粒度限流


1.2 滑动窗口


核心原理:将时间窗口细分为更小的时间片(如 10 秒),统计最近 N 个时间片的请求总和。



基于 Redis 的 Lua 脚本如下:


// Redis Lua实现滑动窗口(精确到毫秒)String lua = """    local now = tonumber(ARGV    local window = tonumber(ARGV    local key = KEYS[1]        redis.call('ZREMRANGEBYSCORE', key, '-inf', now - window)    local count = redis.call('ZCARD', key)        if count < tonumber(ARGV then        redis.call('ZADD', key, now, now)        redis.call('EXPIRE', key, window/1000)        return 1    end    return 0    """;
复制代码


技术亮点:某证券交易系统采用滑动窗口后,将 API 异常率从 5%压降至 0.3%。


通过 Redis ZSET 实现时间切片,误差控制在±10ms 内。


优势对比



2.3 漏桶算法


核心原理:请求像水流一样进入漏桶,系统以固定速率处理请求。


桶满时新请求被丢弃。



具体实现如下:


// 漏桶动态实现(Semaphore优化版)public class LeakyBucket {    private final Semaphore permits;    private final ScheduledExecutorService scheduler;
public LeakyBucket(int rate) { this.permits = new Semaphore(rate); this.scheduler = Executors.newScheduledThreadPool(1); scheduler.scheduleAtFixedRate(() -> permits.release(rate), 1, 1, TimeUnit.SECONDS); }
public boolean tryAcquire() { return permits.tryAcquire(); }}
复制代码


技术痛点:某智能家居平台用此方案,确保即使 10 万台设备同时上报数据,系统仍按 500 条/秒的速率稳定处理。


但突发流量会导致队列积压,就像用漏斗倒奶茶——珍珠容易卡住。


适用场景:IoT 设备控制指令下发、支付渠道限额等需要严格恒定速率的场景


1.4 令牌桶算法


核心原理:以固定速率生成令牌,请求需获取令牌才能执行。


突发流量可消耗桶内积攒的令牌。



具体实现如下:


// Guava RateLimiter高级用法RateLimiter limiter = RateLimiter.create(10.0, 1, TimeUnit.SECONDS); // 初始预热limiter.acquire(5); // 尝试获取5个令牌
// 动态调整速率(需反射实现)Field field = RateLimiter.class.getDeclaredField("tokens");field.setAccessible(true);AtomicDouble tokens = (AtomicDouble) field.get(limiter);tokens.set(20); // 突发时注入20个令牌
复制代码


实战案例:某视频平台用此方案应对热点事件:平时限制 10 万 QPS,突发时允许 3 秒内超限 50%,既防雪崩又保用户体验。


动态特性

  • 正常时限制 QPS

  • 突发时允许透支

  • 持续突发会耗尽令牌


2 生产环境实战


2.1 网关层分布式限流


某电商双 11 方案:通过 Redis+Lua 实现分布式计数,配合 Nginx 本地缓存,在网关层拦截了 83%的恶意请求。



2.2 自适应熔断机制


我们还需要自适应熔断机制。


某社交平台用此方案,在突发流量时自动将限流阈值从 5 万降到 3 万,等系统恢复后再逐步提升。



3 避坑指南与性能优化


3.1 致命误区


在数据库连接池前做限流!


某公司曾因此导致连接泄漏,最终撑爆数据库。


正确做法应遵循熔断三原则


  1. 快速失败:在入口层拦截无效请求

  2. 动态降级:核心服务保留最小资源

  3. 自动恢复:熔断后渐进式放量


3.2 性能优化


某金融系统通过 JMH 测试发现,使用 LongAdder 替代 AtomicLong,限流吞吐量提升 220%。



性能优化手段:减少 CAS 竞争 和 分段锁基座。


总结


上面列举了工作中最常用的 4 种限流方案。


对于不同的业务场景,我们需要选择不同的限流方案。



限流的黄金法则如下:



记住:好的限流方案就像高铁闸机——既保证通行效率,又守住安全底线。


文章转载自:苏三说技术

原文链接:https://www.cnblogs.com/12lisu/p/18815508

体验地址:http://www.jnpfsoft.com/?from=001YH

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
瞧瞧别人家的限流,那叫一个优雅!_Java_不在线第一只蜗牛_InfoQ写作社区