一:总结下分布式锁使用原理:
1.获取内存锁对象:
Lock lock = redisLockRegistry.obtain(key);
内部解析:
【注】该地方返回的是,如果已有的返回之前的对象,否则new个新的对象。
private final Map<String, RedisLock> locks = new ConcurrentHashMap<>();
@Override
public Lock obtain(Object lockKey) {
Assert.isInstanceOf(String.class, lockKey);
String path = (String) lockKey;
return this.locks.computeIfAbsent(path, RedisLock::new);
}
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
- 获取锁过程:
lock.tryLock(String lockKey, long seconds)
内部解析:
①首先使用缓存ReentrantLock锁,这样同一机器不用去请求redis
②其次使用redis锁,lua脚本
这里说明:同一台机器重复获取redis锁,相当于重入,超时时间加一倍。
private static final String OBTAIN_LOCK_SCRIPT =
"local lockClientId = redis.call('GET', KEYS[1])\n" +
"if lockClientId == ARGV[1] then\n" +
" redis.call('PEXPIRE', KEYS[1], ARGV[2])\n" +
" return true\n" +
"elseif not lockClientId then\n" +
" redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2])\n" +
" return true\n" +
"end\n" +
"return false";
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
long now = System.currentTimeMillis();
if (!this.localLock.tryLock(time, unit)) {
return false;
}
try {
long expire = now + TimeUnit.MILLISECONDS.convert(time, unit);
boolean acquired;
while (!(acquired = obtainLock()) && System.currentTimeMillis() < expire) { /SONAR
Thread.sleep(100); /SONAR
}
if (!acquired) {
this.localLock.unlock();
}
return acquired;
}
catch (Exception e) {
this.localLock.unlock();
rethrowAsLockException(e);
}
return false;
}
3.获取到锁之后,执行正常流程完成后,涉及到释放锁。
if(Objects.nonNull(lock) && hasLock){
lock.unlock();
redisLockRegistry.expireUnusedOlderThan(-300);
}
① 首先释放redis锁。
②再根据超时时间释放缓存锁数据。
@Override
public void expireUnusedOlderThan(long age) {
Iterator<Map.Entry<String, RedisLock>> iterator = this.locks.entrySet().iterator();
long now = System.currentTimeMillis();
while (iterator.hasNext()) {
Map.Entry<String, RedisLock> entry = iterator.next();
RedisLock lock = entry.getValue();
if (now - lock.getLockedAt() > age && !lock.isAcquiredInThisProcess()) {
iterator.remove();
}
}
}
不过有个问题:
在上面释放redis锁之后,在释放缓存锁数据时,是根据是否是本机数据,并且超时时间来删除的。此时有个场景会有问题:
A,线程执行完释放锁
B线程进来等待锁
C线程开始来执行。
此时内存锁只要A释放了,任何一个线程执行完都可以释放这把内存锁,导致C是锁不住的问题。
就会看到并发了。所以上面-300设置是有问题的
public boolean isAcquiredInThisProcess() {
return RedisLockRegistry.this.clientId.equals(
RedisLockRegistry.this.redisTemplate.boundValueOps(this.lockKey).get());
}
【解决方法】
超时时间跟redis锁默认超时时间保持一致;
redisLockRegistry.expireUnusedOlderThan(60000L);
!lock.isAcquiredInThisProcess()表示是其他机器锁住了,或者是其他机器已经释放两种情况,所以删除没有问题。
-----未测试哈