架构师之路(十二)之探讨一下高并发场景下分布式锁该用哪种方式实现较好?
什么是分布式锁? 分布式锁是控制分布式系统之间同步访问共享资源的一种方式。 在 分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证 一致性,这个时候,便需要使用到分布式锁。 各位兄弟姐妹们,上述介绍来自度娘,直接搬过来看的概念,如果还不明白,下面直接来张图,跟大家简单说一下分布式锁到底是干啥的. 图 1-1 我简单的画了一幅图,解释一下图: 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,以上是本人通过实践验证的三种较为常见的分布式锁的实现,各位读者如果有不同的见解欢迎各位来信指导!