Redisson PermitExpirableSemaphore 剖析

某些场景下需要考虑信号量机制,比如控制整体的并发量,redisson提供了在分布式环境下的解决方案,即 PermitExpirableSemaphore。

记录一下,主要是是否会重复初始化导致重置可用信号量、如何变更总可用信号量。

使用非常简单,以下是官方的使用示例

RPermitExpirableSemaphore semaphore = redisson.getPermitExpirableSemaphore("mySemaphore");

semaphore.trySetPermits(23);

// acquire permit
String id = semaphore.acquire();

// or acquire permit with lease time in 10 seconds
String id = semaphore.acquire(10, TimeUnit.SECONDS);

// or try to acquire permit
String id = semaphore.tryAcquire();

// or try to acquire permit or wait up to 15 seconds
String id = semaphore.tryAcquire(15, TimeUnit.SECONDS);

// or try to acquire permit with least time 15 seconds or wait up to 10 seconds
String id = semaphore.tryAcquire(10, 15, TimeUnit.SECONDS);
if (id != null) {
   try {
     ...
   } finally {
       semaphore.release(id);
   }
}

实际在redis中,存储了两个key数据:

     1、初始化时"传入名称",string类型,存储可用信号量

      2、 "{传入名称}:timeout",zset类型,存储已申请的信号量id、过期时间。

里面有几个关键方法,解析如下:

一、semaphore.trySetPermits(23)

即设置许可证数量,它的关键实现如下:

    public RFuture<Boolean> trySetPermitsAsync(int permits) {
        return commandExecutor.evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "local value = redis.call('get', KEYS[1]); " +
                "if (value == false or value == 0) then "
                    + "redis.call('set', KEYS[1], ARGV[1]); "
                    + "redis.call('publish', KEYS[2], ARGV[1]); "
                    + "return 1;"
                + "end;"
                + "return 0;",
                Arrays.<Object>asList(getRawName(), getChannelName()), permits);
    }

即key存在,且不为数字0时,注意这里的数字 0,这个保证了即使许可证被用完了,也不会被重置。

二、semaphore.tryAcquire(15, TimeUnit.SECONDS)

申请一个许可证,15秒超时,这里源码比较长,就不贴了,基本思路是:

1、删除timeout中已过期的信号量数据

2、有过期的,增加可用信号量数量

3、如果可用信号量大于等于需要申请的信号量,可用信号量-1,timeout中增加申请的信号量数据

三、semaphore.release(id)

删除timeout中该信号量数据,增加可申请信号量

四、semaphore.addPermits(permits)

调整总信号量数,正数增加,负数减少

    public RFuture<Void> addPermitsAsync(int permits) {
        return commandExecutor.evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_VOID,
                "local value = redis.call('get', KEYS[1]); " +
                "if (value == false) then "
                  + "value = 0;"
              + "end;"
              + "redis.call('set', KEYS[1], tonumber(value) + tonumber(ARGV[1])); "
              + "if tonumber(ARGV[1]) > 0 then "
                  + "redis.call('publish', KEYS[2], ARGV[1]); "
              + "end;",
                Arrays.<Object>asList(getRawName(), getChannelName()), permits);
    }

如果已经初始化,直接修改剩余信号量数据为:剩余可用信号量+增加/减少的信号量。

四、结论

回答最初的问题:

1、重复调用trySetPermits,不会对已有信号量设置造成影响,因为string类型,一旦初始化,则 value == 0 这个条件永远不可能满足

2、修改信号量时,只能手动执行一次,可以使用addPermits方法或者让运维直接使用 incrby/decrby 指令修改剩余信号量值,增加/减少总信号量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值