分布式锁

常见的三种:

image-20250310112536137

基于Redis的分布式锁

image-20250310145255912

在高并发的场景下,阻塞式的锁很容易耗光系统的性能。

误删分布式锁

image-20250310155100038

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private StringRedisTemplate stringRedisTemplate;

private String name;

private static final String ID_PREFIX= UUID.randomUUID().toString(true)+"-";

private static final String KEY_PREFIX="lock:";

public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
this.stringRedisTemplate = stringRedisTemplate;
this.name = name;
}

@Override
public boolean tryLock(long timeoutSec) {

//利用UUID加线程id为key,防止多台jvm导致的线程id一致,从而误删锁
String threadId = ID_PREFIX +Thread.currentThread().getId();

Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId , timeoutSec, TimeUnit.SECONDS);

return Boolean.TRUE.equals(success);
}

@Override
public void unlock() {

//比对key值

String threadId = ID_PREFIX +Thread.currentThread().getId();

String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);

if(threadId.equals(id)){
stringRedisTemplate.delete(KEY_PREFIX+name);
}
}
极端情况下的误删

image-20250310154928934

获取锁,执行业务皆成功,但是在释放锁时由于极端情况导致释放锁,被阻塞,如FULL GC,由于阻塞时间过长,触发了超时释放锁的机制,锁被释放,但是,线程1还是在阻塞中,在阻塞的这段时间内,恰好有另一个线程,获取了这把锁,并开始执行业务,在线程2执行业务期间,线程1阻塞结束,执行释放锁操作,就误删了线程2的锁。

出现这个情况的原因是:判断锁标识和释放锁的动作是分开的,两个动作。

原子操作的lua脚本

image-20250310160345255

lua脚本,获取keys[1]的value,与正常的value比较,相同就调用delete方法,删除锁

1
2
3
4
if(redis.call('get',KEYS[1])== ARGV[1]) then
return redis.call('del',KEYS[1])
end
return 0

利用静态代码块加载lua脚本

1
2
3
4
5
6
7
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
static {
UNLOCK_SCRIPT=new DefaultRedisScript<>();
UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
UNLOCK_SCRIPT.setResultType(Long.class);
}

调用lua脚本释放锁

1
2
3
4
5
6
7
8
9
10
11
@Override
public void unlock() {
stringRedisTemplate.execute(
//lua脚本
UNLOCK_SCRIPT,
//key的集合,单个参数使用这个方法
Collections.singletonList(KEY_PREFIX + name),
//value,参数
ID_PREFIX +Thread.currentThread().getId());
}
}