分布式锁原理与实现

场景描述:

  小型电商网站,下单,生产有一定业务含义的唯一订单编号。

思路分析:

  如果单台服务器已无法撑起并发量,怎么办?集群?

  

 

分布式锁的用途:  

  

  在分布式环境下协同共享资源的使用。

 

 

分布式锁的特点:

  1.排他性: 只有一个线程能获取到。

  2.阻塞性: 其他未抢到的线程阻塞,直到锁释放出来,在抢。

  3.可重入性:线程获得锁后,后续是否可重复获取该锁。

 

我们掌握的计算机技术中,有哪些能提供排他性?

  1. 文件系统

  2. 数据库: 主键 唯一约束 for update

  3. 缓存redis: setnx

  4. zookeeper: 类似文件系统

 

常用的分布式锁实现技术

  1. 基于数据库实现分布式锁

    性能较差。容易出现单点故障。

    锁没有失效时间,容易死锁。

  2. 基于缓存实现分布式锁

    实现复杂

    存在死锁

  3. 基于zookeeper实现分布式锁

    实现相对简单

    可靠性高

    性能较好  

 

基于zookeeper实现分布式锁

  方式一:

   1.去获取锁创建节点

   2.获取锁成功,执行业务并且释放锁,等待唤醒。

   3. 获取锁失败,注册节点的watcher,阻塞等待,直到上一个成功获取锁释放到锁,才会取消watcher,尝试抢锁。

  特性:

    同父的子节点不可重名    

  假如部署在规模较大集群会发生'惊群效应'

   1.巨大的服务器性能损耗

   2.网络冲击

   3.可能造成宕机

  方式二: (解决方案)

   1.创建一个锁目录lock

   2.希望获得锁的线程A就在lock目录下,创建临时顺序节点

   3.获取锁目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁

   4.线程B获取所有节点,判断自己不是最小节点,设置监听(watcher)比自己次小的节点(只关注比自己次小的节点是为了防止发生“羊群效应”)

   5.线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是最小的节点,获得锁。

  特性:

   临时顺序节点

  代码实现

   
/**
 * zookeeper锁实现(临时顺序结点)
 * @author skymr
 *
 */
public class ZookeeperLock1 implements Lock, Watcher{
    
    
    public ZookeeperLock1(String url, int sessionTimeOut, String path){
        this.parrentPath = path;
        try {
            //url是zookepper服务器的地址
            zk = new ZooKeeper(url, sessionTimeOut, this);
            latch.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    //zk客户端
    private ZooKeeper zk;
    
    //结点路径
    private String parrentPath;
    
    //用于初始化zk的,zk连接是异步的,但连接成功后才能进行调用
    private CountDownLatch latch = new CountDownLatch(1);
    
    private static ThreadLocal<String> currentNodePath = new ThreadLocal<String>();
 
    public void lock() {
        if(!tryLock()){
            String mypath = currentNodePath.get();
            //如果尝试加锁失败,则进入等待
            synchronized(mypath){
                System.out.println(Thread.currentThread().getName() +" lock失败,进入等待");
                try {
                    mypath.wait();
                } catch (Exception e) {
                }
                System.out.println(Thread.currentThread().getName() +" lock等待完成");
            }
            //等待别人释放锁后,自己再去加锁
            lock();
        }
        else{
            System.out.println(Thread.currentThread().getName() +" lock成功");
        }
    }
 
    public void lockInterruptibly() throws InterruptedException {
        
    }
 
    public boolean tryLock() {
        try {
            //加锁代码是创建一个节点
            String mypath = currentNodePath.get();
            if(mypath == null){
                mypath = zk.create(parrentPath + "/", "111".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
                currentNodePath.set(mypath);
            }
            final String currentPath = mypath;
            List<String> allNodes = zk.getChildren(parrentPath, false);
            Collections.sort(allNodes);
            //不抛异常就表示创建成功啦
            String nodeName = mypath.substring((parrentPath + "/").length());
            if(allNodes.get(0).equals(nodeName)){
                //当前结点是最小的节点,获取锁成功
                return true;
            }
            else{
                //监听最小的结点
                String targetNodeName = parrentPath + "/" + allNodes.get(0);
                System.out.println(Thread.currentThread().getName() +" 需要等待节点删除" + targetNodeName);
                zk.exists(targetNodeName, new Watcher() {
                    public void process(WatchedEvent event) {
                        if(event.getType() == EventType.NodeDeleted){
                            synchronized(currentPath){
                                currentPath.notify();
                            }
                            System.out.println(Thread.currentThread().getName() +" 通知Lock等待中的线程重试加锁");
                        }                        
                    }
                });
            }
            return false;
        } catch (Exception e) {
            return false;
        }
    }
 
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }
 
    public void unlock() {
        try {
            //释放锁,删除节点
            String mypath = currentNodePath.get();
            if(mypath != null){
                System.out.println(Thread.currentThread().getName() +" 释放锁");
                zk.delete(mypath, -1);
                currentNodePath.remove();
            }
        } catch (Exception e) {
        }
    }
 
    public Condition newCondition() {
        return null;
    }
 
    public void process(WatchedEvent event) {
        System.out.println(event);
        if(event.getType() == EventType.None){
            //当连接上了服务器后,初始化完成
            latch.countDown();
        }
    }
 
}

 

 

posted @ 2018-12-03 10:26  Java菜得抠脚  阅读(554)  评论(0编辑  收藏  举报