Redisson 分布式限流器 RRateLimiter 的使用及原理

这篇具有很好参考价值的文章主要介绍了Redisson 分布式限流器 RRateLimiter 的使用及原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


一、基本使用

1.1 创建限流器

/**
 * Returns rate limiter instance by name
 * 
 * @param name of rate limiter
 * @return RateLimiter object
 */
RRateLimiter getRateLimiter(String name);
/**
 * Initializes RateLimiter's state and stores config to Redis server.
 * 
 * @param mode - rate mode
 * @param rate - rate
 * @param rateInterval - rate time interval
 * @param rateIntervalUnit - rate time interval unit
 * @return true if rate was set and false otherwise
 */
boolean trySetRate(RateType mode, long rate, long rateInterval, RateIntervalUnit rateIntervalUnit);

trySetRate 用于设置限流参数。其中 RateType 包含 OVERALLPER_CLIENT 两个枚举常量,分别表示全局限流和单机限流。后面三个参数表明了令牌的生成速率,即每 rateInterval 生成 rate 个令牌,rateIntervalUnitrateInterval 的时间单位。

1.2 获取令牌

/**
 * Acquires a specified permits from this RateLimiter, 
 * blocking until one is available.
 *
 * Acquires the given number of permits, if they are available 
 * and returns immediately, reducing the number of available permits 
 * by the given amount.
 * 
 * @param permits the number of permits to acquire
 */
void acquire(long permits);

/**
 * Acquires the given number of permits only if all are available
 * within the given waiting time.
 *
 * Acquires the given number of permits, if all are available and returns immediately,
 * with the value true, reducing the number of available permits by one.
 *
 * If no permit is available then the current thread becomes
 * disabled for thread scheduling purposes and lies dormant until
 * the specified waiting time elapses.
 *
 * If a permits is acquired then the value true is returned.
 *
 * If the specified waiting time elapses then the value false
 * is returned.  If the time is less than or equal to zero, the method
 * will not wait at all.
 *
 * @param permits amount
 * @param timeout the maximum time to wait for a permit
 * @param unit the time unit of the timeout argument
 * @return true if a permit was acquired and false
 *         if the waiting time elapsed before a permit was acquired
 */
boolean tryAcquire(long permits, long timeout, TimeUnit unit);

acquiretryAcquire 均可用于获取指定数量的令牌,不过 acquire 会阻塞等待,而 tryAcquire 会等待 timeout 时间,如果仍然没有获得指定数量的令牌直接返回 false

1.3 使用示例

@Slf4j
@SpringBootTest
class RateLimiterTest {
    
    @Autowired
    private RedissonClient redissonClient;
    
    private static final int threadCount = 10;

    @Test
    void test() throws InterruptedException {
        RRateLimiter rateLimiter = redissonClient.getRateLimiter("my_limiter");
        rateLimiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);

        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                rateLimiter.tryAcquire(5, 3, TimeUnit.SECONDS);
                latch.countDown();
                log.info("latch count {}", latch.getCount());
            }).start();
        }
        
        latch.await();
    }
}
2024-01-16 20:14:27 INFO  [Thread-2] atreus.ink.rate.RateLimiterTest : latch count 9
2024-01-16 20:14:27 INFO  [Thread-3] atreus.ink.rate.RateLimiterTest : latch count 8
2024-01-16 20:14:28 INFO  [Thread-1] atreus.ink.rate.RateLimiterTest : latch count 7
2024-01-16 20:14:29 INFO  [Thread-10] atreus.ink.rate.RateLimiterTest : latch count 6
2024-01-16 20:14:29 INFO  [Thread-8] atreus.ink.rate.RateLimiterTest : latch count 5
2024-01-16 20:14:30 INFO  [Thread-5] atreus.ink.rate.RateLimiterTest : latch count 4
2024-01-16 20:14:30 INFO  [Thread-4] atreus.ink.rate.RateLimiterTest : latch count 3
2024-01-16 20:14:30 INFO  [Thread-6] atreus.ink.rate.RateLimiterTest : latch count 2
2024-01-16 20:14:30 INFO  [Thread-7] atreus.ink.rate.RateLimiterTest : latch count 1
2024-01-16 20:14:30 INFO  [Thread-9] atreus.ink.rate.RateLimiterTest : latch count 0

二、实现原理

Redisson 的 RRateLimiter 基于令牌桶实现,令牌桶的主要特点如下:

  • 令牌以固定速率生成。
  • 生成的令牌放入令牌桶中存放,如果令牌桶满了则多余的令牌会直接丢弃,当请求到达时,会尝试从令牌桶中取令牌,取到了令牌的请求可以执行。
  • 如果桶空了,那么尝试取令牌的请求会被直接丢弃。

RRateLimiter 在创建限流器时通过下面 Lua 脚本设置限流器的相关参数:

redis.call('hsetnx', KEYS[1], 'rate', ARGV[1]);
redis.call('hsetnx', KEYS[1], 'interval', ARGV[2]);
return redis.call('hsetnx', KEYS[1], 'type', ARGV[3]);

而获取令牌则是通过以下的 Lua 脚本实现:

-- 请求参数示例
-- KEYS[1] my_limiter
-- KEYS[2] {my_limiter}:value
-- KEYS[4] {my_limiter}:permits
-- ARGV[1] 3 本次请求的令牌数
-- ARGV[2] 1705396021850 System.currentTimeMillis()
-- ARGV[3] 6966135962453115904 ThreadLocalRandom.current().nextLong()

-- 读取 RRateLimiter.trySetRate 中配置的限流器信息
local rate = redis.call('hget', KEYS[1], 'rate');  -- 10 一个时间窗口内产生的令牌数
local interval = redis.call('hget', KEYS[1], 'interval');  -- 1000 一个时间窗口对应的毫秒数
local type = redis.call('hget', KEYS[1], 'type');  -- 0 全局限流
assert(rate ~= false and interval ~= false and type ~= false, 'RateLimiter is not initialized')

local valueName = KEYS[2];  -- {my_limiter}:value 当前可用令牌数字符串的 key
local permitsName = KEYS[4];  -- {my_limiter}:permits 授权记录有序集合的 key

-- 单机限流配置 无需考虑
if type == '1' then
    valueName = KEYS[3];
    permitsName = KEYS[5];
end;

-- 查询当前可用的令牌数 查询失败表明是首次请求令牌
local currentValue = redis.call('get', valueName);
if currentValue == false then -- 首次请求令牌
    -- 单次请求的令牌数不能超过一个时间窗口内产生的令牌数
    assert(tonumber(rate) >= tonumber(ARGV[1]), 'Requested permits amount could not exceed defined rate');
    
    -- 更新当前可用令牌数以及令牌授权记录 {my_limiter}:permits
    -- set {my_limiter}:permits 10
    redis.call('set', valueName, rate);
    -- zadd {my_limiter}:permits 1705396021850 6966135962453115904_1
    redis.call('zadd', permitsName, ARGV[2], struct.pack('fI', ARGV[3], ARGV[1]));
    -- decrby {my_limiter}:permits 3
    redis.call('decrby', valueName, ARGV[1]);
    return nil;
else -- 再次请求令牌
    -- 查询可以回收的令牌对应的授权记录 即一个时间窗口前的所有授权记录且包括一个时间窗口前这一时刻
    -- 旧令牌回收的本质是新令牌的加入 如果一个令牌是在一个时间窗口前被分配的 那经过一个时间窗口后这个空出的位置应该已经由新令牌填充
    -- zrangebyscore {my_limiter}:permits 0 1705396020850
    local expiredValues = redis.call('zrangebyscore', permitsName, 0, tonumber(ARGV[2]) - interval); -- [1936135962853113704_2, 536135765023123704_5]
    
    -- 统计可以回收的令牌数
    local released = 0;
    for i, v in ipairs(expiredValues) do
        local random, permits = struct.unpack('fI', v);
        -- released = released + 2
        -- released = released + 5
        released = released + permits;
    end;

    -- 删除授权记录并回收令牌
    if released > 0 then
        -- zrem {my_limiter}:permits 1936135962853113704_2 536135765023123704_5
        redis.call('zrem', permitsName, unpack(expiredValues));
        currentValue = tonumber(currentValue) + released;
        -- incrby {my_limiter}:value 7
        redis.call('set', valueName, currentValue);
    end;

    if tonumber(currentValue) < tonumber(ARGV[1]) then
        -- 如果回收后可用令牌数仍然不足 返回需要等待的时间
        -- zrangebyscore {my_limiter}:permits (1705396020850 1705396021850 withscores limit 0 1
        local nearest = redis.call('zrangebyscore', permitsName, '(' .. (tonumber(ARGV[2]) - interval), tonumber(ARGV[2]), 'withscores', 'limit', 0, 1);
        local random, permits = struct.unpack('fI', nearest[1]);
        -- 1705396021650 - 1705396021850 + 1000 = 800
        return tonumber(nearest[2]) - (tonumber(ARGV[2]) - interval);
    else
        
        redis.call('zadd', permitsName, ARGV[2], struct.pack('fI', ARGV[3], ARGV[1]));
        redis.call('decrby', valueName, ARGV[1]);
        return nil;
    end;
end;

参考:

https://github.com/oneone1995/blog/issues/13
https://www.infoq.cn/article/Qg2tX8fyw5Vt-f3HH673文章来源地址https://www.toymoban.com/news/detail-809093.html

到了这里,关于Redisson 分布式限流器 RRateLimiter 的使用及原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redisson分布式锁

    Redisson是一个在Redis的基础上实现的Java驻内存数据网格,可参考Redisson官方文档使用,它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。 简单来说,就是直接 RLock rlock = redissonClient.getLock(“lock”); 获取到锁,然后lock()和unlock()即可。 并发达到了660,比之

    2024年02月16日
    浏览(44)
  • Redisson—分布式对象

    每个Redisson对象实例都会有一个与之对应的Redis数据实例,可以通过调用getName方法来取得Redis数据实例的名称(key)。 所有与Redis key相关的操作都归纳在RKeys这个接口里: Redisson的分布式RBucketJava对象是一种通用对象桶可以用来存放任类型的对象。 除了同步接口外,还提供了异

    2024年02月07日
    浏览(47)
  • Redisson 分布式锁

    Redis是基础客户端库,可用于执行基本操作。 Redisson是基于Redis的Java客户端,提供高级功能如分布式锁、分布式集合和分布式对象。 Redisson提供更友好的API,支持异步和响应式编程,提供内置线程安全和失败重试机制。 实现步骤: 通过导入坐标和配置,注入RedissonClient对象之

    2024年02月11日
    浏览(56)
  • 运用分布式锁 redisson

    导入依赖 根据springboot版本不同自行选择版本 dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.15.3/version /dependency 创建客户端

    2024年01月19日
    浏览(46)
  • 【Redisson】Redisson--分布式远程服务(Remote Service)

    Redisson系列文章: 【Redisson】Redisson–基础入门 【Redisson】Redisson–布隆(Bloom Filter)过滤器 【Redisson】Redisson–分布式锁的使用(推荐使用) 【分布式锁】Redisson分布式锁底层原理 【Redisson】Redisson–限流器 当前有两台服务器连接的是同一个Redisson中间件,这两台服务器叫它们

    2024年02月13日
    浏览(47)
  • Redisson实现分布式锁示例

    可以下载redis desktop manager软件来查看redis里面存放的东西 红色框内的TTL值就是过期时间,默认-1,表示永不过期,指定过期时间后就变成你指定的值了。 上面的方法,我们让线程睡眠60S,代表我们的业务执行时间,在调用这个方法时,我们可以在 redis desktop manager软件上实时查

    2024年02月12日
    浏览(49)
  • SpringBoot+Redisson分布式锁

    org.redisson.config.Config类是Redisson框架中用于配置Redisson客户端的类。以下是一些常用的配置项: codec(编码) :默认值是org.redisson.codec.JsonJacksonCodec,用于定义与Redis交互时使用的编解码器。 useSingleServer :设置为true时,将使用单节点模式进行连接。 useMasterSlave :设置为true时,

    2024年01月19日
    浏览(47)
  • redis的分布式事务-redisson

    Redisson分布式锁是一种基于redis实现的分布式锁,它利用redis的setnx命令实现分布式锁的互斥访问。同时还支持锁的自动续期功能,可以避免因为某个进程崩溃或者网络故障导致锁无法释放的情况。 只要线程一加锁成功,就会启动一个watch dog看门狗,它是一个后台线程,会每隔

    2024年02月13日
    浏览(48)
  • Redisson分布式锁 原理&源码 分析

    获取锁的Lua脚本: 释放锁的Lua脚本: tryLock()底层代码分析 time :剩余的等待重试时间 ttl :现被持有的锁的剩余有效时间 计算尝试获取锁所消耗的时间,然后再计算出等待获取锁的剩余时间time, 如果time=0,则不再重试了直接返回获取锁失败, 如果time0,则通过subscribe去

    2024年02月11日
    浏览(66)
  • Redis实战——Redisson分布式锁

    目录 1 基于Redis中setnx方法的分布式锁的问题 2 Redisson         2.1 什么是Redisson         2.2 Redisson实现分布式锁快速入门         2.3 Redisson 可重入锁原理                 什么是可重入锁?                 Redisson中又是如何实现的呢?         2

    2024年02月15日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包