分布式锁的zookeeper实现

    最近看了一遍,分布式锁的实现,以为都看懂了。上手还是遇到一些问题,所以写下这篇博客,作为参考,废话不多说,先上一篇代码:

OrderCodeGenerator :

/**
 * orderCode 生成类 这个是我们要操作的目标对象
 * Created by Dell on 2018/5/15.
 */
public class OrderCodeGenerator {
    private int i = 1;

    public String getOrderCode() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss " + (i++));
        String s = simpleDateFormat.format(new Date());
        System.out.println(s);
        return s;
    }
}

    模拟数据层,生成以时间+序号 的订单 编号

OrderCodeServiceImpl:

/**
 * 业务实现层
 * Created by Dell on 2018/5/15.
 */
public class OrderCodeServiceImpl {
    final static OrderCodeGenerator ocg = new OrderCodeGenerator();
    private CountDownLatch cdl;

    public OrderCodeServiceImpl(int size) {
        cdl = new CountDownLatch(size);
    }
    //自定义锁的实现
    Lock lock = new DistributedLock("/test");

    public void createOrder() {
        new Thread(new Runnable() {
            public void run() {
                try {
                    lock.lock();
                    cdl.await();//阻塞等待,所有线程创建完毕。模拟并发情况
                    ocg.getOrderCode();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }).start();
    }

    /**
     * 计数递减的方法
     */
    public void countDown() {
        this.cdl.countDown();
    }
}
 业务层,调用数据层生成订单编号,其中 final关键字是必须的,这里模拟多个服务间共有变量,cdl.await(),阻塞线程,模拟并发请求。size标识并发数,从上游传过来 ;cdl.countDown()没生成一个线程,计数减一。


DistributedLock:

package com.wera.chapter1;

import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * Created by Dell on 2018/5/15.
 */
public class DistributedLock implements Lock {
    private ZkClient zkClient;
    private String parentPath = "";
    private ThreadLocal<String> currentPath = new ThreadLocal<String>();
    private ThreadLocal<String> beforePath = new ThreadLocal<String>();
    CountDownLatch cdl = new CountDownLatch(1);

    public DistributedLock(String parentPath) {
        this.parentPath = parentPath;
        //建立链接
        zkClient = new ZkClient("localhost:2181");
        //序列话接口
        zkClient.setZkSerializer(new MySerializer());

        if (!zkClient.exists(this.parentPath)) {
            zkClient.createPersistent(this.parentPath);
        }

    }

    public void lock() {
        if (!tryLock()) {//未获取到监听
            waitForLock(); //注册监听,阻塞自己
            lock();//递归调用自身
        }
    }

    public void lockInterruptibly() throws InterruptedException {

    }

    //注册监听 监听成功就阻塞自己
    public void waitForLock() {
        IZkDataListener listener = new IZkDataListener() {
            public void handleDataChange(String s, Object o) throws Exception {
                System.out.println("节点 " + s + " 的数据发生了变化" + o);
            }

            public void handleDataDeleted(String s) throws Exception {
                System.out.println("节点 " + s + " 被删除");
                cdl.countDown();
            }
        };
            zkClient.subscribeDataChanges(beforePath.get(), listener);
            if (zkClient.exists(beforePath.get())) {
                try {
                    cdl.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            zkClient.unsubscribeDataChanges(beforePath.get(), listener);
    }

    /**
     * 临时顺序节点有一下两个特性:
     * 1)断开链接时候,节点删除
     * 2)它是有序递增的
     * 我们为每个线程建立一个节点,建立节点成功就表示拿到了锁lock,并用TheadLocal接收,同时返回当前 节点的名称
     * 父节点下的所有子节点可以排序,确保最小的节点为当前节点即可。如果当前节点不是最小节点,我们只关注当前节点的前一个节点,
     * 对它加监听,当前 节点 删除时唤醒线程 ;
     * 前节点依然存在的时候阻塞线程。
     * 释放锁的过程就是删除前一节点
     * 有几种情况:
     * 1)子节点列表为空,表示第一个拿到锁,同时创建节点,这个时候,当前 节点等同于前节点。
     * 2)子节点列表不为空,但是当前节点为空,表示当前 线程并没有创建节点,有其他的线程创建过节点,这个时候创建本线程的节点,递归 继续
     * 3)子节点列表不为空且长度等于1,当前节点也不为空.这个时候就说明了只有当前线程创建了节点。所以这个 时候也是拿到了锁。这个时候前节点等同于当前节点
     * 4)子节点列表不为空且长度也大于1,当前节点不为空,并且当前节点不再列表的首位,需要继续监听,阻塞,这个时候前节点不等同于当前节点
     * 5)子节点列表不为空且长度大于1,当前节点不为空,并且 当前节点再列表的首位,说明拿到了锁。这个时候前节点等同于当前节点
     * @return
     */
    public boolean tryLock() {
       boolean isback = false;
       List<String> children = zkClient.getChildren(parentPath);
       if (null == children && children.size() == 0 ) { //当前未创建节点 ,创建节点,于是获得了锁
            currentPath.set(zkClient.createEphemeralSequential(this.parentPath+"/","aa"));
            beforePath.set(currentPath.get()); //这一种情况只有一个
            isback = true;
       } else {

           if (null == currentPath.get()) { //本线程创建一个节点
               currentPath.set(zkClient.createEphemeralSequential(this.parentPath+"/","aa"));
               return tryLock();
           }else { //该线程已经创建过节点了。
               if (children.size() == 1) { //当前线程创建了一个节点,并且该父节点下的子节点数是1,也就是说只有当前线程创建了一个节点,所以它也拿到了锁
                   isback = true;
                   beforePath.set(currentPath.get());//这一种情况只有一个
               }else {
                   Collections.sort(children);
                   String curent = currentPath.get().substring(this.parentPath.length() + 1);
                   int currentIndex = children.indexOf(curent);
                   if (currentIndex > 0) {
                       beforePath.set(this.parentPath + "/" + children.get(currentIndex - 1));
                       isback = false;
                   }else {
                       isback = true;
                       beforePath.set(currentPath.get());//这一种情况只有一个
                   }
               }

           }
       }
       return  isback;
    }


    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    public void unlock() {
            zkClient.delete(beforePath.get());
    }

    public Condition newCondition() {
        return null;
    }
}

自定义锁的实现,实现逻辑是利用zookeeper特性,如分布式,同一父节点下的节点唯一(不可重复),详细请参见 注释。

OrderCodeTest:

/**
 * Created by Dell on 2018/5/15.
 */
public class OrderCodeTest {

    public static void main(String[] args) throws InterruptedException {
        int size = 20;
        OrderCodeServiceImpl orderCodeService = new OrderCodeServiceImpl(size);
        for (int i = 0; i < size; i++) {
            orderCodeService.createOrder();//调用业务层的逻辑
            orderCodeService.countDown();//调用一次计数减一次
        }

    }
}

  程序入口,size=20,模拟20个请求 。同时调用计数器,当计数为0,模拟并发请求情景。


控制台打印结果如下:

log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
2018-05-15 19:09:17 1
节点 /test/0000002810 被删除
2018-05-15 19:09:17 2
节点 /test/0000002811 被删除
2018-05-15 19:09:18 3
2018-05-15 19:09:18 4
节点 /test/0000002812 被删除
2018-05-15 19:09:18 5
节点 /test/0000002814 被删除
2018-05-15 19:09:18 6
2018-05-15 19:09:18 7
节点 /test/0000002815 被删除
节点 /test/0000002816 被删除
2018-05-15 19:09:18 8
2018-05-15 19:09:18 9
节点 /test/0000002817 被删除
2018-05-15 19:09:18 10
2018-05-15 19:09:18 11
2018-05-15 19:09:18 12
节点 /test/0000002820 被删除
2018-05-15 19:09:18 13
2018-05-15 19:09:19 14
节点 /test/0000002822 被删除
2018-05-15 19:09:19 15
节点 /test/0000002823 被删除
2018-05-15 19:09:19 16
节点 /test/0000002824 被删除
节点 /test/0000002825 被删除
2018-05-15 19:09:19 17
2018-05-15 19:09:19 18
节点 /test/0000002826 被删除
2018-05-15 19:09:19 19
2018-05-15 19:09:19 20
节点 /test/0000002828 被删除

Disconnected from the target VM, address: '127.0.0.1:52753', transport: 'socket'

点击查看gitHub地址,如果打不开请 复制以下地址浏览器中打开:

https://github.com/wera0817/learnroot

猜你喜欢

转载自blog.csdn.net/wera0601/article/details/80327513