分布式锁
锁是用来解决什么问题的
- 一个进程中的多个线程,多个线程并发访问同一个资源的时候,如何解决线程安全问题。
- 一个分布式架构系统中的两个模块同时去访问一个文件对文件进行读写操作。
- 多个应用对同一条数据做修改的时候,如何保证数据的安全性。
在单进程中,我们可以用到synchronized、lock之类的同步操作去解决,但是对于分布式架构下多进程的情况下,如何做到跨进程的锁。就需要借助一些第三方手段来完成。
设计一个分布式锁需要解决的问题
分布式锁的解决方案
- 利用数据库的唯一约束
lock表(伪表)
lock(
id int(11)
methodName varchar(100),
memo varchar(1000)
modifyTime timestamp
unique key mn (methodName) --唯一约束
)
- 获取锁(插入一条记录)
try{
exec insert into lock(methodName,memo) values(‘method’,’desc’);
return true;
}Catch(DuplicateException e){
return false;
}
- 释放锁
delete from lock where methodName=’’;
存在需要思考的地方
- 锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。
- 锁是非阻塞的,数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。
- 锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁
-
zookeeper实现分布式锁
利用zookeeper的唯一节点特性或者有序临时节点特性获得最小节点作为锁. zookeeper 的实现相对简单,通过curator客户端,已经对锁的操作进行了封装,原理如下
zookeeper的优势
- 可靠性高、实现简单。
- zookeeper因为临时节点的特性,如果因为其他客户端因为异常和zookeeper连接中断了,那么节点会被删除,意味着锁会被自动释放。
- zookeeper本身提供了一套很好的集群方案,比较稳定。
- 释放锁操作,会有watch通知机制,也就是服务器端会主动发送消息给客户端这个锁已经被释放了
- 基于缓存的分布式锁实现
redis中有一个setNx命令,这个命令只有在key不存在的情况下为key设置值。所以可以利用这个特性来实现分布式锁的操作。
- 获取锁
/**
* 进程获得锁
* @param key 方法
* @param timeout 超时时间
* @return UUID
*/
public String getLock(String key,int timeout){
try{
Jedis jedis = RedisManager.getJedis();
String value = UUID.randomUUID().toString();
//模拟锁阻塞状态 (多路复用)
long end = System.currentTimeMillis() + timeout;
while (System.currentTimeMillis() < end ){
if(jedis.setnx(key,value) == 1){
//设置key失效时间 (模拟超时释放锁)
jedis.expire(key,timeout);
return value;
}
Thread.sleep(1000);
//双重判断 是否设置过期时间
if(jedis.ttl(key) == -1){
jedis.expire(key,timeout);
}
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
- 释放锁
/**
* 进程释放锁 删除key
* @param key 方法
* @param value UUID
* @return
*/
public boolean releaseLock(String key,String value){
try {
Jedis jedis = RedisManager.getJedis();
while (true){
jedis.watch(key); //监控key
if(value.equals(jedis.get(key))){
//开启事务
Transaction transaction = jedis.multi();
jedis.del(key);
List<Object> list = transaction.exec();
//删除失败 循环删除
if(list == null){
continue;
}
return true;
}
jedis.unwatch();
break;
}
}catch (Exception e){
e.printStackTrace();
}
return false;
}