Java实现分布式锁-基于Zookeeper(3)

787 阅读2分钟

Zookeeper数据结构

  • ZooKeeper数据模型的结构与Unix文件系统很类似,整体上可以看作是一棵树,每个节点称做一个ZNode。每个Znode可以类似看作是一个目录,其下可以创建子目录。很显然zookeeper集群自身维护了一套数据结构。这个存储结构是一个树形结构,其上的每一个节点,我们称之为"znode",每一个znode默认能够存储1MB的数据,每个ZNode都可以通过其路径唯一标识。
  • Zookeeper 节点类型可以分为三大类:持久性节点(Persistent)、临时性节点(Ephemeral)、顺序性节点(Sequential)

Zookeeper的观察器

  • 在Zookeeper中可以设置观察器的3个方法:
    • 1.getData() 获取数据
    • 2.getChilderen() 获取子节点
    • 3.exists() 判断当前节点是否存在
  • 通过在Zookeeper中设置观察器,节点数据发生变化,发送给客户端观察器只能监控一次,在监控需要重新设置 ,但是本次我们使用zookeeper的升级版(curator),可以解决监控一次的问题。

Zookeeper实现原理

  • 利用Zookeeper的瞬时有序节点的特性,在多线程并发创建瞬时节点的时候,得到有序的序列,序号最小的线程获得锁。其他获得锁的线程都是失败的。其他线程只能监听自己序号的前一个序号,前一个序号执行完成以后,删除自己序号的节点,下一个序号的线程得到通知,接着执行。所以创建节点的时候,已经确定了线程的执行顺序按照序号去执行。

代码实现

  • pom.xml
	<dependency>
          <groupId>org.apache.zookeeper</groupId>
          <artifactId>zookeeper</artifactId>
          <version>3.4.14</version>
       </dependency>
  • ZkLock.java
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

@Slf4j
public class ZkLock implements AutoCloseable, Watcher {

    private ZooKeeper zooKeeper;
    private String znode;

    public ZkLock() throws IOException {
        this.zooKeeper = new ZooKeeper("localhost:2181",
                10000,this);
    }

    public boolean getLock(String businessCode) {
        try {
            //创建业务 根节点
            Stat stat = zooKeeper.exists("/" + businessCode, false);
            if (stat==null){
                zooKeeper.create("/" + businessCode,businessCode.getBytes(),
                        ZooDefs.Ids.OPEN_ACL_UNSAFE,
                        CreateMode.PERSISTENT);
            }

            //创建瞬时有序节点  /order/order_00000001
            znode = zooKeeper.create("/" + businessCode + "/" + businessCode + "_", businessCode.getBytes(),
                    ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.EPHEMERAL_SEQUENTIAL);

            //获取业务节点下 所有的子节点
            List<String> childrenNodes = zooKeeper.getChildren("/" + businessCode, false);
            //子节点排序
            Collections.sort(childrenNodes);
            //获取序号最小的(第一个)子节点
            String firstNode = childrenNodes.get(0);
            //如果创建的节点是第一个子节点,则获得锁
            if (znode.endsWith(firstNode)){
                return true;
            }
            //不是第一个子节点,则监听前一个节点
            String lastNode = firstNode;
            for (String node:childrenNodes){
                if (znode.endsWith(node)){
                    zooKeeper.exists("/"+businessCode+"/"+lastNode,true);
                    break;
                }else {
                    lastNode = node;
                }
            }
            synchronized (this){
                wait();
            }

            return true;

        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public void close() throws Exception {
        zooKeeper.delete(znode,-1);
        zooKeeper.close();
        log.info("我已经释放了锁!");
    }

    @Override
    public void process(WatchedEvent event) {
        if (event.getType() == Event.EventType.NodeDeleted){
            synchronized (this){
                notify();
            }
        }
    }
}
  • ZookeeperController.java
@RestController
public class ZookeeperController {
    @Autowired
    private CuratorFramework client;

    @RequestMapping("zkLock")
    public String zookeeperLock(){
        try (ZkLock zkLock = new ZkLock()) {
            if (zkLock.getLock("order")){
                Thread.sleep(10000);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "方法执行完成!";
    }
}