某些场景下需要考虑信号量机制,比如控制整体的并发量,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、过期时间。
里面有几个关键方法,解析如下:
即设置许可证数量,它的关键实现如下:
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.
即key存在,且不为数字0时,注意这里的数字 0,这个保证了即使许可证被用完了,也不会被重置。
申请一个许可证,15秒超时,这里源码比较长,就不贴了,基本思路是:
1、删除timeout中已过期的信号量数据
2、有过期的,增加可用信号量数量
3、如果可用信号量大于等于需要申请的信号量,可用信号量-1,timeout中增加申请的信号量数据
删除timeout中该信号量数据,增加可申请信号量
调整总信号量数,正数增加,负数减少
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.
如果已经初始化,直接修改剩余信号量数据为:剩余可用信号量+增加/减少的信号量。
回答最初的问题:
1、重复调用trySetPermits,不会对已有信号量设置造成影响,因为string类型,一旦初始化,则 value == 0 这个条件永远不可能满足
2、修改信号量时,只能手动执行一次,可以使用addPermits方法或者让运维直接使用 incrby/decrby 指令修改剩余信号量值,增加/减少总信号量