public String order(){
String lockKey = "product:001";
// 获取锁对象
RLock lock = redisson.getLock(lockKey);
try {
// 加分布式锁
lock.lock();
// 执行业务逻辑
System.out.println("抢单逻辑。。。");
} finally {
// 释放锁
lock.unlock();
}
return "success";
}
获取锁对象,其实就是获取一个RedissonLock对象。
RLock lock = redisson.getLock(lockKey);
Redisson继承了RedissonExpirable抽象类,实现了RLock接口,如下图:
Redisson构造方法:
public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {
// 对应的RedissonExpirable构造方法
super(commandExecutor, name);
// commandExecutor为通过redis连接配置生成的命令执行器
this.commandExecutor = commandExecutor;
// id为通过UUID.randomUUID()生成的uuid
this.id = commandExecutor.getConnectionManager().getId();
// internalLockLeaseTime为redis连接配置文件配置的看门狗超时时间,默认30秒
this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
}
开始竞争锁。
lock.lock();
// 上级方法会捕获中断异常,从而中断当前线程
// 传参为:this.lockInterruptibly(-1L, (TimeUnit)null);
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
long threadId = Thread.currentThread().getId();
// 通过lua脚本原子地设置锁名称到redis,并设置过期时间(默认30秒)
// 是一个hash结构,key为锁名称,value为uuid+当前线程id
// ttl为剩余过期时间毫秒数
Long ttl = this.tryAcquire(leaseTime, unit, threadId);
if (ttl != null) {
// ttl不为空,表示当前所已被占用
RFuture<RedissonLockEntry> future = this.subscribe(threadId);
this.commandExecutor.syncSubscription(future);
try {
while(true) {
ttl = this.tryAcquire(leaseTime, unit, threadId);
if (ttl == null) {
return;
}
if (ttl >= 0L) {
this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} else {
this.getEntry(threadId).getLatch().acquire();
}
}
} finally {
this.unsubscribe(future, threadId);
}
}
}
加锁的lua脚本分析:
if (redis.call('exists', KEYS[1]) == 0)
then
// 锁名不存在,设置到redis,并设置过期时间(默认30秒)
redis.call('hset', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1)
then
// 锁存在,则对应的field加1,并重置过期时间
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
// 获取key剩余时间毫秒数
return redis.call('pttl', KEYS[1]);
主动解锁。
lock.unlock();
public void unlock() {
// 解锁
Boolean opStatus = (Boolean)this.get(this.unlockInnerAsync(Thread.currentThread().getId()));
if (opStatus == null) {
throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + this.id + " thread-id: " + Thread.currentThread().getId());
} else {
if (opStatus) {
this.cancelExpirationRenewal();
}
}
}
lua脚本:
if (redis.call('exists', KEYS[1]) == 0)
// 锁不存在,发布消息
then redis.call('publish', KEYS[2], ARGV[1]);
return 1;
end;
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0)
// 锁已释放
then
return nil;
end;
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
if (counter > 0)
then redis.call('pexpire', KEYS[1], ARGV[2]);
return 0;
else redis.call('del', KEYS[1]);
// 释放锁
redis.call('publish', KEYS[2], ARGV[1]);
return 1;
end;
return nil;
redis中使用lua脚本格式:
eval script numkeys key [key ...] arg [arg ...]
命令解释:
script:具体的lua命令。
numkeys:参数用于指定键名参数的个数。
key [key …]:从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] ,KEYS[2] ,以此类推)。
arg [arg …]:附加参数,可以在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、ARGV[2] ,诸如此类)。
lua示例:
> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
其中:这是一段有返回值的lua脚本。数字2指明了键名参数的数量,key1和key2是键名参数,分别使用KEYS[1]和KEYS[2]访问,first和second是附加参数,通过ARGV[1]和ARGV[2]访问。
在lua脚本中执行redis命令:
redis.call();
redis.pcall();
示例:
> eval "return redis.call('set','foo','bar')" 0
OK
> eval "return redis.call('set',KEYS[1],'bar')" 1 foo
OK
> get foo
bar