分布式锁场景秒杀场景案例对于商品秒杀场景,我们需要防止超卖库存或重复扣款等并发问题,我们通常需要使用分布式锁锁是用于解决共享资源竞争导致的数据不一致问题。以手机闪购为例,我们在抢购过程中通常有三个步骤:扣除相应商品的库存;2.为产品创建订单;3、用户付费。对于这样的场景,我们可以使用分布式锁来解决。比如我们可以在用户进入秒杀的“下单”链接时锁定商品库存,进而完成扣减库存等操作。手术完成后。释放锁让下一个用户继续进入,保证库存安全;也可以减少秒杀失败导致的DB回滚次数。整个流程如下图所示:注:锁的粒度要根据具体场景和需求来权衡。Zookeeper的分布式锁的三个分布式锁的实现主要是利用了Zookeeper的两个特性来实现的:Zookeeper的一个节点不能重复创建。Zookeeper的Watcher监控机制是不公平的。对于非公平锁,我们在加锁过程如下图所示。优缺点其实上面的实现既有优点也有缺点:优点:实现比较简单,有通知机制,可以提供更快的响应。有点类似于ReentrantLock的思想。对于节点删除失败的场景,会话超时保证节点可以被删除。缺点:重量级,在大量锁的情况下会有“雷群”问题。“惊群”是指当一个节点被删除时,大量订阅了该节点删除动作的Watcher的线程会回调,这对Zk集群是非常不利的。所以要避免这种现象的发生。解决“惊群”:为了解决“惊群”问题,我们需要放弃订阅一个节点的策略,那么怎么办呢?我们把锁抽象成一个目录,多个线程在这个目录下创建瞬时顺序节点,因为Zookeeper会帮我们保证节点的顺序,所以可以通过节点的顺序来判断锁。先创建一个顺序节点,然后获取当前目录下的最小节点,判断最小节点是否为当前节点。如果是,则锁获取成功,如果不是,则锁获取失败。获取锁失败的节点在当前节点上获取一个时序节点,注册并监听这个节点,当节点被删除时通知当前节点。解锁时,删除节点后通知下一个节点。公平锁是基于非公平锁的缺点,我们可以通过以下解决方案来避免。优缺点优点:如前所述,借助临时顺序节点,可以避免多个节点同时并发竞争锁,减轻服务器压力。缺点:对于读写场景,无法解决一致性问题。如果在读的时候也获取锁,这会导致性能下降。对于此类问题,我们可以实现读写锁,比如jdk中的ReadWriteLock读写锁实现了读写锁的特性:如果多个线程都在读取读写锁,则可以并发读取,这是一个无锁状态。如果有写锁在操作,那么读写锁需要等待写锁。在加写锁的时候,由于前面的读锁都是并发的,所以需要监听最后一个读锁的完成,执行写锁。步骤如下:读请求,如果前面是读锁,可以不监听直接读。如果前面有一个或多个写锁,那么只需要监控最后一个写锁。写请求只需要监听前面的节点即可。Watcher机制与互斥体相同。分布式锁实战本文源码使用环境:JDK1.8,Zookeeper3.6.xCurator组件实现POM依赖于org.apache.curatorcurator-framework2.13.0org.apache.curatorcurator-recipes2.13.0mutual由于Zookeeper非公平锁的“惊群”效应,非公平锁在Zookeeper中并不是最好的选择。下面是模拟秒杀使用Zookeeper分布式锁的例子。publicclassMutexTest{staticExecutorServiceexecutor=Executors.newFixedThreadPool(8);staticAtomicIntegerstock=newAtomicInteger(3);publicstaticvoidmain(String[]args)throwsInterruptedException{CuratorFrameworkclient=getZkClient();Stringkey="/lock/lockId_111/111";finalInterProcessMutexmutex=new(InterProcess,clientMkey);for(inti=0;i<99;i++){executor.submit(()->{if(stock.get()<0){System.err.println("库存不足,直接返回");return;}try{booleanacquire=mutex.acquire(200,TimeUnit.MILLISECONDS);if(acquire){ints=stock.decrementAndGet();if(s<0){System.err.println("进入秒杀,库存Insufficient");}else{System.out.println("采购成功,剩余库存:"+s);}}}catch(Exceptione){e.printStackTrace();}finally{try{if(mutex.isAcquiredInThisProcess())mutex.release();}catch(Exceptione){e.printStackTrace();}}});}while(true){if(executor.isTerminated()){executor.shutdown();System.out。println("秒杀后剩余库存为:"+stock.get());}TimeUnit.MILLISECONDS.sleep(100);}}privatestaticCuratorFrameworkgetZkClient(){StringzkServerAddress="127.0.0.1:2181";ExponentialBackoffRetryretryPolicy=newExponentialBackoffRetry(1000,3,5000);CuratorFrameworkzkClient=CuratorFrameworkFactory.builder().connectString(zkServerAddress).sessionTimeoutMs(5000).connectionTimeoutMs(5000).connectionTimeoutMs(retry)retryPolicy).build();zkClient.start();returnzkClient;}}可以使用读写锁来保证缓存双写的强一致性,因为多线程读的时候读写锁是免锁的是的,只有当前面有写锁时,它才会等待写锁完成,然后访问数据publicclassReadWriteLockTest{staticExecutorServiceexecutor=Executors.newFixedThreadPool(8);staticAtomicIntegerstock=newAtomicInteger(3);staticInterProcessMutexreadLock;staticInterProcessMutexwriteLock;publicstaticvoidmain(String[]args)throwsInterruptedException{CuratorFrameworkclient=getZkClient();Stringkey="/lock/lockId_111/1111";InterProcessReadWriteLockreadWriteLock=newInterProcessReadWriteLock(client,key);readLock=readWriteLock.readLock();writeLock=readWriteLock.writeLock();for(inti=0;i<16;i++){executor.submit(()->{try{booleanread=readLock.acquire(2000,TimeUnit.MILLISECONDS);if(read){intnum=stock.get();System.out.println("读取库存,当前库存为:"+num);if(num<0){System.err.println("库存不足,直接返回");return;}}}catch(Exceptione){e.printStackTrace();}finally{if(readLock.isAcquiredInThisProcess()){try{readLock.release();}catch(Exceptione){e.printStackTrace();}}}try{booleanacquire=writeLock.acquire(2000,TimeUnit.MILLISECONDS);我f(acquire){ints=stock.get();if(s<=0){System.err.println("输入秒杀,库存不足");}else{s=stock.decrementAndGet();System.out.println("购买成功,剩余库存:"+s);}}}catch(Exceptione){e.printStackTrace();}finally{try{if(writeLock.isAcquiredInThisProcess())writeLock.release();}catch(Exceptione){e.printStackTrace();}}});}while(true){if(executor.isTerminated()){executor.shutdown();System.out.println("剩余库存为:"+stock.get());}TimeUnit.MILLISECONDS.sleep(100);}}privatestaticCuratorFrameworkgetZkClient(){StringzkServerAddress="127.0.0.1:2181";ExponentialBackoffRetryretryPolicy=newExponentialBackoffRetry(1000,3,5000);CuratorFramework。().connectString(zkServerAddress).sessionTimeoutMs(5000).connectionTimeoutMs(5000).retryPolicy(retryPolicy).build();zkClient.start();returnzkClient;}}打印结果如下,会有8个输出results一开始读取库存,当前库存为:3然后回头在writelock中减少库存读取库存,当前库存为:3读取库存,当前库存为:3读取库存,当前库存为:3读取库存,当前库存为:3读取库存,当前库存为:3读取库存,当前库存为:3读取库存,当前库存为:3读取库存,当前库存为:3购买成功,剩余库存:2购买成功,剩余库存:1购买成功,剩余库存:0进入秒杀,库存不足进入秒杀,库存不足进入秒杀,库存不足进入秒杀,库存不足进入秒杀,库存不足读取库存,当前库存为:0读取库存,当前库存为:0读取库存,当前库存为:0读取库存,当前库存为:0读取库存,当前库存为:0读取库存,当前库存为:0读取库存,当前库存为:0读取库存,当前库存为:0输入秒杀,Insufficientinventoryentersekill,Insufficientinventory库存不足进入秒杀,库存不足进入秒杀,库存不足进入秒杀,库存不足进入秒杀,库存不足进入秒杀,库存不足最常用的分布式锁有Redis分布式锁和Zookeeper的分布式锁。在性能方面,Redis每秒的TPS可以轻松达到数万。在大规模高并发场景下,我推荐使用Redis分布式锁作为推荐的技术方案。如果并发要求不是特别高,可以使用Zookeeper分布式来处理。参考资料https://www.cnblogs.com/leeego-123/p/12162220.htmlhttp://curator.apache.org/https://blog.csdn.net/hosaos/article/details/89521537