• 周六. 7 月 27th, 2024

    架构师之路(十二)之探讨一下高并发场景下分布式锁该用哪种方式实现较好?

    root

    2 月 21, 2021 #架构师之路

    什么是分布式锁?

    分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在 分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证 一致性,这个时候,便需要使用到分布式锁。各位兄弟姐妹们,上述介绍来自度娘,直接搬过来看的概念,如果还不明白,下面直接来张图,跟大家简单说一下分布式锁到底是干啥的.

    我简单的画了一幅图,解释一下图:1) 三个用户,代表了三个不同请求,也就是三个不同的进程2) 三个主机,代表了分布式架构中多主机部署的情况3) 一个系统,代表了部署在主机上的应用4) 一个资源,代表了多用户访问相同资源的情况5) 增加了一把锁,而这个锁就是今天要说的分布式锁,它是由系统来控制,不同进程访问需要加锁控制,已求达到互斥访问,资源原子性的目的这就是分布式锁,这下应该明白了啥是分布式锁了吧,,,各位亲…那么我们为啥要用分布式锁,请听我接下来继续唠..

    为啥用分布式锁?

        其实在上一章节,这个问题就已经回答过了,通过上图的描述,除了介绍什么是分布式锁以外,其实隐含着回答了,我们为什么要用分布式锁,显而易见嘛,如果你不用分布式锁,大家都去争抢同一个资源,一定会造成资源的重复访问,重复操作,那么对业务而言将会产生很可怕的后果!

    失去锁,有何危害?

    各位读者,应该很庆幸能读到本片文章吧,嘻嘻,,大家试想一下,你们的公司业务有那么一个场景:

    1) 你们的系统部署在不同的主机或虚拟机上,并且实现了分布式部署,

    2)某一时刻,用户正在做业务,并且是处在高并发场景下,假设当前的多个相同业务请求落到了四台tomcat应用服务器上,那么就至少有四个进程

    要开始工作;

    3)这四个进程需要操作同一份订单数据,但很可惜的是,你们的业务系统并没有锁处理机制,导致了同一时刻,同一份订单数据在同一时间内被反复

    操作,修改甚至删除,并且每次请求之后,都会将结果响应给终端用户,那么这个时候除了造成业务数据恐怖的不一致以外,很有可能会造成数据库的假死,锁表等的现象,这在高并发业务场景中将会很可怕!

    分布式有何讲究?

        我相信,当前较为流行的应用部署一定是基于分布式结构,分布式思想提出也有N年的时间了,现在大家只要一说到系统部署应该首先想到的就是分布式的结构,没错,传统的集中式有太多的局限性了,我们这里不再说二者区别,相信大家比我都明白吧,,呵呵,

        在分布式世界中,有一个CAP的理论,C代表的一致性(Consistency),A代表的是高可应用行(Available),P代表的是高容错性(Partition tolerance),

    是说一个再牛逼哄哄的分布式系统,CAP也才能有其二同时满足,这也说明了鱼和熊掌不可兼得。在绝大多数的高并发业务场景中,要么牺牲数据的一致性来达到服务的高可用;要么牺牲服务的高可用,来达到数据的一致性、实时性、准确性!所以,构造一个完美的业务系统架构,其实还是挺难的。

    那么分布式系统只要遵循CAP原理,根据具体的业务要求,基本上都是可以实现的,那么分布式锁又需要具备哪些特点呢?

    分布式锁具备的特点

    大家不妨想一想,假设我们有个订单业务系统,需要定时处理订单的数据汇总业务,为防止同一个单子在同一时间被汇总多次,因此,我们需要加把锁,这把锁至少能隔离开不同的进程请求,或者说等当前的进程请求处理完毕,下一个进程才能接着处理下一个业务单子,操作同一份数据,那么至少得具备:

    1)该锁必须的是排他性的

    2)该锁必须是可重入的,如果有出现方法递归调用的时候,当前线程在最外层一旦获取了锁,那么该线程是可以继续使用这把锁访问被调用者的临界资源,

    这个地方说起来有点绕口哈,简单说来就是,A方法调用B方法,并且A获取了锁,这个时候如果B是原子操作的,那么A方法在释放锁之前,B方法同时也会获得该锁的资源。这么解释应该明白了哈。所以这个把锁必须是可以重入的,否则起不到临界资源隔离的作用。

    3)该锁必须得设置失效时间,否则会出现死锁等待的情况

    4)该锁必须得具备高可用性

    5)该锁必须具备高性能,因为频繁的获取锁、解锁会消耗cpu,以及内存

    6)该锁必须具备异步非阻塞的能力,也就是线程申请失败,要及时返回获取锁失败

    如何用分布式锁?

        分布式锁的实现,在业内较为流行的做法有三种:

        1)基于Mysql数据库的分布式锁

        2)基于Redis的分布式锁

        3)基于Zookeeper的分布式锁

        接下来,用干货来介绍这三种分布式锁的优缺点,

      基于Mysql数据库的分布式锁

            基于数据库的分布式锁原理其实很简单,我们可以利用主键的唯一性,来记录当前线程的操作,那么主键应该记录的是临界资源的值,并且该值是唯一的。数据表中,主要还存在该值,或者状态是某个值,我们就认为,该方法或资源已经被加锁了。

    案例说明

          废话少说吧,直接贴上代码:

      /**
       *  @Author: yuenbin
       *  @Date :2020/9/14
       * @Time :9:32
       * @Motto: It is better to be clear than to be clever !
       * @Destrib:  演示一下使用mysql的乐观锁,实现分布式锁
       * 给线程加锁
      **/
      public  void tryLock (String lockId,String ip ,String name ,int expire ){
    
          DistLock distLock = new DistLock() ;
          distLock.setLockId(lockId);
          distLock.setHolderIp(ip);
          distLock.setHolderName(name);
          distLock.setExpire(expire);
          distLock.setStatus("1");
          int affect = distLockService.insertSelective(distLock) ;
          if(affect > 0 )
          {
             log.debug(lockId+"被线程"+name+"持有,请稍等...!");
          }
    
      }
     @Scheduled(fixedRate = 1000)
        public void doTryMysqlLock(){
            log.debug("定时器执行了...");
            InetAddress inetAddress = null ;
            try {
                inetAddress = InetAddress.getLocalHost();
            }catch (UnknownHostException ex ){
                ex.printStackTrace();
            }
            String clientIp = inetAddress.getHostAddress() ;
    
            //尝试获取分布式锁
            boolean lock = tryLock("testDistLock", clientIp, Thread.currentThread().getName(), 2);
    
            if(lock){
                log.debug("分布式锁获得成功...");
                testDistLock();
                unlock("testDistLock");
                log.debug("释放分布式锁成功...");
            }else {
                log.debug("分布式锁获得失败...");
                DistLockExample distLockExample = new DistLockExample() ;
                distLockExample.createCriteria().andLockIdEqualTo("testDistLock")
                        .andStatusEqualTo("1");
                DistLock distLock = distLockService.selectFirstByExample(distLockExample);
                log.debug("分布式锁被{}占有,ip地址为:{}",distLock.getHolderName(),distLock.getHolderIp());
                return;
            }
        }
    

    解释一下:

        1)  我创建了一张表,表名叫dist_lock,表结构如下:    

    create table dist_lock
    (
    	id int not null auto_increment
    		primary key,
    	lock_id varchar(250) not null,
    	expire int null,
    	holder_name varchar(250) not null,
    	holder_ip varchar(100) null,
    	create_time datetime default CURRENT_TIMESTAMP null,
    	update_time datetime default CURRENT_TIMESTAMP null,
    	status char(4) null
    )
    ;
    
    comment on column dist_lock.lock_id is '分布式锁主键'
    ;
    
    comment on column dist_lock.expire is '分布式锁过期时间'
    ;
    
    comment on column dist_lock.holder_name is '分布式锁持有者'
    ;
    
    comment on column dist_lock.holder_ip is '客户端ip地址'
    ;
    
    

        2)  表中的lock_id表示临界资源值,status为1表示该资源被线程占有,也就是加了分布式锁,

        3)  表中的expire表示该锁资源的失效日期,锁不可能无限期占有,必须加一个过期时间加以控制

    我们来接着看一下这个过期时间是如何控制的,上干货:

       /**
         *  @Author: yuenbin
         *  @Date :2020/9/14
         * @Time :10:36
         * @Motto: It is better to be clear than to be clever !
         * @Destrib: 每隔15秒扫一次过期锁
        **/
        @Scheduled(fixedRate = 1000*5 )
        public void checkLock(){
    
           log.debug("分布式锁过期时间检查开始....");
    
                 try {
    
                     DistLockExample distLockExample = new DistLockExample() ;
                     distLockExample.createCriteria().andStatusEqualTo("1");
                     List<DistLock> lockList = distLockService.selectByExample(distLockExample) ;
                     if( null != lockList && lockList.size() > 0 ){
                         List<DistLock> filterList = lockList.stream().filter(distLock ->
                                 ifExpired(distLock.getLockId())
                         ).collect(Collectors.toList());
    
                         filterList.forEach(distLock -> {
                             unlock(distLock.getLockId());
                         });
                         log.debug("过期的锁一共有:{"+filterList.size()+"}条");
                     }
    
                 }catch (Exception ex ){
                     ex.printStackTrace();
                 }
    
        }
    /**
       *  @Author: yuenbin
       *  @Date :2020/9/14
       * @Time :10:17
       * @Motto: It is better to be clear than to be clever !
       * @Destrib: 当前分布式锁是否过期
      **/
      public boolean ifExpired(String lockId){
    
          boolean ifExpired =  false ;
    
          DistLockExample distLockExample = new DistLockExample() ;
          distLockExample.createCriteria().andLockIdEqualTo(lockId)
                  .andStatusEqualTo("1") ;
    
          DistLock distLock = null;//distLockService.selectFirstByExample(distLockExample) ;
    
          int expire = distLock.getExpire() ;
    
          long current = System.currentTimeMillis() ; //获取当前时间
    
          long diff =  expire * 1000 ; //预计过期时间 单位毫秒
    
          Date date = distLock.getCreateTime() ;
    
          long expect = date.getTime() + diff ;
    
          if(current > expect ){
              System.out.println(lockId+"已经过期了,准备解锁...");
              ifExpired = true ;
          }
    
          return ifExpired ;
    
      }
    

    解释一下:

        1)checkLock的目的是定时检测一下数据库中的lock资源是否在规定时间内过期,在正常的业务场景中,必须有这么一个机制,否则可能会出现死锁的情况

        2)如果有过期的情况出现,就要将该锁更新为失效,并为其他线程分配锁

    那么解锁的过程如何呢,接着上干货:

     /**
       *  @Author: yuenbin
       *  @Date :2020/9/14
       * @Time :10:10
       * @Motto: It is better to be clear than to be clever !
       * @Destrib:  解锁操作
      **/
      public void unlock(String lockId ){
    
          DistLock distLock = new DistLock();
          distLock.setStatus("0");
    
          DistLockExample distLockExample = new DistLockExample() ;
          distLockExample.createCriteria().andLockIdEqualTo(lockId)
                  .andStatusEqualTo("1") ;
    
              int affect = distLockService.updateByExampleSelective(distLock, distLockExample);
              if(affect >0 ){
                  log.debug(lockId+"解锁成功!");
          }
    
      }

           解锁操作其实很简单,只要满足特定的条件,把数据表中状态改掉即可。

           mysql的分布式锁执行我是使用的springMVC搭建的框架,用单元测试实现的,后续大家可根据自己的喜好,搭建实现方式,下面,给大家贴一下执行效果吧,干货上起:

    效果演示

        //to-do 准备贴一下执行效果

    执行效果

    线程抢占情况

    基于Redis的分布式锁

        Redis实现分布式锁的原理其实也不复杂,其核心思想是使用redis自带的setnx()并配合getset()来实现分布式锁。下面用一张图介绍一下redis分布式锁的实现原理:

    解释一下获取锁过程:

        1)某一时刻,有多个线程想要从redis获取分布式锁,操作redis,并调用setnx命令

        2)如果调用setnx命令,返回1表示,成功设置key,获取锁成功,如果返回0,表示该key已存在,并被其他线程给设置过。这里需要注意的是,为避免

    出现死锁的可能,比如redis突然挂掉,或网络断开,那么其他进程在尝试获取锁的时候,没有设置过期时间,上一个进程将会一直持续持有锁,就会造成

    死锁的发生,因此,在调用setnx命令的时候,需要将过期时间作为值赋给key。

        3)接着调用get命令,获取该key,需要判断一下该key是否过期,如果过期了,需要调用getset()命令,将新的锁过期时间设置一下,设置成功将自动获取到该分布式锁,getset()命令操作是原子性的。

        4)如果还未过期,则需要等待;

        释放锁过程:

         1)释放锁的时候,需要提前判断一下该锁是否过期,来保证多线程场景下的安全性,设想一下某一个线程拿到分布式锁之后,某一时刻该锁过期了,并且任务也执行完了,如果直接删除该锁,可能会把其他线程获取的锁也一并删除,这样直接操作会很危险

    命令介绍

    SETNX

    SETNX key value

    将 key 的值设为 value,当且仅当 key 不存在。
    若给定的 key 已经存在,则 SETNX 不做任何动作。
    SETNX 是SET if Not eXists的简写。

    返回值

    返回整数,具体为
    – 1,当 key 的值被设置
    – 0,当 key 的值没被设置

    案例说明

    废话少说,直接上干货:

    redis 上锁过程

      /**
         *  @Author: yuenbin
         *  @Date :2020/9/15
         * @Time :15:38
         * @Motto: It is better to be clear than to be clever !
         * @Destrib: 加锁操作 redis
        **/
        public boolean tryRedisLock(String key ,int expire ){
    
            long value = System.currentTimeMillis() + expire*1000 ;
            boolean flag = RedisUtil.setnx(key , String.valueOf(value));
            log.debug("当前获取锁状态{}",flag);
            if(flag){
                 return true ;
            }
    
            String oldExpired = RedisUtil.get(key) ;
    
            if(StringUtils.isNotBlank(oldExpired)){
    
                boolean ifExceed = Long.parseLong(oldExpired) < System.currentTimeMillis() ;
                log.debug("是否超时{}?",ifExceed);
                //如果超时了,,则进行设置
                if( ifExceed){
                    log.debug("redis--分布式锁超时,重新设置...");
                  String currentExpired =  RedisUtil.getAndSet(key,System.currentTimeMillis()+expire*1000+"" );
                  if(currentExpired.equalsIgnoreCase(oldExpired)){
                      return true ;
                  }
    
                }
    
            }
    
            return false ;
    
        }
    

    redis 释放锁过程

    /**
         *  @Author: yuenbin
         *  @Date :2020/9/15
         * @Time :15:37
         * @Motto: It is better to be clear than to be clever !
         * @Destrib: 解锁操作,,redis
        **/
        public void unRedisLock(String key ){
            String expired = RedisUtil.get(key) ;
            if(StringUtils.isNotBlank(expired)){
                //未超时,则释放该锁
                if(Long.parseLong(expired) >  System.currentTimeMillis()){
                    RedisUtil.del(key);
                    log.debug("redis--解锁成功 !");
                }
            }
    
        }

    效果演示

    基于Zookeeper的分布式锁

          zk实现的分布式锁既是一种公平锁,也是一种可重入锁。zk是当今互联网分布式架构体系中用的非常广泛的一种分布式协调器,非常多的互联网大厂都在

    使用zk来管理协调组件,管理服务等。为什么说zk是一种公平锁呢?因为zk的节点可以控制有序性,我们可以规定节点编号排在最前面的,优先有执行权,这不就是锁的实现么?因此,zk可以构造一种阻塞或非阻塞的分布式公平锁,下面一张图介绍一下,zk实现的锁原理机制:

    解释一下

    1)zk实现分布式锁主要借助于zk特有的一种节点监听机制,这种节点监听机制有点类似于击鼓传花玩法,也就是从一个节点到下一个节点,不会出现跨节点传递通知的情况。这种机制的非常好的一点就是,避免了羊群效应,某一个节点挂掉,不会引起其他节点的批量监听,并批量做出应急反应,这样直接避免了服务器某时刻可能的巨大压力。

    2)zk节点创建的时候,是有序的,有序的节点能保证获取锁的公平性。每个节点只需要关注或监听它的前一个节点,并保证自己当前的节点是所有节点中最小号,因为只有排在最前面,才有资格获取到锁。

    3)zk创建的节点是临时节点,临时节点不会长久存在于zk,这样避免了zk挂掉或者出现网络问题的时候,不会出现死锁的情况。

    案例说明

    zk实现分布式锁的过程,这里有个实例,废话少说,直接上干货:

     /**
         *  @Author: yuenbin
         *  @Date :2020/9/16
         * @Time :16:44
         * @Motto: It is better to be clear than to be clever !
         * @Destrib:  给节点上锁
        **/
        public     boolean tryLock(String lockName ){
            String currentNode="";
            try {
                if(currentNode == null || currentNode.equals("")){
                    currentNode = zkClient.createEphemeralSequential(rootNode+"/", "lock");
                    currentNodeLocal.set(currentNode);
                    System.out.println("["+currentNode+"]节点创建成功");
                }
    
                List<String> children =  zkClient.getChildren(rootNode);
                //升序排序
                Collections.sort(children);
    
                if(currentNodeLocal.get().equalsIgnoreCase(rootNode +"/"+children.get(0))){
                  return true ;
                }else
                {
                    // 寻找比tempNode小的节点,也就是排好序的当前节点的上一个位置节点
                    String beforeNode = children.get(Collections.binarySearch(children, currentNode.substring(currentNode.lastIndexOf("/")+1))-1);
                    beforeNode=rootNode + "/"+beforeNode ;
                    beforeNodeLocal.set(beforeNode);
                }
    
             } catch (Exception  e) {
    
            }
    
            return  false;
        }
    

    等待获取锁:

    public      void waitForLock()  {
            countDownLatch = new CountDownLatch(1);
            System.out.println("节点["+currentNodeLocal.get()+"]正在等待["+beforeNodeLocal.get()+"]");
            IZkDataListener iZkDataListener = new IZkDataListener() {
                @Override
                public void handleDataChange(String s, Object o) throws Exception {
    
                }
    
                @Override
                public void handleDataDeleted(String s) throws Exception {
                    if(countDownLatch !=null){
                        countDownLatch.countDown();
                    }
                }
            } ;
    
            zkClient.subscribeDataChanges(beforeNodeLocal.get(), iZkDataListener);
            if (zkClient.exists(beforeNodeLocal.get())) {
    
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            zkClient.unsubscribeDataChanges(beforeNodeLocal.get(), iZkDataListener);
    
        }

    解锁过程:

     /**
         *  @Author: yuenbin
         *  @Date :2020/9/16
         * @Time :16:44
         * @Motto: It is better to be clear than to be clever !
         * @Destrib:  给节点解锁
        **/
        public     void unlock() {
            try {
                zkClient.delete(currentNodeLocal.get());
                 System.out.println(currentNodeLocal.get() + "释放锁成功");
             } catch (Exception e) {
                e.printStackTrace();
             }
        }

    模拟多线程任务

    static class Task implements Runnable {
    
            private CountDownLatch count ;
            public Task(CountDownLatch count){
                this.count=count;
            }
            @Override
            public void run() {
                DistrLockByZookeeper distrLockByZookeeper = new DistrLockByZookeeper();
                distrLockByZookeeper.getLock("");
                while (sum > 0 ) {
                    try {
                        System.out.println("线程" + Thread.currentThread().getName() + "\t正在卖第" + (sum--) + "张票");
                    } catch (Exception ex) {
    
                    } finally {
                        distrLockByZookeeper.unlock();
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                count.countDown();
    
                }
        }
    

    main 方法展示

    public static void main(String[] args) {
    
            CountDownLatch count = new CountDownLatch(200) ;
            Task task = new Task(count);
            for(int i=1;i<=200;i++){
                new Thread(task, "pool-"+i).start();
            }
    
            try {
                count.await();
                if(count.getCount() == 0 ){
                    System.out.println("全部车票已售完");
                  }
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }

    主变量定义

    
        protected  static  ZkClient zkClient = new ZkClient("你的zk:端口", 10000);
    
        //定义需要等待的已经持有锁的节点
        private static ThreadLocal<String> beforeNodeLocal = new ThreadLocal<>() ;
    
        //当前节点路径
        private static ThreadLocal<String> currentNodeLocal  =new ThreadLocal<>();
    
        private static int sum=999;
    
    
        //根节点
        private static String rootNode ="/locks";
    
        //锁类型
        private static String lockFlag="-locking-";
    
    
        private static CountDownLatch countDownLatch =null ;
    
    
        public DistrLockByZookeeper(){
            if(!zkClient.exists(rootNode)){
                zkClient.createPersistent(rootNode);
            }
         }

    效果展示:

    我这里模拟的是999个车票,200个线程同时跑,模拟真实业务抢票场景,使用zk分布式锁,控制抢票过程,不会出现抢票重复,抢票死锁的现象。如下是效果部分展示

    [/locks/0000007507]节点创建成功
    [/locks/0000007508]节点创建成功
    [/locks/0000007510]节点创建成功
    [/locks/0000007512]节点创建成功
    [/locks/0000007511]节点创建成功
    [/locks/0000007509]节点创建成功
    [/locks/0000007513]节点创建成功
    节点[/locks/0000007452]正在等待[/locks/0000007451]
    节点[/locks/0000007468]正在等待[/locks/0000007467]
    节点[/locks/0000007471]正在等待[/locks/0000007470]
    线程pool-169	正在卖第999张票
    节点[/locks/0000007472]正在等待[/locks/0000007471]
    节点[/locks/0000007470]正在等待[/locks/0000007469]
    节点[/locks/0000007466]正在等待[/locks/0000007465]
    [/locks/0000007638]节点创建成功
    节点[/locks/0000007469]正在等待[/locks/0000007468]
    节点[/locks/0000007473]正在等待[/locks/0000007472]
    节点[/locks/0000007465]正在等待[/locks/0000007464]
    节点[/locks/0000007463]正在等待[/locks/0000007462]
    节点[/locks/0000007464]正在等待[/locks/0000007463]
    节点[/locks/0000007462]正在等待[/locks/0000007461]
    节点[/locks/0000007461]正在等待[/locks/0000007460]
    节点[/locks/0000007460]正在等待[/locks/0000007459]
    节点[/locks/0000007456]正在等待[/locks/0000007455]
    [/locks/0000007637]节点创建成功
    [/locks/0000007636]节点创建成功
    节点[/locks/0000007459]正在等待[/locks/0000007458]
    [/locks/0000007635]节点创建成功
    节点[/locks/0000007455]正在等待[/locks/0000007454]
    节点[/locks/0000007453]正在等待[/locks/0000007452]
    [/locks/0000007634]节点创建成功
    节点[/locks/0000007454]正在等待[/locks/0000007453]
    节点[/locks/0000007474]正在等待[/locks/0000007473]
    节点[/locks/0000007477]正在等待[/locks/0000007476]
    节点[/locks/0000007479]正在等待[/locks/0000007478]
    节点[/locks/0000007478]正在等待[/locks/0000007477]
    节点[/locks/0000007481]正在等待[/locks/0000007480]
    节点[/locks/0000007482]正在等待[/locks/0000007481]
    节点[/locks/0000007483]正在等待[/locks/0000007482]
    节点[/locks/0000007484]正在等待[/locks/0000007483]
    节点[/locks/0000007485]正在等待[/locks/0000007484]
    节点[/locks/0000007486]正在等待[/locks/0000007485]
    节点[/locks/0000007487]正在等待[/locks/0000007486]
    [/locks/0000007633]节点创建成功
    节点[/locks/0000007488]正在等待[/locks/0000007487]
    [/locks/0000007632]节点创建成功
    节点[/locks/0000007490]正在等待[/locks/0000007489]
    节点[/locks/0000007489]正在等待[/locks/0000007488]
    节点[/locks/0000007491]正在等待[/locks/0000007490]
    节点[/locks/0000007492]正在等待[/locks/0000007491]
    节点[/locks/0000007493]正在等待[/locks/0000007492]
    [/locks/0000007631]节点创建成功
    节点[/locks/0000007494]正在等待[/locks/0000007493]
    节点[/locks/0000007495]正在等待[/locks/0000007494]
    节点[/locks/0000007496]正在等待[/locks/0000007495]
    节点[/locks/0000007497]正在等待[/locks/0000007496]
    节点[/locks/0000007499]正在等待[/locks/0000007498]
    节点[/locks/0000007498]正在等待[/locks/0000007497]
    节点[/locks/0000007502]正在等待[/locks/0000007501]
    节点[/locks/0000007503]正在等待[/locks/0000007502]
    节点[/locks/0000007501]正在等待[/locks/0000007500]
    节点[/locks/0000007504]正在等待[/locks/0000007503]
    节点[/locks/0000007505]正在等待[/locks/0000007504]
    节点[/locks/0000007506]正在等待[/locks/0000007505]
    节点[/locks/0000007508]正在等待[/locks/0000007507]
    [/locks/0000007639]节点创建成功
    [/locks/0000007641]节点创建成功
    [/locks/0000007642]节点创建成功
    [/locks/0000007643]节点创建成功
    [/locks/0000007644]节点创建成功
    [/locks/0000007645]节点创建成功
    [/locks/0000007646]节点创建成功
    [/locks/0000007647]节点创建成功
    [/locks/0000007648]节点创建成功
    [/locks/0000007649]节点创建成功
    [/locks/0000007650]节点创建成功
    节点[/locks/0000007512]正在等待[/locks/0000007511]
    节点[/locks/0000007511]正在等待[/locks/0000007510]
    节点[/locks/0000007509]正在等待[/locks/0000007508]
    节点[/locks/0000007513]正在等待[/locks/0000007512]
    节点[/locks/0000007637]正在等待[/locks/0000007636]
    节点[/locks/0000007633]正在等待[/locks/0000007632]
    节点[/locks/0000007632]正在等待[/locks/0000007631]
    [/locks/0000007630]节点创建成功
    线程pool-120	正在卖第802张票
    线程pool-121	正在卖第803张票
    线程pool-165	正在卖第804张票
    线程pool-122	正在卖第805张票
    线程pool-126	正在卖第806张票
    /locks/0000007513释放锁成功
    线程pool-132	正在卖第807张票
    线程pool-23	正在卖第808张票
    线程pool-127	正在卖第809张票
    线程pool-118	正在卖第810张票
    线程pool-123	正在卖第811张票
    线程pool-119	正在卖第812张票
    /locks/0000007506释放锁成功
    线程pool-2	正在卖第813张票
    线程pool-166	正在卖第814张票
    线程pool-164	正在卖第815张票
    线程pool-130	正在卖第816张票
    线程pool-134	正在卖第817张票
    线程pool-117	正在卖第818张票
    线程pool-116	正在卖第819张票
    线程pool-115	正在卖第820张票
    线程pool-128	正在卖第821张票
    线程pool-129	正在卖第822张票
    /locks/0000007636释放锁成功
    /locks/0000007500释放锁成功
    /locks/0000007457释放锁成功
    线程pool-114	正在卖第823张票
    线程pool-131	正在卖第824张票
    线程pool-113	正在卖第825张票
    线程pool-133	正在卖第826张票
    线程pool-144	正在卖第827张票
    线程pool-112	正在卖第828张票
    线程pool-111	正在卖第829张票
    线程pool-105	正在卖第830张票
    线程pool-145	正在卖第831张票
    线程pool-107	正在卖第832张票
    线程pool-146	正在卖第833张票
    线程pool-104	正在卖第834张票
    线程pool-103	正在卖第835张票
    线程pool-147	正在卖第835张票
    线程pool-56	正在卖第836张票
    线程pool-149	正在卖第837张票
    线程pool-102	正在卖第838张票
    线程pool-106	正在卖第839张票
    线程pool-74	正在卖第840张票
    线程pool-83	正在卖第841张票
    线程pool-84	正在卖第842张票
    线程pool-76	正在卖第843张票
    线程pool-64	正在卖第844张票
    线程pool-85	正在卖第845张票
    线程pool-90	正在卖第846张票
    线程pool-92	正在卖第847张票
    线程pool-87	正在卖第848张票
    线程pool-96	正在卖第849张票
    线程pool-88	正在卖第850张票
    线程pool-95	正在卖第851张票
    线程pool-86	正在卖第852张票
    线程pool-89	正在卖第853张票
    线程pool-93	正在卖第854张票
    线程pool-97	正在卖第855张票
    线程pool-91	正在卖第856张票
    线程pool-94	正在卖第857张票
    线程pool-99	正在卖第858张票
    线程pool-110	正在卖第859张票
    线程pool-98	正在卖第860张票
    线程pool-101	正在卖第861张票
    线程pool-100	正在卖第862张票
    线程pool-108	正在卖第863张票
    线程pool-109	正在卖第864张票
    线程pool-52	正在卖第865张票
    线程pool-39	正在卖第866张票
    线程pool-75	正在卖第867张票
    线程pool-63	正在卖第868张票
    线程pool-51	正在卖第869张票
    线程pool-81	正在卖第870张票
    线程pool-19	正在卖第871张票
    线程pool-78	正在卖第872张票
    线程pool-50	正在卖第873张票
    线程pool-79	正在卖第874张票
    线程pool-80	正在卖第875张票
    线程pool-12	正在卖第876张票
    线程pool-31	正在卖第877张票
    线程pool-82	正在卖第878张票
    线程pool-53	正在卖第879张票
    线程pool-22	正在卖第880张票
    线程pool-77	正在卖第881张票
    线程pool-13	正在卖第882张票
    线程pool-11	正在卖第883张票
    线程pool-54	正在卖第884张票
    线程pool-21	正在卖第885张票
    线程pool-58	正在卖第886张票
    线程pool-24	正在卖第887张票
    线程pool-49	正在卖第888张票
    线程pool-14	正在卖第889张票
    线程pool-10	正在卖第890张票
    线程pool-20	正在卖第891张票
    线程pool-55	正在卖第892张票
    线程pool-59	正在卖第893张票
    线程pool-57	正在卖第894张票
    线程pool-48	正在卖第895张票
    线程pool-25	正在卖第896张票
    线程pool-199	正在卖第897张票
    线程pool-60	正在卖第898张票
    线程pool-16	正在卖第899张票
    线程pool-9	正在卖第900张票
    线程pool-61	正在卖第901张票
    线程pool-26	正在卖第902张票
    线程pool-65	正在卖第903张票
    线程pool-46	正在卖第904张票
    线程pool-66	正在卖第905张票
    线程pool-8	正在卖第906张票
    线程pool-67	正在卖第907张票
    线程pool-45	正在卖第908张票
    线程pool-68	正在卖第909张票
    线程pool-27	正在卖第910张票
    线程pool-69	正在卖第911张票
    线程pool-47	正在卖第912张票
    线程pool-70	正在卖第913张票
    线程pool-44	正在卖第914张票
    线程pool-71	正在卖第915张票
    线程pool-7	正在卖第916张票
    线程pool-72	正在卖第917张票
    线程pool-38	正在卖第918张票
    线程pool-30	正在卖第919张票
    线程pool-137	正在卖第920张票
    线程pool-73	正在卖第921张票
    线程pool-198	正在卖第922张票
    线程pool-187	正在卖第923张票
    线程pool-18	正在卖第924张票
    线程pool-15	正在卖第925张票
    线程pool-161	正在卖第926张票
    线程pool-150	正在卖第927张票
    线程pool-179	正在卖第928张票
    线程pool-196	正在卖第929张票
    线程pool-167	正在卖第930张票
    线程pool-193	正在卖第931张票
    线程pool-154	正在卖第932张票
    线程pool-135	正在卖第933张票
    线程pool-159	正在卖第934张票
    线程pool-188	正在卖第935张票
    线程pool-6	正在卖第16张票
    线程pool-163	正在卖第15张票
    线程pool-28	正在卖第14张票
    线程pool-152	正在卖第13张票
    /locks/0000007485释放锁成功
    /locks/0000007460释放锁成功
    线程pool-143	正在卖第7张票
    线程pool-182	正在卖第8张票
    线程pool-34	正在卖第9张票
    线程pool-158	正在卖第10张票
    线程pool-29	正在卖第11张票
    线程pool-33	正在卖第12张票
    /locks/0000007630释放锁成功
    /locks/0000007465释放锁成功
    /locks/0000007456释放锁成功
    /locks/0000007464释放锁成功
    /locks/0000007466释放锁成功
    /locks/0000007469释放锁成功
    /locks/0000007638释放锁成功
    /locks/0000007483释放锁成功
    /locks/0000007507释放锁成功
    /locks/0000007508释放锁成功
    /locks/0000007481释放锁成功
    /locks/0000007644释放锁成功
    /locks/0000007454释放锁成功
    /locks/0000007453释放锁成功
    /locks/0000007477释放锁成功
    /locks/0000007631释放锁成功
    /locks/0000007635释放锁成功
    /locks/0000007474释放锁成功
    /locks/0000007510释放锁成功
    /locks/0000007451释放锁成功
    /locks/0000007511释放锁成功
    /locks/0000007478释放锁成功
    /locks/0000007509释放锁成功
    /locks/0000007641释放锁成功
    /locks/0000007479释放锁成功
    /locks/0000007634释放锁成功
    /locks/0000007639释放锁成功
    线程pool-177	正在卖第6张票
    /locks/0000007452释放锁成功
    线程pool-36	正在卖第5张票
    线程pool-174	正在卖第4张票
    /locks/0000007636释放锁成功
    线程pool-125	正在卖第2张票
    线程pool-157	正在卖第1张票
    线程pool-138	正在卖第3张票
    /locks/0000007506释放锁成功
    /locks/0000007457释放锁成功
    /locks/0000007513释放锁成功
    /locks/0000007500释放锁成功
    全部车票已售完

    我该怎么选择呢?

        我想各位读者一定也会遇到类似的问题,在真实业务场景中,我们该如何进行抉择,该选择什么样的分布式锁实现方式呢?上文通过具体的实例、效果展示、验证等简单的总结如下,其实各有千秋,各有优缺:

     常用分布式锁实现比较  
     类型性能支持的锁线程阻塞高并发效率  
     mysql较弱乐观锁 较弱,容易锁表最低  
     redis最强既可以是公平锁也可以是非公平锁非阻塞最强最高  
     zookeeper中等公平锁既可以是阻塞也可以是非阻塞最大可支持10亿个节点中间  

    其实通过比较就能看出来,zk在高并发业务场景中还是逊色于redis的实现,而redis由于是在内存层级工作,因此效率是最高的。数据库实现分布式锁

    除了mysql以外,oracle也可以实现,但在高并发,多线程的业务场景中,频繁的I/O可能对业务数据库造成一定的压力,各位还是要谨慎选择。

    ok,以上是本人通过实践验证的三种较为常见的分布式锁的实现,各位读者如果有不同的见解欢迎各位来信指导!

    root