spring整合了redis以后可以直接使用redis分布式锁,过程是:
- 引入依赖
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
- 注入RedisConnectionFactory到RedisLockRegistry,这是用来连接redis执行redis执行的
@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory connectionFactory){
return new RedisLockRegistry(connectionFactory,LOCK_KEY,5000L);
}
- 注入RedisLockRegistry到要用redis锁的地方,然后使用即可
@Resource
private RedisLockRegistry redisLockRegistry;
//1.生成锁对象
Lock lock = redisLockRegistry.obtain(lockKey);
//2.获取锁
if(lock.tryLock()){
try{
......//3.业务代码
}finally{
//4.释放锁
lock.unlock();
}
}
接下来看看这个redis分布式锁的实现原理,看下spring对细节的顶级处理。
首先obtain()方法就是从locks(Map<String, RedisLockRegistry.RedisLock>)中根据key获取RedisLockRegistry.RedisLock对象,这里用来map进行缓存,对相同的key取得的就是相同的对象,避免重复new降低效率。
public Lock obtain(Object lockKey) {
Assert.isInstanceOf(String.class, lockKey);
String path = (String)lockKey;
return (Lock)this.locks.computeIfAbsent(path, (x$0) -> {
return new RedisLockRegistry.RedisLock(x$0);
});
}
tryLock()方法就是获取redis分布式锁的。首先尝试获取本地ReentrantLock可重入公平锁,当应用的线程第一次获取redis锁时,就会在redis中生成key表示自己获取到锁了,如果接下来时应用本地的线程来获取锁,就会直接通过ReentrantLock先拿锁,没拿到说明有线程拿到锁了,就不用去redis获取,可以提高效率。然后再到redis去获取分布式锁。
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
long now = System.currentTimeMillis();
//首先尝试获取本地ReentrantLock可重入公平锁
if (!this.localLock.tryLock(time, unit)) {
return false;
} else {
try {
long expire = now + TimeUnit.MILLISECONDS.convert(time, unit);
boolean acquired;
//不断获取直到超时
while(!(acquired = this.obtainLock()) && System.currentTimeMillis() < expire) {
Thread.sleep(100L);
}
//没拿到就释放本地ReentrantLock
if (!acquired) {
this.localLock.unlock();
}
return acquired;
} catch (Exception var9) {
this.localLock.unlock();
this.rethrowAsLockException(var9);
return false;
}
}
}
获取锁的本质就是执行一段lua脚本加锁,首先锁key是在构造RedisLockRegistry.RedisLock生成的RedisLockRegistry.this.registryKey + ":" + path;
,path就是我们要操作的redis中的key,
private boolean obtainLock() {
Boolean success = (Boolean)RedisLockRegistry.this.redisTemplate.execute(RedisLockRegistry.this.obtainLockScript, Collections.singletonList(this.lockKey), new Object[]{RedisLockRegistry.this.clientId, String.valueOf(RedisLockRegistry.this.expireAfter)});
boolean result = Boolean.TRUE.equals(success);
if (result) {
this.lockedAt = System.currentTimeMillis();
}
return result;
}
加锁的过程就是执行一段lua脚本,先获取key的value,判断是否为自身的lockClientId,如果是说明我们已经拿到这个锁,为了保证可重入性,重新设置过期时间返回true继续加锁;如果key的value不存在说明没有应用线程拿到锁,我们自己来加锁,往redis中扔个key,value为自身lockClientId;否则说明这个锁已经被占用了,返回false。
this.obtainLockScript = new DefaultRedisScript("
local lockClientId = redis.call('GET', KEYS[1])
if lockClientId == ARGV[1] then
redis.call('PEXPIRE', KEYS[1], ARGV[2])
return true
elseif not lockClientId then
redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2])
return true
end
return false", Boolean.class);
unlock就是释放锁了,就是到redis中删除key。