博客
关于我
RateLimiter源码解析
阅读量:414 次
发布时间:2019-03-06

本文共 5343 字,大约阅读时间需要 17 分钟。

Guava库中的RateLimiter是一个强大的限流器,采用令牌桶算法来控制消息消费速度,防止下游服务因为消息过载而崩溃。RateLimiter的核心实现分为两类:基础限流器SmoothBursty和带有预热功能的SmoothWarmingUp。以下从源码角度详细解析其工作原理。

RateLimiter的基本结构

RateLimiter作为顶层类,主要通过静态工厂方法创建实例。它内部维护两个关键字段:

  • stopwatch:用于计算时间间隔,实现令牌的获取与等待。
  • mutexDoNotUseDirectly:用于线程同步,防止并发访问带来的竞态条件。

创建实例的工厂方法

RateLimiter提供了三种工厂方法:

  • create(double permitsPerSecond):创建基础限流器SmoothBursty。
  • create(double permitsPerSecond, Duration warmupPeriod):创建带预热功能的限流器SmoothWarmingUp。
  • create(double permitsPerSecond, long warmupPeriod, TimeUnit unit):同上,提供统一的接口。
  • 其中,permitsPerSecond表示每秒产生的令牌数量,warmupPeriod是预热阶段的时间,用于从慢速到快速平滑过渡。

    SmoothBursty: 基础限流器

    SmoothBursty是RateLimiter的基础实现,主要用于平稳化消息的生产速率。其内部维护了以下字段:

    • storedPermits:当前桶中的令牌数量。
    • maxPermits:桶的最大容量,通常等于permitsPerSecond。
    • stableIntervalMicros:稳定速率下生产一个令牌所需的时间,单位为微秒。

    构造过程

    SmoothBursty的构造过程如下:

    public static RateLimiter create(double permitsPerSecond) {    return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());}static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {    RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);    rateLimiter.setRate(permitsPerSecond);    return rateLimiter;}

    构造时,传入stopwatch和maxBurstSeconds(默认为1秒),maxBurstSeconds限制了桶中令牌的最大持有时间。

    setRate方法

    setRate用于调整限流器的速率,实现如下:

    public final void setRate(double permitsPerSecond) {    checkArgument(permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");    synchronized (mutex()) {        doSetRate(permitsPerSecond, stopwatch.readMicros());    }}

    setRate首先校验参数,确保permitsPerSecond为正值。然后在同步块中执行doSetRate,调整稳定速率下的令牌生产间隔。

    令牌获取逻辑

    SmoothBursty的acquire方法用于获取令牌:

    public double acquire() {    return acquire(1);}public double acquire(int permits) {    long microsToWait = reserve(permits);    stopwatch.sleepMicrosUninterruptibly(microsToWait);    return 1.0 * microsToWait / SECONDS.toMicros(1L);}

    acquire方法通过reserve获取所需的令牌数量,若桶中有足够的令牌,直接返回;否则,线程阻塞,直到获取到令牌。

    reserve方法的核心逻辑:

    final long reserve(int permits) {    checkPermits(permits);    synchronized (mutex()) {        return reserveAndGetWaitLength(permits, stopwatch.readMicros());    }}final long reserveAndGetWaitLength(int permits, long nowMicros) {    long momentAvailable = reserveEarliestAvailable(permits, nowMicros);    return max(momentAvailable - nowMicros, 0);}final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {    resync(nowMicros);    long returnValue = nextFreeTicketMicros;    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);    double freshPermits = requiredPermits - storedPermitsToSpend;    long waitMicros =         storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)         + (long) (freshPermits * stableIntervalMicros);    this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);    this.storedPermits -= storedPermitsToSpend;    return returnValue;}

    resync方法用于同步当前时间,更新桶中的令牌数量和下次获取时间。reserveEarliestAvailable方法根据当前时间决定是否需要等待,通过计算桶中令牌的存储情况,决定下一次获取令牌的时间。

    SmoothWarmingUp: 带预热功能的限流器

    SmoothWarmingUp在SmoothBursty的基础上增加了预热功能,以平滑地从低速率向高速率过渡。其内部维护了额外的字段:

    • warmupPeriodMicros:预热阶段的总时间。
    • coldFactor:冷启动时的速率因子(默认为3.0)。

    构造过程

    SmoothWarmingUp的构造过程如下:

    static RateLimiter create(    double permitsPerSecond,     long warmupPeriod,     TimeUnit unit,     double coldFactor,    SleepingStopwatch stopwatch) {    RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit, coldFactor);    rateLimiter.setRate(permitsPerSecond);    return rateLimiter;}

    构造时,传入预热时间、时间单位、冷启动因子和stopwatch。

    setRate方法

    setRate方法与SmoothBursty类似,调整限流器的速率:

    public final void setRate(double permitsPerSecond) {    checkArgument(permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");    synchronized (mutex()) {        doSetRate(permitsPerSecond, stopwatch.readMicros());    }}

    doSetRate方法初始化稳定速率下的令牌生产间隔,并更新预热相关参数。

    预热逻辑

    SmoothWarmingUp在resync方法中处理预热逻辑:

    void resync(long nowMicros) {    if (nowMicros > nextFreeTicketMicros) {        double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();        storedPermits = min(maxPermits, storedPermits + newPermits);        nextFreeTicketMicros = nowMicros;    }}

    coolDownIntervalMicros方法返回预热阶段的冷却间隔:

    double coolDownIntervalMicros() {    return warmupPeriodMicros / maxPermits;}

    与SmoothBursty不同,SmoothWarmingUp的最大容量maxPermits可以大于permitsPerSecond,允许预热阶段的灵活配置。

    令牌获取逻辑

    SmoothWarmingUp的acquire方法与SmoothBursty类似,但reserveEarliestAvailable方法增加了对预热阶段的处理:

    final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {    resync(nowMicros);    long returnValue = nextFreeTicketMicros;    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);    double freshPermits = requiredPermits - storedPermitsToSpend;    long waitMicros =         storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)         + (long) (freshPermits * stableIntervalMicros);    this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);    this.storedPermits -= storedPermitsToSpend;    return returnValue;}

    storedPermitsToWaitTime方法根据当前桶中的令牌数量和所需的令牌数量,计算需要等待的时间。该方法考虑了预热阶段的梯形区间和稳定速率区间,确保高频率的消息处理不会超过限流器的容量。

    总结

    RateLimiter通过两种不同的实现策略(SmoothBursty和SmoothWarmingUp)满足不同的应用场景。SmoothBursty适合高频率的消息处理,通过简单的桶模型直接控制消息速率。而SmoothWarmingUp则在低频率下提供额外的预热阶段,平滑地从低速率过渡到高速率,避免因突发高负载而导致系统压力过大。两种实现都通过令牌桶算法,确保消息消费的均匀性和系统的稳定性。

    转载地址:http://xlkkz.baihongyu.com/

    你可能感兴趣的文章
    mysql自增id超大问题查询
    查看>>
    MySQL集群解决方案(4):负载均衡
    查看>>
    MySQL高级-视图
    查看>>
    nacos集群搭建
    查看>>
    Navicat for MySQL 查看BLOB字段内容
    查看>>
    Neo4j的安装与使用
    查看>>
    Neo4j(2):环境搭建
    查看>>
    nessus快速安装使用指南(非常详细)零基础入门到精通,收藏这一篇就够了
    查看>>
    Nessus漏洞扫描教程之配置Nessus
    查看>>
    Nest.js 6.0.0 正式版发布,基于 TypeScript 的 Node.js 框架
    查看>>
    Netpas:不一样的SD-WAN+ 保障网络通讯品质
    查看>>
    netsh advfirewall
    查看>>
    Netty WebSocket客户端
    查看>>
    Netty 异步任务调度与异步线程池
    查看>>
    Netty中集成Protobuf实现Java对象数据传递
    查看>>
    Netty工作笔记0006---NIO的Buffer说明
    查看>>
    Netty工作笔记0011---Channel应用案例2
    查看>>
    Netty工作笔记0013---Channel应用案例4Copy图片
    查看>>
    Netty工作笔记0014---Buffer类型化和只读
    查看>>
    Netty工作笔记0020---Selectionkey在NIO体系
    查看>>