当前位置: 首页 / 技术分享 / 正文
分布式锁的实现(二)

2023-01-12

   节点

  4.4. 子节点实现的锁

  在生产环境中,使用子节点做分布式锁的实现的场景是最常见的,包括阻塞型的锁和非阻塞型的锁。因此,设计子节点实现的锁的父类,提供公共的处理逻辑。将阻塞型和非阻塞型的实现不同的逻辑分别在子类中实现即可。

  package com.qianfeng.lock.childNodeLock;

  import com.qianfeng.lock.ZkLocker;

  import com.qianfeng.lock.ZkLockerBase;

  import org.apache.zookeeper.CreateMode;

  import org.apache.zookeeper.KeeperException;

  import java.util.Collections;

  import java.util.List;

  /**

  * @author 千锋大数据教研院 - 章鱼哥

  * @company 北京千锋互联科技有限公司

  */

  public abstract class ZkChildNodeLockerBase extends ZkLockerBase implements ZkLocker {

  /**

  * 创建的子节点锁的全路径

  */

  protected String nodeFullPath;

  public ZkChildNodeLockerBase(String connectString, String lockName) {

  super(connectString, lockName);

  createLockNode();

  }

  public ZkChildNodeLockerBase(String lockName) {

  super();

  this.lockName = lockName;

  createLockNode();

  }

  // 创建锁节点

  public void createLockNode() {

  // 1. 判断节点是否存在

  if (!exists(getLockName())) {

  // 3. 创建锁节点

  createNode(getLockName(), CreateMode.CONTAINER);

  }

  }

  @Override

  public boolean lock() {

  // 1. 在锁节点下创建子节点

  this.nodeFullPath = createNode(getLockName() + "/child-", CreateMode.EPHEMERAL_SEQUENTIAL);

  // 2. 判断是否可以成功上锁

  return canLock();

  }

  @Override

  public boolean unlock() {

  return deleteNode(this.nodeFullPath);

  }

  @Override

  public boolean exists() {

  // 获取当前锁节点下的子节点数量,如果大于0,说明有锁存在

  try {

  Listchildren = getZkCli().getChildren(getLockName(), false);

  return children.size() > 0;

  } catch (KeeperException | InterruptedException ignored) {

  }

  return false;

  }

  protected abstract boolean canLock();

  /**

  * 获取当前创建的子节点之前的节点(根据序号)

  * @return 之前的节点

  */

  protected String getPreviousNode() {

  // 定义变量,记录上一个节点名称

  String previousNodeName = null;

  try {

  // 1. 获取锁节点下所有的子节点

  Listchildren = getZkCli().getChildren(getLockName(), false);

  // 2. 对所有的节点进行名字排序

  Collections.sort(children);

  // 3. 获取当前创建的子节点名称

  String childNodeName = this.nodeFullPath.substring(getLockName().length() + 1);

  // 4. 遍历所有的节点,进行名称的比对

  for (String child : children) {

  if (child.equals(childNodeName)) {

  break;

  }

  previousNodeName = child;

  }

  } catch (KeeperException | InterruptedException e) {

  e.printStackTrace();

  }

  return previousNodeName;

  }

  }

  4.5. 子节点实现的非阻塞型锁

  package com.qianfeng.lock.childNodeLock;

  /**

  * @author 千锋大数据教研院 - 章鱼哥

  * @company 北京千锋互联科技有限公司

  */

  public class ZkChildNodeNoneBlockingLocker extends ZkChildNodeLockerBase {

  public ZkChildNodeNoneBlockingLocker(String connectString, String lockName) {

  super(connectString, lockName);

  }

  public ZkChildNodeNoneBlockingLocker(String lockName) {

  super(lockName);

  }

  @Override

  protected boolean canLock() {

  // 1. 判断是否有之前的节点

  String previousNodeName = getPreviousNode();

  // 2. 如果不存在之前的节点,说明上锁成功

  if (previousNodeName == null) {

  return true;

  }

  // 3. 如果存在之前的节点,说明上锁失败,删除自己创建的子节点即可

  deleteNode(this.nodeFullPath);

  return false;

  }

  }

  4.6. 子节点实现的阻塞型锁

  A程序在进行上锁的时候,发现已经被其他的程序获取到锁了,自己需要等待其他的程序释放锁。基本的实现逻辑就是自己监控已经创建好的节点,发现这个节点消失的时候,自己去创建节点。但是为什么没有用节点作为锁来实现阻塞型的锁呢?是因为这里有“羊群效应”。

  假如有100个程序来获取锁,结果发现锁已经被其他的程序注册了,于是这100个程序都去监听这个节点。一旦这个节点被删除,这100个程序都可以获取到状态的变更,会同时请求ZooKeeper创建节点。这样会带来瞬时的资源占用剧增,严重的甚至可能导致服务器宕机。因此,我们在实现阻塞型锁的时候,使用的是有序的子节点来实现的,每一个程序监控比自己编号小的节点即可。详情可参考3.2.章节的内容。

  package com.qianfeng.lock.childNodeLock;

  import org.apache.zookeeper.AddWatchMode;

  import org.apache.zookeeper.KeeperException;

  import java.util.concurrent.CountDownLatch;

  /**

  * @author 千锋大数据教研院 - 章鱼哥

  * @company 北京千锋互联科技有限公司

  */

  public class ZkChildNodeBlockingLocker extends ZkChildNodeLockerBase {

  // 等待释放信号

  private CountDownLatch latch = new CountDownLatch(1);

  public ZkChildNodeBlockingLocker(String connectString, String lockName) {

  super(connectString, lockName);

  }

  public ZkChildNodeBlockingLocker(String lockName) {

  super(lockName);

  }

  @Override

  protected boolean canLock() {

  // 获取自己创建的子节点之前序号的节点

  String previousNode = getPreviousNode();

  // 如果之前节点不存在,说明已经上锁成功

  if (previousNode == null) {

  return true;

  }

  // 如果有比当前创建的节点序号更小的节点,说明上锁失败,自己阻塞,监听之前的节点即可

  try {

  getZkCli().addWatch(getLockName() + "/" + previousNode, event -> {

  if (event.getType().equals(Event.EventType.NodeDeleted)) {

  // 说明之前节点被删除掉了

  latch.countDown();

  }

  }, AddWatchMode.PERSISTENT);

  // 等待信号

  latch.await();

  } catch (KeeperException | InterruptedException e) {

  e.printStackTrace();

  }

  // 自己成为了最小的节点,上锁成功

  return true;

  }

  }

好程序员公众号

  • · 剖析行业发展趋势
  • · 汇聚企业项目源码

好程序员开班动态

More+
  • HTML5大前端 <高端班>

    开班时间:2021-04-12(深圳)

    开班盛况

    开班时间:2021-05-17(北京)

    开班盛况
  • 大数据+人工智能 <高端班>

    开班时间:2021-03-22(杭州)

    开班盛况

    开班时间:2021-04-26(北京)

    开班盛况
  • JavaEE分布式开发 <高端班>

    开班时间:2021-05-10(北京)

    开班盛况

    开班时间:2021-02-22(北京)

    开班盛况
  • Python人工智能+数据分析 <高端班>

    开班时间:2021-07-12(北京)

    预约报名

    开班时间:2020-09-21(上海)

    开班盛况
  • 云计算开发 <高端班>

    开班时间:2021-07-12(北京)

    预约报名

    开班时间:2019-07-22(北京)

    开班盛况
在线咨询
试听
入学教程
立即报名

Copyright 2011-2023 北京千锋互联科技有限公司 .All Right 京ICP备12003911号-5 京公网安备 11010802035720号