Skip to content

分布式锁

  • mysql
  • redis 的 setnx
  • redission
  • redLock
  • zookeeper
  • curator

redis的分布式锁可用性更高,但是分布式不友好。一般单机的redis实现分布式锁性能就够用。

如果非要要求可靠性,可以选择zk,只是zk是cp的,性能要差一点。

Reids分布式锁

redis实现分布式锁

手写 zk 分布式锁

zk 实现分布式锁,是依赖 zk 的临时有序节点

多个线程在 rootPath 下面按顺序创建节点。

  1. 首先有持久节点lock
  2. 每个请求获取锁的时候会在持久节点lock下创建一个临时节点
  3. 创建临时节点之后会判断自己是不是最小的节点,如果是,代表获取到锁。如果不是,则watch上一个节点。
  4. 获取锁的节点删除临时节点时,下一个节点就能获取到锁。这样能避免多线程竞争,类似于排队等锁。
java
@Slf4j
public class ZkLockUtil implements AutoCloseable {

    private final ZooKeeper zooKeeper;

    private String zNode;

    public ZkLockUtil() {
        ZooKeeper zooKeeperClient = null;
        try {
            zooKeeperClient = new ZooKeeper("localhost:2181", 100000, null);
        } catch (IOException e) {
            e.printStackTrace();
        }
        this.zooKeeper = zooKeeperClient;
    }

    @Override
    public void close() throws Exception {
        zooKeeper.delete(zNode, -1);
    }

    public boolean getLock(String businessCode) {
        try {
            //判断根节点
            judgeRootPath(businessCode);

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

            //序号最小的获取到锁
            List<String> childrens = zooKeeper.getChildren("/" + businessCode, false);
            Collections.sort(childrens);
            String minZnode = childrens.get(0);

            //创建的节点是最小节点,就获取锁
            if (zNode.endsWith(minZnode)) {
                return true;
            }

            //监听上一个节点的删除事件
            String preNode = minZnode;
            CountDownLatch countDownLatch = new CountDownLatch(1);

            for (String cruZnode : childrens) {
                if (zNode.endsWith(cruZnode)) {
                    //watch preZNode,当 preZNode 删除的时候触发
                    zooKeeper.exists("/" + businessCode + "/" + preNode, watchedEvent -> {
                        countDownLatch.countDown();
                    });
                } else {
                    preNode = cruZnode;
                }
            }

            //阻塞自身,让出锁
            synchronized (this) {
                countDownLatch.await();
            }

            //当被唤醒的时候,说明上个节点被删除了,此时获取到锁
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    private void judgeRootPath(String businessCode) {
        try {
            //判断根节点是否存在
            Stat exists = zooKeeper.exists("/" + businessCode, false);
            //节点不存在,首次创建
            if (Objects.isNull(exists)) {
                //创建根节点目录
                //永久节点
                zooKeeper.create("/" + businessCode, businessCode.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (KeeperException | InterruptedException e) {
            log.error("zk judge root path error:{}",e.getMessage());
        }
    }

}

分布式锁常见实现方案总结

Curator

Curator 封装了 zk 实现分布式锁的细节。

java
    @SneakyThrows
    public void create(int num) {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework client = CuratorFrameworkFactory.newClient("localhost:2181", retryPolicy);
        client.start();
        InterProcessMutex lock = new InterProcessMutex(client, "/" + ORDER_KEY);
        try {
            if (lock.acquire(30, TimeUnit.SECONDS)) {
                log.info("{} get lock", num);
                Thread.sleep(600);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            log.info("{} release lock", num);
            lock.release();
            client.close();
        }
    }