Ractive编程实战:基于Zookeeper实现分布式锁


本文的源码已上传至 Github

分布式锁需要解决的问题

  • 数据一致性问题:多个client抢锁,只能有一个client能拿到锁,不能出现多个client拿到多把锁的情况;
  • 锁重入问题:同个客户端可以多次获取同一把锁;
  • 单点故障问题:提供锁服务的server应该能尽快从故障状态中恢复并继续提供服务;
  • 锁释放时的回调机制:如何高效地将锁已经被释放的消息通知给所有client;
  • 其它的异常情况:比如获得锁的线程如果挂了怎么办,如果单纯用超时机制去解决,那又如何界定是业务导致的超时还是异常导致的超时?

为什么是zookeeper

  • 针对数据一致性问题:zookeeper使用ZAB协议保证集群间数据的一致性
  • 针对单点故障问题:官网给出的数据是在4万至5万client连接的情况下能在200ms内选举出新的leader;
  • 针对锁释放时如何通知其它client:zookeeper的watch机制提供了节点事件的回调,可以让节点在新增修改删除等事件发生时执行;
  • 针对超时时间:zookeeper的EPHEMERAL节点只维持在一个session当中,当获得锁的线程发生异常退出时节点自动删除,也就相当于释放了锁;

针对上诉几个问题,zookeeper作为分布式协调组件拥有得天独厚的优势,俨然一副实现分布式锁的天生胚子。

本文基于zookeeper实现分布式锁的思路

  • 加锁:在zookeeper中创建一个EPHEMERAL SEQUENTIAL的节点,因为EPHEMERAL的特性是只存在于session中,如果client发生异常退出了,锁也自然释放了;而SEQUENTIAL能够让每个client之间创建的节点互不干扰;
  • 解锁:判断重入计数器大小,如果大于0自减,否则删除对应的节点;
  • 锁的获取机制:第一个节点的线程获得锁,其它线程相继监听它们的前继节点,如果前继节点被删除,说明锁被释放,则尝试获取执行权。
  • 锁的重入机制:加锁时检索节点中是否包含当前线程创建的节点,如果有则计数器+1;解锁的时候当计数器为0才释放锁;

关键函数

加锁
@Override
public void tryLock() {
      try {
          // 判断是否重入锁
          if (isReentrantLock()) {
              reentrant++;
          } else {
              zk.create(String.format("/%s-", threadName), threadName.getBytes(),
                      ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL, this, CTX);
              latch.await();
          }
      } catch (Exception e) {
          logger.error("unknown error on trying lock ", e);
      }
  }
获取执行权逻辑
@Override
public void processResult(int rc, String path, Object ctx, List<String> children) {
    // 获取到当前目录创建了多少把锁,进行排序,然后判断自己是否是第一个节点,如果是则执行,
    // 如果不是则监听前继节点继续阻塞。
    Collections.sort(children);
    int index = children.indexOf(lockName);
    if (index == 0) {
        // 如果当前节点是第一个,则取消阻塞
        logger.info(threadName + " is working...");
        latch.countDown();
    } else {
        try {
            // 监听前继节点
            zk.exists("/" + children.get(index - 1), this);
        } catch (Exception e) {
            logger.error(String.format("unknown error while listen to the path %s",
                    children.get(index - 1)), e);
        }
    }
}
解锁
@Override
public void unLock() {
    try {
        if (reentrant > 0) {
            reentrant--;
            zk.getChildren("/", false, this, CTX);
        } else {
            // 删除当前节点
            zk.delete("/" + lockName, -1);
        }
    } catch (Exception e) {
        logger.error("unknown error on unlock ", e);
    }
}
测试函数
public class BaseTest {
    @Test
    public void test() {
        for (int i=0; i<10; i++) {
            new Thread( () -> {
                ZKLock lock = new ZKLock();
                lock.setThreadName(Thread.currentThread().getName());
                lock.tryLock();
                // 业务逻辑
                try {
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(Thread.currentThread().getName() + " is done");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock.unLock();
            }).start();
        }
        while (true) {

        }
    }
}
程序执行的效果

在这里插入图片描述
本文实现的锁只是提供了大致的思路,并未经过生产测试,如果各位读者正好在实现这一方面的需求,希望这篇文章能够给帮助到大家。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值