分布式锁
1.什么是分布式锁?它有什么作用?
-
分布式锁是控制分布式系统之间同步访问共享资源的一种的方式。
-
在分布式系统中,常常需要协调它们之间的动作。如果不同的系统或者同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰保证一致性,在这样的场景加需要使用分布式锁。
2.分布式锁的实现方式有哪几种?
分布式锁有三种实现方式:
- 数据库分布式锁。
- redis分布式锁。
- zookeeper分布式锁。
3.这些三种方式有何优缺点?
-
前两种都有死锁的致命缺点。
死锁的原因:当某一台主机获取到锁时,这个时候主机因为某种原因(比如主机挂掉了),一直没有释放锁,就导致了死锁。
-
企业中都是采用zookeeper来实现分布式锁,原因之一,就是zookeeper可以完美的解决死锁。
4.zookeeper为什么能解决死锁问题?
zookeeper分布式锁,主要利用了zookeeper的三大特功能。
- zookeeper中不允许有重复的节点。
- zookeeper可以创建临时节点,临时节点在断开连接之后,会立即删除。
- zookeeper自带事件监听,当节点放生变化时(新增,删除,修改),会立即监听到。
解决死锁,必须满足两个条件
- 当系统挂掉时,也能够释放锁。利用临时节点断开连接之后,立即删除节点。
- 当释放锁的时候,可以发出通知给其他请求。利用事件监听,监听到节点删除时,唤醒其他等待的请求。
5.zookeeper分布式锁的实现原理?
主要是四步,获得锁,等待,释放锁,唤醒。
-
获得锁
当多个请求访问共享资源时,都要先去zookeeper中创建一个临时节点(该临时节点有且只能有一个)。创建成功,则表示获得锁,创建失败,表示没有获得锁。
-
等待
没有获取锁的请求,都会进入等待状态。并且都去zookeeper的临时节点上注册一个监听事件,监听临时节点的状态。
-
释放锁
获得的锁的请求在完成操作之后,断开与zookeeper的连接。连接断开之后,zookeeper立即删除临时节点。
删除节点时触发了其他请求的事件监听,通知正在等待的请求。
-
唤醒
其他请求的监听到临时节点被删除,唤醒正在等待的请求。这些请求开始下一轮的锁竞争。
6.zookeeper分布式锁代码实现
-
DistributionLock接口
package com.hyg.lock; /** * 分布式锁 * @author Administrator * */ public interface DistributionLock { //获取锁 public void lock(); //释放锁 public void unlock(); }
-
AbstractZookeeperLock抽象类
package com.hyg.lock; import java.util.concurrent.CountDownLatch; import org.I0Itec.zkclient.ZkClient; /** * 重构重复代码,将重复代码交父类执行 * * @author Administrator * */ public abstract class AbstractZookeeperLock implements DistributionLock { // zk连接地址 private static final String CONNECTION_ADDRESS = "192.168.100.129:2181"; // 创建zk连接 protected ZkClient zkClient = new ZkClient(CONNECTION_ADDRESS); // 创建临时节点 protected static final String TEMP_NODE = "/lock"; // 信号灯 protected CountDownLatch countDownLatch = null; /** * 获取锁 */ public void lock() { if (tryLock()) { System.out.println(Thread.currentThread().getName() + "####获取锁成功####"); } else { // 等待 waitLock(); // 等待结束,重新获取锁 lock(); } } /** * 当获取锁不成功的情况下,重新获取锁 */ abstract void waitLock(); /** * 是否获取锁成功 成功:true 失败:false * * @return */ abstract boolean tryLock(); /** * 断开连接,Zookeeper会删除临时节点TEMP_NODE */ public void unlock() { if (zkClient != null) { zkClient.close(); System.out.println(Thread.currentThread().getName() + "###释放锁!"); } } }
-
实现类
package com.hyg.lock; import java.util.concurrent.CountDownLatch; import org.I0Itec.zkclient.IZkDataListener; /** * zookeeper实现分布式锁 * * @author Administrator * */ public class ZookeeperLock extends AbstractZookeeperLock { /** * 去Zookeeper中创建节点。 如果创建成功,表示获得锁,返回true。 创建失败,表示没有获取锁,返回false。 * */ @Override boolean tryLock() { try { //创建临时节点 zkClient.createEphemeral(TEMP_NODE); return true; } catch (Exception e) { return false; } } @Override void waitLock() { // 使用事件监听,获取到TEMP_NODE节点被删除的时候,唤醒等待的线程 IZkDataListener iZkDataListener = new IZkDataListener() { // 删除节点 public void handleDataDeleted(String dataPath) throws Exception { // 当临时节点TEMP_NODE被删除时 if (TEMP_NODE.equals(dataPath)) { // 唤醒等待的线程 if (countDownLatch != null) { countDownLatch.countDown(); } } } // 修改节点数据 public void handleDataChange(String dataPath, Object data) throws Exception { } }; // 注册节点信息 zkClient.subscribeDataChanges(TEMP_NODE, iZkDataListener); // 检测节点是否存在 if (zkClient.exists(TEMP_NODE)) { // 创建信号量 countDownLatch = new CountDownLatch(1); // 等待唤醒 try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } //删除注册信息 zkClient.unsubscribeDataChanges(TEMP_NODE, iZkDataListener); } }
-
测试代码
DistributionLock distributionLock = new ZookeeperLock(); public void run() { try { //1.加锁 distributionLock.lock(); //加锁操作 ....... }catch (Exception e) { // TODO: handle exception }finally { distributionLock.unlock(); } }
run() {
try {
//1.加锁
distributionLock.lock();
//加锁操作
…
}catch (Exception e) {
// TODO: handle exception
}finally {
distributionLock.unlock();
}
}