当前位置: 首页 > backend >正文

限流系列:resilience4j-ratelimiter

目录

令牌桶算法

Resilience4j-Ratelimiter

数据模型

示例

​​​​​​​突刺问题

大致流程

decorateCheckedSupplier

​​​​​​​waitForPermission

​​​​​​​acquirePermission

​​​​​​​updateStateWithBackOff

​​​​​​​calculateNextState


令牌桶算法

       令牌桶算法就是以固定速率生成令牌放入桶中,每个请求都需要从桶中获取令牌,没有获取到令牌的请求会被阻塞限流(桶中的令牌不够的时候);只要能从桶里取出令牌就能通过。

       当令牌消耗速度小于生成的速度时,令牌桶内就会预存这些未消耗的令牌(直到桶的上限);当有突发流量进来时,可以直接从桶中取出令牌,而不会被限流,从而支持突发流量的快速处理。

Resilience4j-Ratelimiter

       不管是令牌桶算法还是漏桶算法都可以用延迟计算的方式来实现,延迟计算指的是不需要单独的线程来定时生成令牌或者从漏桶中定时取出请求,而是由调用限流器的线程自己去计算是否有足够的令牌以及需要sleep的时间,延迟计算的方式可以节省一个线程资源。

       resilience4j-ratelimiter实现允许突发流量的方式是在每个周期开始时补充全部的令牌(这是与guava rateLimiter差别最大的地方,详见《限流系列:guava rateLimiter》),使得在周期开始时可以处理最多limitForPeriod个请求,形成突发。

       同时需要注意,与传统的令牌桶算法不同,这里的令牌不会累积,而是每个周期重置,因此突发流量的大小受限于limitForPeriod。而传统令牌桶可能允许累积未使用的令牌,从而支持更大的突发。

​​​​​​​数据模型

字段

名称

作用

limitForPeriod

在每个周期(period)内允许的最大请求数量。

控制在指定时间窗口内的请求上限,防止短时间内请求过多。

limitRefreshPeriod

时间窗口(周期)的长度,即每隔多长时间重置一次请求计数。

定义了速率限制的周期,每个周期开始时,请求计数会被重置为 0。

​​​​​​​示例

RateLimiterConfig config = RateLimiterConfig.custom()
    .limitForPeriod(10)                        // 每秒 10 个令牌
    .limitRefreshPeriod(Duration.ofMillis(1000))   // 刷新周期
    .timeoutDuration(Duration.ofMillis(0))       // 立即拒绝超限请求
    .build();
RateLimiter rateLimiter = RateLimiter.of("myRateLimiter", config);
CheckedFunction0<String> restrictedCall = RateLimiter.decorateCheckedSupplier(
        rateLimiter,
        () -> "success" // 被保护的业务方法
);
Try<String> result = Try.of(restrictedCall)

    .recover(throwable -> {

        log.warn("请求被限流拒绝");

        return "";

    });

log.info("result : {}", result.get());

在这里,配置了:

       limitForPeriod = 10

       limitRefreshPeriod = 1秒

这意味着:

       每秒(limitRefreshPeriod)最多可以处理 10 个请求(limitForPeriod)。

       每过 1 秒,请求计数器会重置,新的 1 秒周期内又可以处理最多 10 个请求。

​​​​​​​突刺问题

       综上所述,resilience4j-ratelimiter 会存在突刺问题,尤其是在使用固定窗口策略时。通过采用滑动窗口策略,可以有效减轻突刺问题的影响,实现更平滑的速率限制。

       使用滑动窗口策略配置示例如下:

slidingWindow:

enabled: true

    intervalInMilliseconds: 1000  // 滑动窗口的总时间间隔为 1 秒

    numberOfBuckets: 10       // 将 1 秒分成 10 个桶,每个桶代表 100 毫秒

       这样,速率限制会更加平滑,每个 100 毫秒内的请求数量会被单独统计,减少了突刺的影响。

       接下来我们探究一下使用固定窗口策略时的代码处理流程,采用滑动窗口策略的探究等下一次再进行。

       使用固定窗口策略时的代码处理流程如下所示:

大致流程

        点击示例里的decorateCheckedSupplier()方法,如下所示:

decorateCheckedSupplier

static <T> CheckedFunction0<T> decorateCheckedSupplier(RateLimiter rateLimiter, CheckedFunction0<T> supplier) {

        return decorateCheckedSupplier(rateLimiter, 1, supplier);

}

static <T> CheckedFunction0<T> decorateCheckedSupplier(RateLimiter rateLimiter, int permits,

        CheckedFunction0<T> supplier) {

        return () -> {

            waitForPermission(rateLimiter, permits);

            try {

                T result = supplier.apply();

                rateLimiter.onResult(result);

                return result;

            } catch (Exception exception) {

                rateLimiter.onError(exception);

                throw exception;

            }

        };

}

       点击waitForPermission()方法,如下所示:

​​​​​​​waitForPermission

static void waitForPermission(final RateLimiter rateLimiter, int permits) {

        boolean permission = rateLimiter.acquirePermission(permits);

        if (Thread.currentThread().isInterrupted()) {

            throw new AcquirePermissionCancelledException();

        }

        if (!permission) {

            throw RequestNotPermitted.createRequestNotPermitted(rateLimiter);

        }

    }

        点击acquirePermission()方法,如下所示:

​​​​​​​acquirePermission

public boolean acquirePermission(final int permits) {

        long timeoutInNanos = state.get().config.getTimeoutDuration().toNanos();

        State modifiedState = updateStateWithBackOff(permits, timeoutInNanos);

        boolean result = waitForPermissionIfNecessary(timeoutInNanos, modifiedState.nanosToWait);

        publishRateLimiterAcquisitionEvent(result, permits);

        return result;

    }

        点击updateStateWithBackOff()方法,如下所示:

​​​​​​​updateStateWithBackOff

private State updateStateWithBackOff(final int permits, final long timeoutInNanos) {

        AtomicRateLimiter.State prev;

        AtomicRateLimiter.State next;

        do {

            prev = state.get();

            next = calculateNextState(permits, timeoutInNanos, prev);

        } while (!compareAndSet(prev, next));

        return next;

    }

        点击calculateNextState()方法,如下所示:

​​​​​​​calculateNextState

private State calculateNextState(final int permits, final long timeoutInNanos,

                                     final State activeState) {

        long cyclePeriodInNanos = activeState.config.getLimitRefreshPeriod().toNanos();

        int permissionsPerCycle = activeState.config.getLimitForPeriod();

        long currentNanos = currentNanoTime();

        long currentCycle = currentNanos / cyclePeriodInNanos;

        long nextCycle = activeState.activeCycle;

        int nextPermissions = activeState.activePermissions;

        if (nextCycle != currentCycle) {

            long elapsedCycles = currentCycle - nextCycle;

            long accumulatedPermissions = elapsedCycles * permissionsPerCycle;

            nextCycle = currentCycle;

            nextPermissions = (int) min(nextPermissions + accumulatedPermissions,

                permissionsPerCycle);

        }

        long nextNanosToWait = nanosToWaitForPermission(permits, cyclePeriodInNanos, permissionsPerCycle, nextPermissions, currentNanos,currentCycle);

        State nextState = reservePermissions(activeState.config, permits, timeoutInNanos, nextCycle,nextPermissions, nextNanosToWait);

        return nextState;

}

在这里:

  1. 获取配置信息:时间窗口的长度
  2. 获取配置信息:每个周期内允许的最大请求数量permissionsPerCycle
  3. 计算了自限流器AtomicRateLimiter实例化以来经过的时长currentNanos,即当前时间减去限流器AtomicRateLimiter实例化的时间
  4. 计算了当前时间所属周期currentCycle
  5. 获取上一个请求所属周期activeCycle
  6. 获取上一个请求令牌数量activePermissions
  7. 计算当前周期与上个请求所属周期的差值
  8. 计算上个请求所属周期到当前周期积累的令牌数量
  9. 对比积累的令牌数量与每个周期内允许的最大请求数量,将比较小的值作为当前周期的令牌数量。

http://www.xdnf.cn/news/9129.html

相关文章:

  • Linux环境下基于Docker安装 PostgreSQL数据库并配置 pgvector
  • [onnx 学习记录] 包包含的主要内容
  • Redisson分布式锁原理
  • 提升系统性能:Windows绿色版管理工具的实用指南
  • 红海云荣膺2025人力资源科技影响力品牌30强
  • IPD流程落地:项目任务书Charter开发
  • (2025.05)ubuntu20.04运行Mono-gs记录
  • android实现使用RecyclerView详细
  • 大模型微调(4):使用 AutoClass 管理 Tokenizer 和 Model
  • 航电系统之协同坐标技术篇
  • iOS 响应者链详解
  • 开发规范-Restful风格、Apifox安装与使用
  • 一、奋斗类(事业奋斗/梦想实现)
  • 三栏布局实现
  • 56页 @《人工智能生命体 新启点》中國龍 原创连载
  • 修改 K8S Service 资源类型 NodePort 的端口范围
  • Java Swing 自定义JOptionPane
  • Python面试1
  • 传输线上的信号速度与阻抗无关,主要由频率决定
  • leetcode:2160. 拆分数位后四位数字的最小和(python3解法,数学相关算法题)
  • OceanBase数据库全面解析(数据定义篇DDL)
  • numpy与matplotlib学习——数据可视化入门
  • 2025密云马拉松复盘
  • 如何实现 C/C++ 与 Python 的通信
  • sqli-labs第二十七关——Trick with selectunion
  • AI时代新词-AI芯片(AI - Specific Chip)
  • 大模型量化原理
  • vue + ant-design + xlsx 实现表格导出进度提示功能
  • 【JSON-To-Video】JSON转视频教程,快速添加配音与字幕!
  • 【Java】异常处理