分布式锁问题
- 1. 分布式锁问题
- 1.1 问题介绍
- 1.2 解决方案
- 1.2.1 分布式锁主流的实现方案
- 1.2.2 使用Redis实现分布式锁
- 1.2.3 分布式锁需要满足的四个条件
- 1.3 实现分布式锁
1. 分布式锁问题
1.1 问题介绍
- 单机单体中的锁机制在分布式集群系统中失效;
- 单纯的Java API并不能提供分布式锁的能力,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问;
1.2 解决方案
1.2.1 分布式锁主流的实现方案
- 基于数据库实现分布式锁;
- 基于缓存(Redis等),性能最高;
- 基于Zookeeper,可靠性最高;
1.2.2 使用Redis实现分布式锁
- 使用set命令添加一个mutex key,同时为该key设置过时时间,并要求该key存在时无法更新数据;
- 通过set命令设置的mutex key即为分布式锁,在该key过时或被删除以前,此key无法被其他请求获取到;
- 具体可通过
set mutex_key uuid nx ex 过期时间
进行加锁,其中mutex_key为锁名,value为一唯一值,过期时间以秒为单位; - 可通过
del mutex_key
手动释放锁,但缺乏原子性,可通过lua脚本进行删除保证操作原子性;
- set命令中使用UUID做value是为了解决该问题:当前服务器可能释放其他服务器的锁,而自身的锁也可能被其他服务器释放;
- UUID是一个唯一值,用于标识不同的服务器;
1.2.3 分布式锁需要满足的四个条件
- 互斥性:在任意时刻,只有一个客户端能持有锁;
- 不会发生死锁:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁;
- 解铃还须系铃人:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了;
- 加锁和解锁必须具有原子性;
1.3 实现分布式锁
- 基于SpringBoot框架,常用配置参考https://springdoc.cn/spring-boot/application-properties.html#application-properties.data.spring.data.redis.cluster.nodes;
- 代码:
// 分布式锁测试
@RequestMapping("/lockTest")
public void lockTest(){
String uuid= UUID.randomUUID().toString();
// 获取锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS);
// 成功获取锁
if (lock){
// 逻辑业务
Object value = redisTemplate.opsForValue().get("num");
if(StringUtils.isEmpty(value)){
return;
}
Long num = Long.parseLong(value+"");
redisTemplate.opsForValue().set("num",num+1);
// 手动释放锁
String lockVal = (String) redisTemplate.opsForValue().get("lock");
if (lockVal.equals(uuid)) {
redisTemplate.delete("lock");
}
// 定义lua脚本
String luaScript="if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1])" +
"else return 0 end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(luaScript);
redisScript.setResultType(Long.class);
// 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值
redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);
}else{
// 等待一会再重新尝试获取锁
try {
Thread.sleep(100);
lockTest();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}