当前位置: 首页 > news >正文

什么是分布式锁?几种分布式锁分别是怎么实现的?

一、什么是分布式锁:

1、什么是分布式锁:

分布式锁,即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。

2、分布式锁应该具备哪些条件:

  • 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
  • 高可用的获取锁与释放锁
  • 高性能的获取锁与释放锁
  • 具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)
  • 具备锁失效机制,即自动解锁,防止死锁
  • 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败

3、分布式锁的实现方式:

基于数据库实现分布式锁基于Zookeeper实现分布式锁基于reids实现分布式锁

这篇文章就简单介绍下这几种分布式锁的实现,重点讲解的是基于redis的分布式锁。

二、基于数据库的分布式锁:

基于数据库的锁实现也有两种方式,一是基于数据库表的增删,另一种是基于数据库排他锁。

1、基于数据库表的增删:

基于数据库表增删是最简单的方式,首先创建一张锁的表主要包含下列字段:类的全路径名+方法名,时间戳等字段。

具体的使用方式:当需要锁住某个方法时,往该表中插入一条相关的记录。类的全路径名+方法名是有唯一性约束的,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。执行完毕之后,需要delete该记录。

(这里只是简单介绍一下,对于上述方案可以进行优化,如:应用主从数据库,数据之间双向同步;一旦挂掉快速切换到备库上;做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍;使用while循环,直到insert成功再返回成功;记录当前获得锁的机器的主机信息和线程信息,下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了,实现可重入锁)

2、基于数据库排他锁:

基于MySql的InnoDB引擎,可以使用以下方法来实现加锁操作:

public void lock(){    connection.setAutoCommit(false)    int count = 0;    while(count < 4){        try{            select * from lock where lock_name=xxx for update;            if(结果不为空){                //代表获取到锁                return;            }        }catch(Exception e){         }        //为空或者抛异常的话都表示没有获取到锁        sleep(1000);        count++;    }    throw new LockException();}

在查询语句后面增加for update,数据库会在查询过程中给数据库表增加排他锁。获得排它锁的线程即可获得分布式锁,当获得锁之后,可以执行方法的业务逻辑,执行完方法之后,释放锁connection.commit()。当某条记录被加上排他锁之后,其他线程无法获取排他锁并被阻塞。

3、基于数据库锁的优缺点:

上面两种方式都是依赖数据库表,一种是通过表中的记录判断当前是否有锁存在,另外一种是通过数据库的排他锁来实现分布式锁。

  • 优点是直接借助数据库,简单容易理解。
  • 缺点是操作数据库需要一定的开销,性能问题需要考虑。

三、基于Zookeeper的分布式锁

基于zookeeper临时有序节点可以实现的分布式锁。每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。 (第三方库有 Curator,Curator提供的InterProcessMutex是分布式锁的实现)

Zookeeper实现的分布式锁存在两个个缺点:

  • (1)性能上可能并没有缓存服务那么高,因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同步到所有的Follower机器上。
  • (2)zookeeper的并发安全问题:因为可能存在网络抖动,客户端和ZK集群的session连接断了,zk集群以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到分布式锁了。

四、基于redis的分布式锁:

redis命令说明:

(1)setnx命令:set if not exists,当且仅当 key 不存在时,将 key 的值设为 value。若给定的 key 已经存在,则 SETNX 不做任何动作。

  • 返回1,说明该进程获得锁,将 key 的值设为 value
  • 返回0,说明其他进程已经获得了锁,进程不能进入临界区。

命令格式:setnx lock.key lock.value

(2)get命令:获取key的值,如果存在,则返回;如果不存在,则返回nil

命令格式:get lock.key

(3)getset命令:该方法是原子的,对key设置newValue这个值,并且返回key原来的旧值。

命令格式:getset lock.key newValue

(4)del命令:删除redis中指定的key

命令格式:del lock.key

方案一:基于set命令的分布式锁

1、加锁:使用setnx进行加锁,当该指令返回1时,说明成功获得锁

2、解锁:当得到锁的线程执行完任务之后,使用del命令释放锁,以便其他线程可以继续执行setnx命令来获得锁

(1)存在的问题:假设线程获取了锁之后,在执行任务的过程中挂掉,来不及显示地执行del命令释放锁,那么竞争该锁的线程都会执行不了,产生死锁的情况。

(2)解决方案:设置锁超时时间

3、设置锁超时时间:setnx 的 key 必须设置一个超时时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放。可以使用expire命令设置锁超时时间

(1)存在问题:

setnx 和 expire 不是原子性的操作,假设某个线程执行setnx 命令,成功获得了锁,但是还没来得及执行expire 命令,服务器就挂掉了,这样一来,这把锁就没有设置过期时间了,变成了死锁,别的线程再也没有办法获得锁了。

(2)解决方案:redis的set命令支持在获取锁的同时设置key的过期时间

4、使用set命令加锁并设置锁过期时间:

命令格式:set <lock.key> <lock.value> nx ex

详情参考redis使用文档:

http://doc.redisfans.com/string/set.html

(1)存在问题:

① 假如线程A成功得到了锁,并且设置的超时时间是 30 秒。如果某些原因导致线程 A 执行的很慢,过了 30 秒都没执行完,这时候锁过期自动释放,线程 B 得到了锁。

② 随后,线程A执行完任务,接着执行del指令来释放锁。但这时候线程 B 还没执行完,线程A实际上删除的是线程B加的锁。

(2)解决方案:

可以在 del 释放锁之前做一个判断,验证当前的锁是不是自己加的锁。在加锁的时候把当前的线程 ID 当做value,并在删除之前验证 key 对应的 value 是不是自己线程的 ID。但是,这样做其实隐含了一个新的问题,get操作、判断和释放锁是两个独立操作,不是原子性。对于非原子性的问题,我们可以使用Lua脚本来确保操作的原子性

5、锁续期:(这种机制类似于redisson的看门狗机制,文章后面会详细说明)

虽然步骤4避免了线程A误删掉key的情况,但是同一时间有 A,B 两个线程在访问代码块,仍然是不完美的。怎么办呢?我们可以让获得锁的线程开启一个守护线程,用来给快要过期的锁“续期”。

① 假设线程A执行了29 秒后还没执行完,这时候守护线程会执行 expire 指令,为这把锁续期 20 秒。守护线程从第 29 秒开始执行,每 20 秒执行一次。

② 情况一:当线程A执行完任务,会显式关掉守护线程。

③ 情况二:如果服务器忽然断电,由于线程 A 和守护线程在同一个进程,守护线程也会停下。这把锁到了超时的时候,没人给它续命,也就自动释放了。

方案二:基于setnx、get、getset的分布式锁

1、实现原理:

(1)setnx(lockkey, 当前时间+过期超时时间) ,如果返回1,则获取锁成功;如果返回0则没有获取到锁,转向步骤(2)

(2)get(lockkey)获取值oldExpireTime ,并将这个value值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向步骤(3)

(3)计算新的过期时间 newExpireTime=当前时间+锁超时时间,然后getset(lockkey, newExpireTime) 会返回当前lockkey的值currentExpireTime

(4)判断 currentExpireTime 与 oldExpireTime 是否相等,如果相等,说明当前getset设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。

(5)在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行del命令释放锁(释放锁之前需要判断持有锁的线程是不是当前线程);如果大于锁设置的超时时间,则不需要再锁进行处理。

2、代码实现:

(1)获取锁的实现方式:

public boolean lock(long acquireTimeout, TimeUnit timeUnit) throws InterruptedException {    acquireTimeout = timeUnit.toMillis(acquireTimeout);    long acquireTime = acquireTimeout + System.currentTimeMillis();    //使用J.U.C的ReentrantLock    threadLock.tryLock(acquireTimeout, timeUnit);    try {    	//循环尝试        while (true) {        	//调用tryLock            boolean hasLock = tryLock();            if (hasLock) {                //获取锁成功                return true;            } else if (acquireTime < System.currentTimeMillis()) {                break;            }            Thread.sleep(sleepTime);        }    } finally {        if (threadLock.isHeldByCurrentThread()) {            threadLock.unlock();        }    }     return false;} public boolean tryLock() {     long currentTime = System.currentTimeMillis();    String expires = String.valueOf(timeout + currentTime);    //设置互斥量    if (redisHelper.setNx(mutex, expires) > 0) {    	//获取锁,设置超时时间        setLockStatus(expires);        return true;    } else {        String currentLockTime = redisUtil.get(mutex);        //检查锁是否超时        if (Objects.nonNull(currentLockTime) && Long.parseLong(currentLockTime) < currentTime) {            //获取旧的锁时间并设置互斥量            String oldLockTime = redisHelper.getSet(mutex, expires);            //旧值与当前时间比较            if (Objects.nonNull(oldLockTime) && Objects.equals(oldLockTime, currentLockTime)) {            	//获取锁,设置超时时间                setLockStatus(expires);                return true;            }        }         return false;    }}

tryLock方法中,主要逻辑如下:lock调用tryLock方法,参数为获取的超时时间与单位,线程在超时时间内,获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。

(2)释放锁的实现方式:

public boolean unlock() {    //只有锁的持有线程才能解锁    if (lockHolder == Thread.currentThread()) {        //判断锁是否超时,没有超时才将互斥量删除        if (lockExpiresTime > System.currentTimeMillis()) {            redisHelper.del(mutex);            logger.info("删除互斥量[{}]", mutex);        }        lockHolder = null;        logger.info("释放[{}]锁成功", mutex);         return true;    } else {        throw new IllegalMonitorStateException("没有获取到锁的线程无法执行解锁操作");    }}

存在问题:

(1)这个锁的核心是基于System.currentTimeMillis(),如果多台服务器时间不一致,那么问题就出现了,但是这个bug完全可以从服务器运维层面规避的,而且如果服务器时间不一样的话,只要和时间相关的逻辑都是会出问题的

(2)如果前一个锁超时的时候,刚好有多台服务器去请求获取锁,那么就会出现同时执行redis.getset()而导致出现过期时间覆盖问题,不过这种情况并不会对正确结果造成影响

(3)存在多个线程同时持有锁的情况:如果线程A执行任务的时间超过锁的过期时间,这时另一个线程就可以获得这个锁了,造成多个线程同时持有锁的情况。类似于方案一,可以使用“锁续期”的方式来解决。

前两种redis分布式锁的存在的问题

前面两种redis分布式锁的实现方式,如果从“高可用”的层面来看,仍然是有所欠缺,也就是说当 redis 是单点的情况下,当发生故障时,则整个业务的分布式锁都将无法使用。

为了提高可用性,我们可以使用主从模式或者哨兵模式,但在这种情况下仍然存在问题,在主从模式或者哨兵模式下,正常情况下,如果加锁成功了,那么master节点会异步复制给对应的slave节点。但是如果在这个过程中发生master节点宕机,主备切换,slave节点从变为了 master节点,而锁还没从旧master节点同步过来,这就发生了锁丢失,会导致多个客户端可以同时持有同一把锁的问题。来看个图来想下这个过程:

那么,如何避免这种情况呢?redis 官方给出了基于多个 redis 集群部署的高可用分布式锁解决方案:RedLock,在方案三我们就来详细介绍一下。(备注:如果master节点宕机期间,可以容忍多个客户端同时持有锁,那么就不需要redLock)

方案三:基于RedLock的分布式锁

redLock的官方文档地址:

https://redis.io/topics/distlock

Redlock算法是Redis的作者 Antirez 在单Redis节点基础上引入的高可用模式。Redlock的加锁要结合单节点分布式锁算法共同实现,因为​​​它是RedLock的基础

1、加锁实现原理:

现在假设有5个Redis主节点(大于3的奇数个),这样基本保证他们不会同时都宕掉,获取锁和释放锁的过程中,客户端会执行以下操作:

(1)获取当前Unix时间,以毫秒为单位,并设置超时时间TTL

TTL 要大于 正常业务执行的时间 + 获取所有redis服务消耗时间 + 时钟漂移

(2)依次尝试从5个实例,使用相同的key和具有唯一性的value获取锁,当向Redis请求获取锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间TTL,这样可以避免客户端死等。比如:TTL为5s,设置获取锁最多用1s,所以如果一秒内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁

(3)客户端 获取所有能获取的锁后的时间 减去 第(1)步的时间,就得到锁的获取时间。锁的获取时间要小于锁失效时间TTL,并且至少从半数以上的Redis节点取到锁,才算获取成功锁

(4)如果成功获得锁,key的真正有效时间 = TTL - 锁的获取时间 - 时钟漂移。比如:TTL 是5s,获取所有锁用了2s,则真正锁有效时间为3s

(5)如果因为某些原因,获取锁失败(没有在半数以上实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁,无论Redis实例是否加锁成功,因为可能服务端响应消息丢失了但是实际成功了。

设想这样一种情况:客户端发给某个Redis节点的获取锁的请求成功到达了该Redis节点,这个节点也成功执行了SET操作,但是它返回给客户端的响应包却丢失了。这在客户端看来,获取锁的请求由于超时而失败了,但在Redis这边看来,加锁已经成功了。因此,释放锁的时候,客户端也应该对当时获取锁失败的那些Redis节点同样发起请求。实际上,这种情况在异步通信模型中是有可能发生的:客户端向服务器通信是正常的,但反方向却是有问题的。

(6)失败重试:当client不能获取锁时,应该在随机时间后重试获取锁;同时重试获取锁要有一定次数限制;

在随机时间后进行重试,主要是防止过多的客户端同时尝试去获取锁,导致彼此都获取锁失败的问题。

算法示意图如下:

2、RedLock性能及崩溃恢复的相关解决方法:

由于N个Redis节点中的大多数能正常工作就能保证Redlock正常工作,因此理论上它的可用性更高。前面我们说的主从架构下存在的安全性问题,在RedLock中已经不存在了,但如果有节点发生崩溃重启,还是会对锁的安全性有影响的,具体的影响程度跟Redis持久化配置有关:

(1)如果redis没有持久化功能,在clientA获取锁成功后,所有redis重启,clientB能够再次获取到锁,这样违法了锁的排他互斥性;

(2)如果启动AOF永久化存储,事情会好些, 举例:当我们重启redis后,由于redis过期机制是按照unix时间戳走的,所以在重启后,然后会按照规定的时间过期,不影响业务;但是由于AOF同步到磁盘的方式默认是每秒一次,如果在一秒内断电,会导致数据丢失,立即重启会造成锁互斥性失效;但如果同步磁盘方式使用Always(每一个写命令都同步到硬盘)造成性能急剧下降;所以在锁完全有效性和性能方面要有所取舍;

(3)为了有效解决既保证锁完全有效性 和 性能高效问题:antirez又提出了“延迟重启”的概念,redis同步到磁盘方式保持默认的每秒1次,在redis崩溃单机后(无论是一个还是所有),先不立即重启它,而是等待TTL时间后再重启,这样的话,这个节点在重启前所参与的锁都会过期,它在重启后就不会对现有的锁造成影响,缺点是在TTL时间内服务相当于暂停状态;

3、Redisson中RedLock的实现:

在JAVA的redisson包已经实现了对RedLock的封装,主要是通过 redisClient 与 lua 脚本实现的,之所以使用 lua 脚本,是为了实现加解锁校验与执行的事务性。

(1)唯一ID的生成:

分布式事务锁中,为了能够让作为中心节点的存储节点获取锁的持有者,从而避免锁被非持有者误解锁,每个发起请求的 client 节点都必须具有全局唯一的 id。通常我们是使用 UUID 来作为这个唯一 id,redisson 也是这样实现的,在此基础上,redisson 还加入了 threadid 避免了多个线程反复获取 UUID 的性能损耗

protected final UUID id = UUID.randomUUID();String getLockName(long threadId) {	return id + ":" + threadId;}

(2)加锁逻辑:

redisson 加锁的核心代码非常容易理解,通过传入 TTL 与唯一 id,实现一段时间的加锁请求。下面是可重入锁的实现逻辑:

<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {	internalLockLeaseTime = unit.toMillis(leaseTime); 	// 获取锁时向5个redis实例发送的命令	return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,			  // 校验分布式锁的KEY是否已存在,如果不存在,那么执行hset命令(hset REDLOCK_KEY uuid+threadId 1),并通过pexpire设置失效时间(也是锁的租约时间)			  "if (redis.call('exists', KEYS[1]) == 0) then " +				  "redis.call('hset', KEYS[1], ARGV[2], 1); " +				  "redis.call('pexpire', KEYS[1], ARGV[1]); " +				  "return nil; " +			  "end; " +			  // 如果分布式锁的KEY已存在,则校验唯一 id,如果唯一 id 匹配,表示是当前线程持有的锁,那么重入次数加1,并且设置失效时间			  "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +				  "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +				  "redis.call('pexpire', KEYS[1], ARGV[1]); " +				  "return nil; " +			  "end; " +			  // 获取分布式锁的KEY的失效时间毫秒数			  "return redis.call('pttl', KEYS[1]);",			  // KEYS[1] 对应分布式锁的 key;ARGV[1] 对应 TTL;ARGV[2] 对应唯一 id				Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));}

(3)释放锁逻辑:

protected RFuture<Boolean> unlockInnerAsync(long threadId) {	// 向5个redis实例都执行如下命令	return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,			// 如果分布式锁 KEY 不存在,那么向 channel 发布一条消息			"if (redis.call('exists', KEYS[1]) == 0) then " +				"redis.call('publish', KEYS[2], ARGV[1]); " +				"return 1; " +			"end;" +			// 如果分布式锁存在,但是唯一 id 不匹配,表示锁已经被占用			"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +				"return nil;" +			"end; " +			// 如果就是当前线程占有分布式锁,那么将重入次数减 1			"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +			// 重入次数减1后的值如果大于0,表示分布式锁有重入过,那么只设置失效时间,不删除			"if (counter > 0) then " +				"redis.call('pexpire', KEYS[1], ARGV[2]); " +				"return 0; " +			"else " +				// 重入次数减1后的值如果为0,则删除锁,并发布解锁消息				"redis.call('del', KEYS[1]); " +				"redis.call('publish', KEYS[2], ARGV[1]); " +				"return 1; "+			"end; " +			"return nil;",			// KEYS[1] 表示锁的 key,KEYS[2] 表示 channel name,ARGV[1] 表示解锁消息,ARGV[2] 表示 TTL,ARGV[3] 表示唯一 id			Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));}

(4)redisson中RedLock的使用:

Config config = new Config();config.useSentinelServers()        .addSentinelAddress("127.0.0.1:6369","127.0.0.1:6379", "127.0.0.1:6389")		.setMasterName("masterName")		.setPassword("password").setDatabase(0); RedissonClient redissonClient = Redisson.create(config);RLock redLock = redissonClient.getLock("REDLOCK_KEY"); try {    // 尝试加锁,最多等待500ms,上锁以后10s自动解锁	boolean isLock = redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS);	if (isLock) {		//获取锁成功,执行对应的业务逻辑	}} catch (Exception e) {    e.printStackTrace();} finally {	redLock.unlock();}

可以看到,redisson 包的实现中,通过 lua 脚本校验了解锁时的 client 身份,所以我们无需再在 finally 中去判断是否加锁成功,也无需做额外的身份校验,可以说已经达到开箱即用的程度了。

同样,基于RedLock实现的分布式锁也存在 client 获取锁之后,在 TTL 时间内没有完成业务逻辑的处理,而此时锁会被自动释放,造成多个线程同时持有锁的问题。而Redisson 在实现的过程中,自然也考虑到了这一问题,redisson 提供了一个“看门狗”的特性,当锁即将过期还没有释放时,不断的延长锁key的生存时间。(具体实现原理会在方案四进行介绍)

方案四:基于Redisson看门狗的分布式锁

前面说了,如果某些原因导致持有锁的线程在锁过期时间内,还没执行完任务,而锁因为还没超时被自动释放了,那么就会导致多个线程同时持有锁的现象出现,而为了解决这个问题,可以进行“锁续期”。其实,在JAVA的Redisson包中有一个"看门狗"机制,已经帮我们实现了这个功能。

1、redisson原理:

redisson在获取锁之后,会维护一个看门狗线程,当锁即将过期还没有释放时,不断的延长锁key的生存时间

2、加锁机制:

线程去获取锁,获取成功:执行lua脚本,保存数据到redis数据库。

线程去获取锁,获取失败:一直通过while循环尝试获取锁,获取成功后,执行lua脚本,保存数据到redis数据库。

3、watch dog自动延期机制:

看门狗启动后,对整体性能也会有一定影响,默认情况下看门狗线程是不启动的。如果使用redisson进行加锁的同时设置了锁的过期时间,也会导致看门狗机制失效。

redisson在获取锁之后,会维护一个看门狗线程,在每一个锁设置的过期时间的1/3处,如果线程还没执行完任务,则不断延长锁的有效期。看门狗的检查锁超时时间默认是30秒,可以通过 lockWactchdogTimeout 参数来改变。

加锁的时间默认是30秒,如果加锁的业务没有执行完,那么每隔 30 ÷ 3 = 10秒,就会进行一次续期,把锁重置成30秒,保证解锁前锁不会自动失效。

那万一业务的机器宕机了呢?如果宕机了,那看门狗线程就执行不了了,就续不了期,那自然30秒之后锁就解开了呗。

4、redisson分布式锁的关键点:

a. 对key不设置过期时间,由Redisson在加锁成功后给维护一个watchdog看门狗,watchdog负责定时监听并处理,在锁没有被释放且快要过期的时候自动对锁进行续期,保证解锁前锁不会自动失效

b. 通过Lua脚本实现了加锁和解锁的原子操作

c. 通过记录获取锁的客户端id,每次加锁时判断是否是当前客户端已经获得锁,实现了可重入锁。

5、Redisson的使用:

在方案三中,我们已经演示了基于Redisson的RedLock的使用案例,其实 Redisson 也封装 可重入锁(Reentrant Lock)、公平锁(Fair Lock)、联锁(MultiLock)、红锁(RedLock)、读写锁(ReadWriteLock)、 信号量(Semaphore)、可过期性信号量(PermitExpirableSemaphore)、 闭锁(CountDownLatch)等,具体使用说明可以参考官方文档:Redisson的分布式锁和同步器

附:redLock的官方文档翻译

相关文章:

什么是分布式锁?几种分布式锁分别是怎么实现的?

一、什么是分布式锁&#xff1a; 1、什么是分布式锁&#xff1a; 分布式锁&#xff0c;即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题&#xff0c;而分布式锁&#xff0c;就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是&am…...

【一天一门编程语言】R 语言程序设计极简教程

R 语言程序设计极简教程 文章目录 R 语言程序设计极简教程R语言简介1.1 介绍1.2 R 语言的基础知识1.2.1 语法1.2.2 数据类型1.2.3 基本操作1.3 R 语言的高级知识1.3.1 函数1.3.2 包1.3.3 面向对象编程1.4 使用 R 语言的实践1.4.1 数据处理1.4.2 数据可视化1.4.3 数据建模1.4.3.…...

记一次顿悟的经历

2023.02.20 一次顿悟的经历 体验一次顿悟 ​ 需求&#xff1a; ​为避免接收数据时一直阻塞&#xff0c;先调用 select 在一定时间内判断是否有数据可读 如果超时&#xff0c;就报错没读到数据&#xff0c;即使返回 如果仍然在 set 里&#xff0c;就调用 recv 函数接收数据 问…...

19_FreeRTOS软件定时器

目录 软件定时器介绍 FreeRTOS软件定时器特点 软件定时器的命令队列 软件定时器的相关配置 单次定时器和周期定时器 软件定时器结构体成员 FreeRTOS软件定时器相关API函数 实验源码 软件定时器介绍 定时器描述:从指定的时刻开始,经过一个指定时间,然后触发一个超时事件…...

值得推荐!安利5款良心又好用的小众软件

电脑上的各类软件有很多&#xff0c;除了那些常见的大众化软件&#xff0c;还有很多不为人知的小众软件&#xff0c;专注于实用功能&#xff0c;简洁干净、功能强悍。今天分享5个实用的软件&#xff0c;简单实用&#xff0c;效果拉满&#xff0c;堪称工作生活必备&#xff01; …...

Enhanced ShockBurst (ESB)原文翻译

自我学习为主&#xff0c;同时也为所需要的提供一份资料 官方地址 增强型ShockBurst&#xff08;ESB&#xff09;是一种支持双向数据包通信的基本协议&#xff0c;包括数据包缓冲、数据包确认和丢失数据包的自动重传。ESB以低功耗提供无线通信&#xff0c;并且实现的代码量小且…...

软件测试之兼容性测试

对于基于计算机平台的软件&#xff0c;在测试过程中必须考虑软、硬件的兼容性&#xff0c;在设计测试用例的过程中必须考虑数据转换或转移的问题&#xff0c;应该尽力发现其可能带来的错误。不仅是基于计算机平台的软件&#xff0c;对于嵌入式软件也一样&#xff0c;在软件升级…...

笔记(一)——容器

容器分类&#xff1a;序列式容器&#xff1a;每个元素都有固定位置&#xff0c;取决于插入的时机和地点&#xff0c;和元素无关&#xff0c;如vector、deque、list、stack、queue。关联式容器&#xff1a;元素位置取决于特定的排序准则&#xff0c;和插入顺序无关&#xff0c;如…...

C++入门:命名空间

假设这样一种情况&#xff0c;当一个班上有两个名叫 Zara 的学生时&#xff0c;为了明确区分它们&#xff0c;我们在使用名字之外&#xff0c;不得不使用一些额外的信息&#xff0c;比如他们的家庭住址&#xff0c;或者他们父母的名字等等。同样的情况也出现在 C 应用程序中。例…...

操作系统(复试准备)

操作系统&#xff08;复试准备&#xff09; 第一章知识点 操作系统概述 操作系统的概念 负责协调软硬件等计算机资源的工作 为上层用户&#xff0c;应用程序提供简单易用的接口 是一种系统软件 操作系统的功能与目标 资源的管理者 处理机管理&#xff0c;存储器管理&#x…...

2023年CDGA考试模拟题库(501-600)

2023年CDGA考试模拟题库(501-600) 501.希望从数据中获取价值的组织认识到,高质量数据比低质量数据更有价值。使用劣质数据充满风险,会损害组织的声誉,导致罚款、收入损失、客户流失和负面的媒体曝光等组织基于各种业务驱动因素而着手进行数据质量管理活动,下列哪项不属于上述驱…...

NCNN+Int8+yolov5部署和量化

【GiantPandaCV引言】 还记得我在两个月前写的文章吗,关于yolov4-tiny+ncnn+int8量化的详细教程:NCNN+INT8+YOLOV4量化模型和实时推理 后来准备写yolov5+ncnn+int8量化的教程,却在yolov5的量化上遇到了麻烦,一方面是量化后速度更慢了,另一方面是精度下降严重,出现满屏都…...

springboot+vue.js协同过滤算法之智能旅游推荐系统java

目 录 第一章 绪论 3 1.1课题背景 3 1.2课题研究的目的和意义 3 1.3 研究现状 4 1.4论文所做的主要工作 4 第二章 技术介绍 5 2.1B/S结构 5 2.2MySQL 介绍 5 2.3MySQL环境配置 6 第三章 系统分析与设计 8 3.1系统说明 8 3.2系统可行性分析…...

Google Brain新提出的优化器“Lion”,效果要比Adam(W)更好

Google Brain新提出的优化器“Lion”&#xff0c;效果要比Adam(W)更好 论文地址&#xff1a;https://arxiv.org/abs/2302.06675代码地址&#xff1a;https://github.com/google/automl/blob/master/lion/lion_pytorch.py 1 简单、内存高效、运行速度更快 与 AdamW 和各种自适…...

慢雾:Discord 私信钓鱼手法分析

事件背景 5 月 16 日凌晨&#xff0c;当我在寻找家人的时候&#xff0c;从项目官网的邀请链接加入了官方的 Discord 服务器。在我加入服务器后立刻就有一个"机器人"(Captcha.bot)发来私信要我进行人机验证。这一切看起来相当的合理。我也点击了这个验证链接进行查看…...

2023-2-25 刷题情况

交换字符使得字符串相同 题目描述 有两个长度相同的字符串 s1 和 s2&#xff0c;且它们其中 只含有 字符 “x” 和 “y”&#xff0c;你需要通过「交换字符」的方式使这两个字符串相同。 每次「交换字符」的时候&#xff0c;你都可以在两个字符串中各选一个字符进行交换。 …...

【数据结构】双向链表的接口实现(附图解和源码)

双向链表的接口实现&#xff08;附图解和源码&#xff09; 文章目录双向链表的接口实现&#xff08;附图解和源码&#xff09;前言一、定义结构体二、接口实现&#xff08;附图解源码&#xff09;1.初始化双向链表2.开辟新空间3.尾插数据4.尾删数据5.打印双向链表中数据6.头插数…...

数据结构与算法之[把数字翻译成字符串]动态规划

前言&#xff1a;最近在刷动态规划的算法题目&#xff0c;感觉这一类题目还是有一点难度的&#xff0c;但是不放弃也还是能学好的&#xff0c;今天给大家分享的是牛客网中的编程题目[把数字翻译成字符串]&#xff0c;这是一道经典的面试题目&#xff0c;快手&#xff0c;字节跳…...

java 面向对象三大特性之多态 万字详解(超详细)

目录 前言 : 一、为什么需要多态 : 1.白璧微瑕 : 2.举栗&#xff08;请甘雨,刻晴,钟离吃饭&#xff09;: 3.代码 : 4.问题 : 二、什么是多态 : 1.定义 : 2.多态的实现步骤&#xff08;重要&#xff09; : 三、多态的使用 : 1.多态中成员方法的使用&#xff08;重要…...

git push origin master 情况

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3;哈喽&#xff01;大家好&#xff0c;我是「奇点」&#xff0c;江湖人称 singularity。刚工作几年&#xff0c;想和大家一同进步&#x1f91d;&#x1f91d;一位上进心十足的【Java ToB端大厂领…...

ElasticSearch查询优化routing

如果一个索引分片多达一百,再加上每个分片数据量大的情况下ES查询速度会慢,这种情况可以根据业务情况考虑使用_routing优化。 _routing 路由 当索引一个文档的时候,文档会被存储在一个主分片上。在存储时一般都会有多个主分片。Elasticsearch 如何知道一个文档应该放置在哪…...

【HashMap 1.7和1.8】

Java中的HashMap是一种常用的数据结构&#xff0c;用于存储键值对。在Java 1.7和1.8中&#xff0c;HashMap的实现有一些不同。 Java 1.7中的HashMap实现是基于“拉链法”的哈希表。每个哈希桶(bucket)是一个链表&#xff0c;存储了散列值相同的键值对。当键值对数量过多时&…...

【Zabbix实战之故障处理篇】Zabbix监控中文乱码问题解决方法

【Zabbix实战之故障处理篇】Zabbix监控中文乱码问题解决方法 一、问题展现1.查看Zabbix仪表盘2.问题分析二、检查Zabbix环境1.检查Zabbix监控主机2.检查Zabbix各组件状态三、在宿主机安装中文字体库1.安装中文字体2.查看字体文件四、安装中文字库1.查看Zabbix所有组件容器2.拷贝…...

学习(mianshi)必备-ClickHouse高性能查询/写入和常见注意事项(五)

目录 一、ClickHouse高性能查询原因-稀疏索引 二、ClickHouse高性能写入-LSM-Tree存储结构 什么是LSM-Tree 三、ClickHouse的常见注意事项和异常问题排查 一、ClickHouse高性能查询原因-稀疏索引 密集索引: 在密集索引中&#xff0c;数据库中的每个键值都有一个索引记录&…...

在Kotlin中探索 Activity Results API 极简的解决方案

Activity Results APIActivity Result API提供了用于注册结果、启动结果以及在系统分派结果后对其进行处理的组件。—Google官方文档https://developer.android.google.cn/training/basics/intents/result?hlzh-cn一句话解释&#xff1a;官方Jetpack组件用于代替startActivity…...

样式冲突太多,记一次前端CSS升级

目前平台前端使用的是原生CSSBEM命名&#xff0c;在多人协作的模式下&#xff0c;容易出现样式冲突。为了减少这一类的问题&#xff0c;提升研效&#xff0c;我调研了业界上主流的7种CSS解决方案&#xff0c;并将最终升级方案落地到了工程中。 样式冲突的原因 目前遇到的样式…...

如何解决报考PMP的那些问题?

关于PMP的报考条件&#xff0c;报考PMP都需要什么条件呢&#xff1f;【学历条件】&#xff1a;需要满足23周岁/高中毕业5年以上/大专以上学历&#xff0c;三个满足一个即可&#xff1b;【PDU条件】&#xff1a;报考PMP需要PDU证明&#xff08;学习项目管理课程的学时证明&#…...

数据结构栈的经典OJ题【leetcode最小栈问题大剖析】【leetcode有效的括号问题大剖析】

目录 0.前言 1.最小栈 1.1 原题展示 1.2 思路分析 1.2.1 场景引入 1.2.2 思路 1.3 代码实现 1.3.1 最小栈的删除 1.3.2 最小栈的插入 1.3.3 获取栈顶元素 1.3.4 获取当前栈的最小值 2. 有效的括号 0.前言 本篇博客已经把两个关于栈的OJ题分块&#xff0c;可以根据目…...

数据结构与算法之打家劫舍(一)动态规划思想

动态规划里面一部题目打家劫舍是一类经典的算法题目之一&#xff0c;他有各种各样的变式&#xff0c;这一篇文章和大家分享一下打家劫舍最基础的一道题目&#xff0c;掌握这一道题目&#xff0c;为下一道题目打下基础。我们直接进入正题。一.题目大家如果刚接触这样的题目&…...

无人驾驶路径规划论文简要

A Review of Motion Planning Techniques for Automated Vehicles综述和分类0Motion Planning for Autonomous Driving with a Conformal Spatiotemporal Lattice从unstructured环境向structured环境的拓展&#xff0c;同时还从state lattice拓展到了spatiotemporal lattice从而…...