转载本文请联系学习Java家乡公众号。记录今天文章开始写的时间是00:53,已经是深夜了,我们来学习一下分布式锁,我们要静静的学习,然后大家一起去体会。什么是分布式锁?分布式锁能解决什么问题?当我们的系统还没有使用分布式架构的时候,我们可以使用同步锁或者Lock锁来保证多个线程并发时,只有一个线程修改共享变量或者执行代码块,但是当我们的系统大部分部署在分布式集群,简单的同步锁和锁只能保证单个实例上的数据一致性,多个实例无用。这时候就需要使用分布式锁来保证共享资源的原子性。比如我们电商系统的库存扣,在订单量小的时候问题不大。如果订单量大,多个实例同时运行。并发扣减库存业务时,可能会出现超卖问题。分布式锁的实现?常见的分布式锁有数据库实现的分布式锁,Zookeeper实现的分布式锁,Redis实现的分布式锁,Redisson实现的分布式锁。其中,数据库实现分布式锁比较简单易懂。可以直接基于数据库实现。在一些分布式业务中经常使用,但是这种方式效率也是最低的,一般不会使用。我们只关注其他三个方法的实现。Zookeeper实现分布式锁比较常见的是使用Zookeeper来实现分布式锁。比如很多项目使用Zookeeper作为分布式注册中心,喜欢使用Zookeeper来实现分布式锁。这主要得益于Zookeeper的两大特点:orderTemporarynode,Watch机制。顺序临时节点:熟悉Zookeeper的同学都知道,Zookeeper提供了多级的节点命名空间,每个节点用斜线分隔的路径表示,类似于我们的文件夹。节点分为持久节点和临时节点。节点也可以标记为已排序。当一个节点被标记为有序时,这个节点具有顺序自增的特性。我们可以使用这个特性来创建我们需要的节点。Watch机制:Watch机制是Zookeeper的另一个重要特性。我们可以在指定的节点上注册一些Watcher,当一些特定的事件被触发时,通知用户这个事件。在Zookeeper中实现分布式锁的过程中,我们首先创建一个持久化节点作为父节点。每当我们需要访问和创建分布式锁时,我们就在这个父节点下创建一个对应的临时时序子节点,临时节点的名字和父节点的名字以及序号组成特征的名字。创建子节点后,对父节点下以该子节点名称开头的子节点进行排序,判断新创建节点的序号是否最小。如果它是最小的,则获取锁。如果不是最小节点,则block等待锁,并在前一个获取该节点的顺序节点处注册Watcher,等待该节点对应的操作获取锁。业务处理完成后删除节点,关闭zk,触发Watcher释放锁。上图是严格按顺序访问的分布式锁的实现。更多的时候,我们引入一些框架来帮助我们实现它们,比如最常用的Curator框架。代码如下:InterProcessMutexlock=newInterProcessMutex(client,lockPath);if(lock.acquire(maxWait,waitUnit)){try{//业务处理}finally{lock.release();}}Zookeeper的天然优势实现分布式锁是Zookeeper是集群实现的,我们的生产环境一般都是集群部署,可以避免单点问题,稳定性好,可以保证每次操作都能释放锁。缺点是频繁的节点创建和删除以及watch事件的注册给zookeeper集群带来很大压力,性能不如Redis实现的分布式锁。Redis实现分布式锁Redis实现的分布式锁是最复杂的,但是性能确实是最好的,所以在对性能要求比较高的系统中,我们都选择使用Redis来实现分布式锁。使用Redis实现分布式锁一般是使用SETNX来实现的。举个简单的例子:}SETNX方法保证了设置锁和锁过期时间的原子性,但是我们要注意锁过期时间的设置。如果业务的执行时间比较长,我们设置过期时间比较短的时候,会造成业务还没有完成就已经释放锁的问题。因此,我们需要根据实际业务处理来评估设置锁的过期时间,以保证业务能够正常处理。Redisson实现分布式锁Redisson是在Redis基础上构建的Java内存数据网格。Redisson基于基于NIO的Netty框架,充分利用了Rediskey-value数据库提供的一系列优势。Redisson基于Java实用工具包中常用的接口,为用户提供了一系列具有分布式特性的常用工具类。.性能也比我们常用的jedis要好。不管是单机模式还是集群模式,Redisson都很好的实现了分布式锁。一般多采用集群模式。在集群模式下,Redisson使用RedLock算法来处理Master节点的宕机。多个应用程序在切换到另一个Master节点期间获取锁。Redisson集群模式获取锁的实现是在不同的节点上获取锁。每个节点都有一个获取锁的超时时间。如果获取锁超时,则认为该节点不可用。当成功获取锁的个数超过一半的Redis节点,并且获取锁所花费的时间没有超过锁过期时间,则认为加锁成功。成功获取锁后,重新计算锁释放时间,从原来的锁释放时间中减去获取锁所花费的时间。如果最后获取锁失败,则成功获取锁的节点也会释放锁。具体代码实现:导入依赖
