欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 金融 > Redis之分布式锁(1)

Redis之分布式锁(1)

2025/6/18 12:35:26 来源:https://blog.csdn.net/zErO__N/article/details/148715070  浏览:    关键词:Redis之分布式锁(1)

        前面的文章我们把Redis的基本知识都已经讲解完毕了,下面我们带入具体的场景中来逐步深入了解一下分布式锁问题。

什么是分布式锁?

        为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度,而这个分布式协调技术的核心就是来实现这个分布式锁。

        假设成员变量X同时存在JVM1、JVM2和JVM3这三个JVM内存中,成员变量X同时会在JVM分配一块内存,三个请求发送过来同时对这个变量操作,显然结果是不对的。不是同时发过来,三个请求分别操作三个不同JVM内存中的数据,变量X之间不存在共享,也不具有可见性,处理的结果也是不对的。(变量A是一种有状态的对象)。

        如果我们在业务中确实存在这个场景的话,我们就需要一种方法来解决这个问题,这就是分布式锁要解决的核心问题。

分布式锁应该具备哪些条件?

        (1)在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;

        (2)高可用、高性能的获取锁和释放锁;

        (3)具备可重入性;

        (4)具备锁失效机制,防止死锁问题出现;

        (5)具备非阻塞锁的特性,即没有获取到锁将直接返回获取锁失败。

分布式锁的实现有哪些?

        (1)Redis:利用Redis的setnx命令。此命令是原子性操作,只有在Key不存在的情况下,才能set成功。

        (2)数据库:依赖于关系型数据库的表和自带锁来实现。

        (3)Zookeeper:利用Zookeeper的顺序临时节点,来实现分布式锁和等待队列。Zookeeper设计的初衷,就是为了实现分布式锁服务的。

        后买的文章中我们会讲解如何通过这三种方法实现分布式锁。

分布式锁基本概念

        我们以Redis实现分布式锁为例来了解分布式锁的基本概念。

加锁

        最简单的方法是使用setnx命令。Key是锁的唯一标识,按业务的类型来决定命名。value应该设置成什么呢?我们先设置为1,那么加锁的伪代码就是:

setnx(key,1)

        当一个线程执行setnx返回1,说明key并不存在,该线程成功获得了锁;当一个线程执行setnx返回0,说明key已经存在,该线程抢锁失败。

解锁

        当得到锁的线程执行完任务后,需要释放锁,以便其他线程可以进入。释放锁的最简单方式是执行del指令,伪代码如下:

del(key);

        释放锁之后,其他线程就可以继续执行setnx命令来获取锁。

锁超时

        如果一个得到锁的线程在执行任务的过程中挂掉,来不及显式的释放锁,这块资源将会永远被锁住(死锁),别的线程再也别想进来。所以,setnx的key必须设置一个超时时间,以保证即使没有被显示释放,这把锁也要在一定时间后自动释放。setnx不支持超时参数,所以需要额外的指令,伪代码如下:

expire(key,time);

        将上述的三部分伪代码综合起来,就是:

if(setnv(key,1) == 1){expire(key,time);try{//业务逻辑    } finally{del(key);}
}

 存在的问题

        上面的伪代码实现中有三个致命的问题:

        问题一:setnx指令和expire指令并不是原子性的。

        这就意味着可能setnx指令刚执行完成时,线程就挂掉了,expire指令并没有执行,那么这个setnx生成的key就没有设置过期时间,也就变成了死锁;

        解决方法:

        问题产生是由于两条指令分开执行,可以使用set指令代替setnx指令,set指令增加了可选参数,可以在set时直接设置过期时间。

set(key,value,time,NX);

        问题二:del指令的误删除。

        如果某线程获得到了锁,并且这个锁的过期时间是40s。由于某些原因,拿到这把锁的线程任务执行的很慢,以至于40s过后该线程还没有执行完任务,这个时候锁自动过期了,另一个线程拿到了这把锁。在另一个线程获得这把锁执行任务时,上一个线程终于执行完了任务,执行了del操作,可是这时删除的是另一个线程上的锁,而另一个线程还没有执行完任务,这就造成了del指令误删;

        解决方法:

        可以在del之前判断一下当前的锁是不是自己加的,具体的实现上可以把当前线程的ID当做value,在删除前可以通过key对应的value值判断是不是自己线程的ID。

//加锁
String threadId = Thread.currentThread().getId();
set(key,threadId,time,NX);
//解锁
if(threadId.equals(redisClient.get(key))){del(key);
}

        问题三:问题二的解决方法可能出现并发。

        问题二的解决方法中,判断和解锁的过程是分开的,不是原子性的,可能会存在并发安全问题。

        解决方法:

        可以让获得锁的线程开启一个守护线程,用来给快要过期的锁“续航”。当拿到锁的线程任务还没有执行完而锁要超时过期时,守护线程会判断并给锁延长过期时间。当线程执行完任务时,会显式的关闭守护线程,这个时候锁就会自然过期;如果当前线程意外挂掉,由于守护线程和当前线程在一个进程内,所以守护线程也会挂掉,锁也会自然超时过期而不会变成无限延长超时时间的死锁。

        这篇文章为分布式锁开了一个头,讲解了什么是分布式锁,并通过Redis实现分布式锁的例子来梳理了一下分布式锁的基本概念。大家有什么问题或者勘误可以在评论区留言,笔者看到都会回复的。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词