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 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.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 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.asList(getRawName(), getChannelName()), permits);
    }

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

四、结论

回答最初的问题:

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

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

你可能感兴趣的:(java,java,redisson)