zookeeper实用(2):实现分布式锁

场景:

分布式锁主要用于在分布式环境中保护跨进程、跨主机、跨网络的共享资源实现互斥访问,以达到保证数据的一致性。

在分布式环境下分布式锁的使用场景,比如秒杀情况,分布式任务,限制访问(同一时间只能发起一次)等。在单机环境中不推荐使用分布式锁,因为分布式锁使用会有一些性能开销的。

分布式锁的实现方式很多,比如DB,redis,zookeeper或者自己实现锁服务。

测试代码:

package com.example.demo.util;

import org.I0Itec.zkclient.ZkClient;

import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public final class DLock
{
    private ZkClient zkClient;
    private String lockName;
    private String thisReadLock;
    private String thisWriteLock;

    /**
     * 分布式锁连接zookeeper 以及 初始化锁主节点
     *
     * @param hostUrl  zookeeper 连接url
     * @param lockName 锁主节点
     */
    public void connect(String hostUrl , String lockName)
    {
        this.lockName = lockName;
        zkClient = new ZkClient(hostUrl);
        if (!zkClient.exists(lockName))
            zkClient.createPersistent(lockName);
    }

    /**
     * 获取读锁
     */
    public void lockRead()
    {
        CountDownLatch readLatch = new CountDownLatch(1);
        // 创建此临时节点, 获取带有顺序号的完整节点
        String thisLockNodeBuilder = lockName + "/" + LockType.READ + "-";
        thisReadLock = zkClient.createEphemeralSequential(thisLockNodeBuilder , "");

        // 找到此读锁前一个写锁。 找到节点的子节点
        List<String> tmp_nodes = zkClient.getChildren(lockName);
        sortNodes(tmp_nodes);
        tmp_nodes.forEach(System.out::println);

        int tmp_index = 0;
        // 倒序循环,直到读锁前面所有的写锁释放,读锁才执行
        for (int i = tmp_nodes.size() - 1; i >= 0; i--)
        {
            if (thisReadLock.equals(lockName + "/" + tmp_nodes.get(i)))
            {
                tmp_index = i;
            } else if (i < tmp_index && tmp_nodes.get(i).split("-")[0].equals(LockType.WRITE.toString()))
            {
                // 找到当前读锁之前的一个写锁
                // 先监听此写锁(监听该写锁是否释放),再阻塞当前读锁
                zkClient.subscribeChildChanges(lockName + "/" + tmp_nodes.get(i) , (parentPath , currentChilds) -> readLatch.countDown());

                try
                {
                    readLatch.await();
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                break;
            }
        }
    }

    /**
     * 释放读锁
     */
    public void unLockRead()
    {
        if (this.thisReadLock != null)
        {
            zkClient.delete(thisReadLock);
            thisReadLock = null;
        }
    }

    /**
     * 获取写锁
     */
    public void lockWrite()
    {
        CountDownLatch writeLatch = new CountDownLatch(1);
        // 创建此临时节点, 获取带有顺序号的完整节点
        String thisLockNodeBuilder = lockName + "/" + LockType.WRITE + "-";
        thisWriteLock = zkClient.createEphemeralSequential(thisLockNodeBuilder , "");

        List<String> tmp_nodes = zkClient.getChildren(lockName);
        sortNodes(tmp_nodes);
        // 倒序循环,直到拿到最前面的写锁,它释放了,后面的写锁才能执行
        for (int i = tmp_nodes.size() - 1; i >= 0; i--)
        {
            if (thisWriteLock.equals(lockName + "/" + tmp_nodes.get(i)))
            {
                // 在锁列表中找到此写锁
                if (i > 0)
                {
                    // 如果此写锁前面还有锁
                    // 监听前面的锁(监听前面的锁是否释放), 然后阻塞当前写锁获取
                    zkClient.subscribeChildChanges(lockName + "/" + tmp_nodes.get(i - 1) , (parentPath , currentChilds) -> writeLatch.countDown());

                    try
                    {
                        // 阻塞当前写锁获取
                        writeLatch.await();
                    } catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                    break;
                }
            }
        }
    }

    /**
     * 释放写锁
     */
    public void unLockWrite()
    {
        if (thisWriteLock != null)
        {
            zkClient.delete(thisWriteLock);
            thisWriteLock = null;
        }
    }

    /**
     * 重新对节点进行排序,按照顺序号排序
     *
     * @param nodes 临时节点
     */
    private void sortNodes(List<String> nodes)
    {
        nodes.sort(Comparator.comparing(o -> o.split("-")[1]));
    }

    /**
     * 锁类型枚举
     */
    private enum LockType
    {
        READ,
        WRITE;
    }
}

模拟多客户端并发请求:

import com.example.demo.util.DLock;
import org.springframework.beans.factory.annotation.Value;

public class TestDLock {

    private static String hostUrl = "127.0.0.1:2181";

    public static void main(String[] args) {
        dLockClient1();
        dLockClient2();
        dLockClient3();
        dLockClient4();
    }

    public static void dLockClient1(){
        // 客户端1
        DLock lock=new DLock();
        lock.connect(hostUrl, "/lock");
        lock.lockRead();
        System.out.println("I am testNode 1");
        try
        {
            System.out.println("I am TestNode 1");
            System.out.println("睡眠10s 之后释放分布式读锁, 开始倒计时");
            for (int i = 0; i < 10; i++)
            {
                System.out.println(10-i);
                Thread.sleep(1000);
            }
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        lock.unLockRead();
    }

    public static void dLockClient2(){
        // 客户端2
        DLock lock=new DLock();
        lock.connect(hostUrl, "/lock");
        lock.lockRead();
        System.out.println("I am TestNode 2");
        lock.unLockRead();
    }

    public static void dLockClient3(){
        // 客户端3
        DLock lock=new DLock();
        lock.connect(hostUrl, "/lock");
        lock.lockWrite();
        System.out.println("I am testNode 3");
        System.out.println("睡眠10s 之后释放分布式写锁, 开始倒计时");
        for (int i = 0; i < 10; i++)
        {
            System.out.println(10-i);
            try
            {
                Thread.sleep(1000);
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
        lock.unLockWrite();
    }

    public static void dLockClient4(){
        // 客户端4
        DLock lock=new DLock();
        lock.connect(hostUrl, "/lock");
        lock.lockRead();
        System.out.println("I am TestNode 4");
        lock.unLockRead();
    }
}

运行结果:

READ-0000000000
I am testNode 1
I am TestNode 1
睡眠10s 之后释放分布式读锁, 开始倒计时
10
9
8
7
6
5
4
3
2
1
17:44:29.643 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Got ping response for sessionid: 0x100026588980000 after 12ms
17:44:29.804 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Reading reply sessionid:0x100026588980000, packet:: clientPath:null serverPath:null finished:false header:: 5,2  replyHeader:: 5,30064771079,0  request:: '/lock/READ-0000000000,-1  response:: null
17:44:29.805 [main] DEBUG org.I0Itec.zkclient.ZkConnection - Creating new ZookKeeper instance to connect to 127.0.0.1:2181.
17:44:29.805 [main] INFO org.apache.zookeeper.ZooKeeper - Initiating client connection, connectString=127.0.0.1:2181 sessionTimeout=30000 watcher=org.I0Itec.zkclient.ZkClient@1e643faf
17:44:29.805 [ZkClient-EventThread-15-127.0.0.1:2181] INFO org.I0Itec.zkclient.ZkEventThread - Starting ZkClient event thread.
17:44:29.809 [main] DEBUG org.I0Itec.zkclient.ZkClient - Awaiting connection to Zookeeper server
17:44:29.809 [main] INFO org.I0Itec.zkclient.ZkClient - Waiting for keeper state SyncConnected
17:44:29.810 [main-SendThread(127.0.0.1:2181)] INFO org.apache.zookeeper.ClientCnxn - Opening socket connection to server 127.0.0.1/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error)
17:44:29.812 [main-SendThread(127.0.0.1:2181)] INFO org.apache.zookeeper.ClientCnxn - Socket connection established to 127.0.0.1/127.0.0.1:2181, initiating session
17:44:29.812 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Session establishment request sent on 127.0.0.1/127.0.0.1:2181
17:44:29.827 [main-SendThread(127.0.0.1:2181)] INFO org.apache.zookeeper.ClientCnxn - Session establishment complete on server 127.0.0.1/127.0.0.1:2181, sessionid = 0x100026588980001, negotiated timeout = 30000
17:44:29.827 [main-EventThread] DEBUG org.I0Itec.zkclient.ZkClient - Received event: WatchedEvent state:SyncConnected type:None path:null
17:44:29.827 [main-EventThread] INFO org.I0Itec.zkclient.ZkClient - zookeeper state changed (SyncConnected)
17:44:29.828 [main-EventThread] DEBUG org.I0Itec.zkclient.ZkClient - Leaving process event
17:44:29.828 [main] DEBUG org.I0Itec.zkclient.ZkClient - State is SyncConnected
17:44:29.838 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Reading reply sessionid:0x100026588980001, packet:: clientPath:null serverPath:null finished:false header:: 1,3  replyHeader:: 1,30064771080,0  request:: '/lock,F  response:: s{30064771077,30064771077,1607247859574,1607247859574,0,2,0,0,0,0,30064771079} 
17:44:29.847 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Reading reply sessionid:0x100026588980001, packet:: clientPath:null serverPath:null finished:false header:: 2,1  replyHeader:: 2,30064771081,0  request:: '/lock/READ-,#ffffffacffffffed057400,v{s{31,s{'world,'anyone}}},3  response:: '/lock/READ-0000000001 
17:44:29.851 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Reading reply sessionid:0x100026588980001, packet:: clientPath:null serverPath:null finished:false header:: 3,8  replyHeader:: 3,30064771081,0  request:: '/lock,F  response:: v{'READ-0000000001} 
READ-0000000001
I am TestNode 2
17:44:29.859 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Reading reply sessionid:0x100026588980001, packet:: clientPath:null serverPath:null finished:false header:: 4,2  replyHeader:: 4,30064771082,0  request:: '/lock/READ-0000000001,-1  response:: null
17:44:29.860 [main] DEBUG org.I0Itec.zkclient.ZkConnection - Creating new ZookKeeper instance to connect to 127.0.0.1:2181.
17:44:29.860 [main] INFO org.apache.zookeeper.ZooKeeper - Initiating client connection, connectString=127.0.0.1:2181 sessionTimeout=30000 watcher=org.I0Itec.zkclient.ZkClient@6e8dacdf
17:44:29.860 [ZkClient-EventThread-18-127.0.0.1:2181] INFO org.I0Itec.zkclient.ZkEventThread - Starting ZkClient event thread.
17:44:29.862 [main] DEBUG org.I0Itec.zkclient.ZkClient - Awaiting connection to Zookeeper server
17:44:29.862 [main] INFO org.I0Itec.zkclient.ZkClient - Waiting for keeper state SyncConnected
17:44:29.862 [main-SendThread(127.0.0.1:2181)] INFO org.apache.zookeeper.ClientCnxn - Opening socket connection to server 127.0.0.1/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error)
17:44:29.863 [main-SendThread(127.0.0.1:2181)] INFO org.apache.zookeeper.ClientCnxn - Socket connection established to 127.0.0.1/127.0.0.1:2181, initiating session
17:44:29.863 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Session establishment request sent on 127.0.0.1/127.0.0.1:2181
17:44:29.870 [main-SendThread(127.0.0.1:2181)] INFO org.apache.zookeeper.ClientCnxn - Session establishment complete on server 127.0.0.1/127.0.0.1:2181, sessionid = 0x100026588980002, negotiated timeout = 30000
17:44:29.870 [main-EventThread] DEBUG org.I0Itec.zkclient.ZkClient - Received event: WatchedEvent state:SyncConnected type:None path:null
17:44:29.870 [main-EventThread] INFO org.I0Itec.zkclient.ZkClient - zookeeper state changed (SyncConnected)
17:44:29.871 [main-EventThread] DEBUG org.I0Itec.zkclient.ZkClient - Leaving process event
17:44:29.871 [main] DEBUG org.I0Itec.zkclient.ZkClient - State is SyncConnected
17:44:29.875 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Reading reply sessionid:0x100026588980002, packet:: clientPath:null serverPath:null finished:false header:: 1,3  replyHeader:: 1,30064771083,0  request:: '/lock,F  response:: s{30064771077,30064771077,1607247859574,1607247859574,0,4,0,0,0,0,30064771082} 
17:44:29.882 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Reading reply sessionid:0x100026588980002, packet:: clientPath:null serverPath:null finished:false header:: 2,1  replyHeader:: 2,30064771084,0  request:: '/lock/WRITE-,#ffffffacffffffed057400,v{s{31,s{'world,'anyone}}},3  response:: '/lock/WRITE-0000000002 
17:44:29.885 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Reading reply sessionid:0x100026588980002, packet:: clientPath:null serverPath:null finished:false header:: 3,8  replyHeader:: 3,30064771084,0  request:: '/lock,F  response:: v{'WRITE-0000000002} 
I am testNode 3
睡眠10s 之后释放分布式写锁, 开始倒计时
10
9
8
7
6
5
4
3
2
1
17:44:39.790 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Got ping response for sessionid: 0x100026588980000 after 5ms
17:44:39.864 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Got ping response for sessionid: 0x100026588980001 after 3ms
17:44:39.898 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Got ping response for sessionid: 0x100026588980002 after 5ms
17:44:39.992 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Reading reply sessionid:0x100026588980002, packet:: clientPath:null serverPath:null finished:false header:: 4,2  replyHeader:: 4,30064771085,0  request:: '/lock/WRITE-0000000002,-1  response:: null
17:44:39.993 [main] DEBUG org.I0Itec.zkclient.ZkConnection - Creating new ZookKeeper instance to connect to 127.0.0.1:2181.
17:44:39.994 [main] INFO org.apache.zookeeper.ZooKeeper - Initiating client connection, connectString=127.0.0.1:2181 sessionTimeout=30000 watcher=org.I0Itec.zkclient.ZkClient@7a79be86
17:44:39.994 [ZkClient-EventThread-21-127.0.0.1:2181] INFO org.I0Itec.zkclient.ZkEventThread - Starting ZkClient event thread.
17:44:40.002 [main] DEBUG org.I0Itec.zkclient.ZkClient - Awaiting connection to Zookeeper server
17:44:40.002 [main] INFO org.I0Itec.zkclient.ZkClient - Waiting for keeper state SyncConnected
17:44:40.003 [main-SendThread(127.0.0.1:2181)] INFO org.apache.zookeeper.ClientCnxn - Opening socket connection to server 127.0.0.1/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error)
17:44:40.005 [main-SendThread(127.0.0.1:2181)] INFO org.apache.zookeeper.ClientCnxn - Socket connection established to 127.0.0.1/127.0.0.1:2181, initiating session
17:44:40.005 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Session establishment request sent on 127.0.0.1/127.0.0.1:2181
17:44:40.021 [main-SendThread(127.0.0.1:2181)] INFO org.apache.zookeeper.ClientCnxn - Session establishment complete on server 127.0.0.1/127.0.0.1:2181, sessionid = 0x100026588980003, negotiated timeout = 30000
17:44:40.021 [main-EventThread] DEBUG org.I0Itec.zkclient.ZkClient - Received event: WatchedEvent state:SyncConnected type:None path:null
17:44:40.021 [main-EventThread] INFO org.I0Itec.zkclient.ZkClient - zookeeper state changed (SyncConnected)
17:44:40.021 [main-EventThread] DEBUG org.I0Itec.zkclient.ZkClient - Leaving process event
17:44:40.022 [main] DEBUG org.I0Itec.zkclient.ZkClient - State is SyncConnected
17:44:40.032 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Reading reply sessionid:0x100026588980003, packet:: clientPath:null serverPath:null finished:false header:: 1,3  replyHeader:: 1,30064771086,0  request:: '/lock,F  response:: s{30064771077,30064771077,1607247859574,1607247859574,0,6,0,0,0,0,30064771085} 
17:44:40.042 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Reading reply sessionid:0x100026588980003, packet:: clientPath:null serverPath:null finished:false header:: 2,1  replyHeader:: 2,30064771087,0  request:: '/lock/READ-,#ffffffacffffffed057400,v{s{31,s{'world,'anyone}}},3  response:: '/lock/READ-0000000003 
17:44:40.046 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Reading reply sessionid:0x100026588980003, packet:: clientPath:null serverPath:null finished:false header:: 3,8  replyHeader:: 3,30064771087,0  request:: '/lock,F  response:: v{'READ-0000000003} 
READ-0000000003
I am TestNode 4
17:44:40.052 [main-SendThread(127.0.0.1:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Reading reply sessionid:0x100026588980003, packet:: clientPath:null serverPath:null finished:false header:: 4,2  replyHeader:: 4,30064771088,0  request:: '/lock/READ-0000000003,-1  response:: null

Process finished with exit code 0

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值