分布式锁
分布式锁是什么?
分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
数据库实现分布式锁
实现案例
如使用数据库事务中的锁如record lock来实现,如下所示
1 获取锁
public void lock(){
connection.setAutoCommit(false) int count = 0; while(count < 4){ try{ select * from lock where lock_name=xxx for update; if(结果不为空){ //代表获取到锁
return;
}
}catch(Exception e){
} //为空或者抛异常的话都表示没有获取到锁
sleep(1000);
count++;
} throw new LockException();
}
2 释放锁
public void release(){
connection.commit();
}
数据库的lock表,lock_name是主键,通过for update操作,数据库就会对该行记录加上record lock,从而阻塞其他人对该记录的操作。
一旦获取到了锁,就可以开始执行业务逻辑,最后通过connection.commit()操作来释放锁。
其他没有获取到锁的就会阻塞在上述select语句上,可能的结果有2种,在超时之前获取到了锁,在超时之前仍未获取到锁(这时候会抛出超时异常,然后进行重试)
数据库当然还有其他方式,如插入一个有唯一约束的数据。成功插入则表示获取到了锁,释放锁就是删除该记录。该方案也有很多问题要解决
ZooKeeper实现分布式锁
实现 案例
这也是ZooKeeper客户端curator的分布式锁实现。
1 获取锁
public void lock(){
path = 在父节点下创建临时顺序节点 while(true){
children = 获取父节点的所有节点 if(path是children中的最小的){
代表获取了节点 return;
}else{
添加监控前一个节点是否存在的watcher wait();
}
}
}
watcher中的内容{
notifyAll();
}
2 释放锁
public void release(){
删除上述创建的节点
}
总结
ZooKeeper版本的分布式锁问题相对比较来说少。
锁的占用时间限制:redis就有占用时间限制,而ZooKeeper则没有,最主要的原因是redis目前没有办法知道已经获取锁的客户端的状态,是已经挂了呢还是正在执行耗时较长的业务逻辑。而ZooKeeper通过临时节点就能清晰知道,如果临时节点存在说明还在执行业务逻辑,如果临时节点不存在说明已经执行完毕释放锁或者是挂了。由此看来redis如果能像ZooKeeper一样添加一些与客户端绑定的临时键,也是一大好事。
是否单点故障:redis本身有很多中玩法,如客户端一致性hash,服务器端sentinel方案或者cluster方案,**很难做到一种分布式锁方式能应对所有这些方案**。而ZooKeeper只有一种玩法,多台机器的节点数据是一致的,没有redis的那么多的麻烦因素要考虑。
总体上来说ZooKeeper实现分布式锁更加的简单,可靠性更高。
分布式锁的实现,在我自己看来有如下3个方面:
怎么获取锁
怎么释放锁
怎么得知锁被释放了
怎么获取锁
能够提供一种方式,多个客户端并发操作,只能有一个客户端能满足相应的要求
如数据库的for update的sql语句、或者插入一个含有唯一约束的数据等
如redis的setnx等
如ZooKeeper的求最小节点的方式
这些都可以保证只能有一个客户端获取到了锁
怎么释放锁
场景一般有2种情况:
1 正常情况下的释放锁
2 异常情况下如何释放锁(即释放锁的操作没有被执行,如挂掉、没执行成功等原因)
如redis正常情况下释放锁是删除lock_key,异常情况下,只能通过lock_key的超时时间了
如ZooKeeper正常情况下释放锁是删除临时节点,异常情况下,服务器也会主动删除临时节点(这种机制就简单多了)
怎么得知锁被释放了
实现方式一般有2种情况:
1 没有获取到锁的客户端不断尝试获取锁
2 服务器端通知客户端锁被释放了
当然第二种情况是最优的(客户端所做的无用功最少),如ZooKeeper通过注册watcher来得到锁释放的通知。而数据库、redis没有办法来通知客户端锁释放了,那客户端就只能傻傻的不断尝试获取锁了。