RPermitExpirableSemaphore是什么
首先,RPermitExpirableSemaphore 是出自于Redisson,Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid),Redisson也是redis官方推荐的,比较常用的是它的分布式锁。
Redisson中文文档:
https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95
RPermitExpirableSemaphore ,可过期性信号量
接口文档:
https://www.javadoc.io/doc/org.redisson/redisson/latest/index.html
用法
举个栗子
创建RedissonClient 单机版
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
// 单机模式
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
;
return Redisson.create(config);
}
一次完整的获得许可,释放许可
public void test() throws Exception {
//通过redissonClient创造一个key为xxx的信号量
RPermitExpirableSemaphore semaphore = redissonClient.getPermitExpirableSemaphore("xxx");
//给信号量设置数量为5,用在限流中就是只允许5次请求
while (!semaphore.trySetPermits(5)) ;
//tryAcquire 的第一个参数是waittime,尝试获得许可证的最大等待时间,超过这个时间则返回null
//第二个参数是许可证的过期时间,也是精华所在,即使发生宕机,jvm崩溃等情况,也不用担心,信号量过期会自动释放
//成功之后会获得一个许可证ID,是在获取期间每次生成的128位唯一随机标识符
String permitId = semaphore.tryAcquire(1, 5, TimeUnit.SECONDS);
System.out.println(permitId);
//释放许可证还需要之前获得permitId
semaphore.release(permitId);
}
semaphore.trySetPermits() 中 lua脚本分析
@Override
public RFuture<Boolean> trySetPermitsAsync(int permits) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"local value = redis.call('get', KEYS[1]); " +
//value==0或者false,都是代表没有这个信号量,进入创建信号量的逻辑
"if (value == false or value == 0) then "
//所谓创建,其实就是redis的set操作, ARGV[1]即为要设置的许可数
+ "redis.call('set', KEYS[1], ARGV[1]); "
+ "redis.call('publish', KEYS[2], ARGV[1]); "
+ "return 1;"
+ "end;"
+ "return 0;",
Arrays.<Object>asList(getName(), getChannelName()), permits);
}
异常记录
1.Caused by: org.redisson.client.RedisException: ERR This instance has cluster support disabled. channel: [id: 0x0fd46049, L:/127.0.0.1:62555 - R:/127.0.0.1:6379] command: CommandData [promise=org.redisson.misc.RedissonPromise@74bd3426[Not completed], command=(CLUSTER NODES), params=[], codec=null]
是由于设置了集群
Config config = new Config();
// set集群版报错
config.useClusterServers()
.addNodeAddress("redis://127.0.0.1:6379")
应该为
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
2.trySetPermits 一直返回的false,可以查看下是否redis中有了相同的key,trySetPermits 其实就是往redis中塞值。
Java中参数相似方法的那些坑
最近遇见的一些问题,记录一下
当哨兵发来告警,redis执行缓慢,第一反应是不是有人执行了 keys *,
输入 slowlog get 进行查看,关于slowlog get 的用法 可以参考 http://www.redis.cn/commands/slowlog.html
发现都是一些SET,GET命令,并没那种复杂度高的命令,如SORT这些。
虽然这里没有复杂操作,但是从slowlog 的结果我们继续分析,
除了复杂操作,还有一种情况也会导致Redis卡顿,就是Redis出现了大key。
通过 MEMORY USAGE key 来查看 数据的大小
MEMORY USAGE testkey
integer) 3700347
!一个key竟然有3m多!
利用redis客户端查看数据,发现保存的值都是满屏的\x00\x00!
再去查看代码,发现代码中使用了trim,所以没发现异常。
String value = stringRedisTemplate.opsForValue().get(“testkey”).trim();
接下来就是分析这个大key到底是怎么插进去的
伪代码如下:
stringRedisTemplate.opsForValue().set(“testkey”,“xxx”,60601000);
乍一看没什么问题,set了一个key,设置了过期时间。
点进去源码一看,震惊,最后一个参数根本不是过期时间,而是偏移量 offset。
stringRedisTemplate.opsForValue().set(“testkey”,“xxx”,60601000);
再来看这个代码,每次给这个key set值,其实都是在扩大这个key。
4个参数的方法才是设置过期时间的
最后在看下这两个方法,真的很像
说到这里,想起来redisson中的两个方法:
你还记得他们的区别吗?
方法2,两个参数的方法,redisson是会帮你自动续期的,因为没有设置leaseTime
不设置leaseTime就会触发redisson的看门狗机制,如果业务没有执行完,会帮你一直续期。 使用场景要根据自己的业务来取舍。
而方法1,三个参数的方法,只要leaseTime设置了,就会在 leaseTime到期后,锁也一并失效。
一般情况下,我们使用三个参数的方法较为常见。
备注: