简介:
分布式集群系统演化的结果,是多线程多进程的分布在不同主机。导致原单机部署情况下的并发控制锁策略失效。
大部分Web应用都是依赖于Java的开发框架,而 Java 并没有提供分布式锁的支持。 为了解决这个问题,需要选择一种跨JVM的互斥机制来控制共享资源的访问。
分布式锁与它的不同方案,就是要解决这个问题。
目前的主流方案有:
1、基于数据库实现分布式锁
2、基于缓存实现分布式锁
3、基于Zookeeper
追求性能,基于缓存实现的分布式锁最好
希望可靠性,选择Zookeeper更胜一筹
使用redis实现分布式锁
setnx 就是redis实现分布式锁的命令
setnx lock 1
-->(integer) 1
setnx lock 1
-->(integer) 0
设置了lock之后,继续访问锁的操作。
如果锁在,会等待重试。锁不在时,才会放行其他请求。 只有把锁解开后(del lock),才能继续其他操作。
死锁问题:
锁释放会出现死锁问题。 自动化解决死锁问题,可以根据具体业务,给锁设定一个合理的过期时间:
setnx lock 1 //设置锁
expire lock 10 // 10秒后锁过期
考虑到上锁操作原子性的必要性,应该将设置锁和设置过期时间在一条指令中执行
set lock 1 nx ex 10 // nx上锁 ex过期时间
实例代码:
public void testLock(){
// 1 获取锁 3秒后过期
Boolean lock = redisTemplage.opsForValue().setIfAbsent("lock","1",3,TimeUnit.SECONDS);
// 2 获取锁成功、查询num的值
if(lock){
Object value = redisTemplate.opsForValue().get("num");
// 判断num为空return
if(StringUtils.isEmpty(value))
return;
//将redis的num加一(业务操作),并手动释放锁
redisTemplate.opsForValue().set("num",++num);
redisTemplate.delete("lock");
}else{
// 获取锁失败,0.1s后再获取
try{
Thread.sleep(100);
testLock();
}catch(...)...
}
}
使用UUID,给锁唯一的标识:
X设置锁后,在手动释放前宕机。Y此时在X设置的锁自动释放后,上了锁。 X在功能恢复后,会将Y设置的锁释放。
为了避免这个问题的出现,应该给锁加uuid。
String uuid = new UUID.RandomUUID().toString();
Boolean lock = redisTemplage.opsForValue().setIfAbsent("lock",uuid,3,TimeUnit.SECONDS);
if(get(lock)==uuid)
redisTemplate.delete("lock");
else{
~~~
}
但删除操作缺少原子性 在删除时,锁过期自动释放。此时上锁后,锁会被前置的删除操作删除。
(共享锁的问题)
LUA脚本:
弱脚本在执行时,其他指令会等待。
所以用LUA脚本可以保证删除操作的原子性。
分布式锁需要满足
-互斥性。在任意时刻,只有一个客户端能持有锁。
-不会发生死锁。
-加锁和解锁过程必须有原子性。