首页 > 编程知识 正文

redis分布式锁setnx实现原理,java redis实现分布式锁

时间:2023-05-03 10:52:36 阅读:33414 作者:4135

对于多线程并发,可以使用锁定来确保一个代码块同时只能由一个线程访问。 例如Java的同步关键字或Reentrantlock类等。

这可以确保同一JVM进程中的多个线程同时运行。

在分布式群集环境中,如何确保不同节点的线程同步运行?

如何在分布式系统中实现不同线程对代码和资源的并发访问?

在单进程并发方案中,可以使用语言和类库提供的锁定。 分布式场景可以使用分布式锁定。

那么,如何实现分布式系统的锁定呢?

分布式锁有很多中实现方法,我们来简单列举一下。

分布式锁的实现有哪些?

1.Memcached分布式锁

Memcached的add命令此命令是原子操作,表示add仅在key不存在时成功,线程已锁定。

2.Redis分布式锁

与Memcached的方式相似,Redis的setnx命令该命令也是原子操作,只有在key不存在时set才成功。 (setnx命令不完整,稍后将介绍替代方案。)

3.Zookeeper分布式锁

利用Zookeeper的顺序临时节点实现分布式锁定和队列。 Zookeeper是为实现分布式锁定服务而设计的。

首先谈谈Redis的分布式锁定。 这种实现方式很有代表性。

如何用Redis实现分布式锁?

Redis分布式锁的基本流程不容易理解,但要写得完美并不是那么容易。 在这里,首先需要了解分布式锁实现的三个主要要素。

1.加锁

最简单的方法是使用setnx命令。 key是钥匙的唯一标志,由业务决定命名。 例如,如果想要锁定某个商品的秒杀事件,key可以被命名为“lock_sale_商品ID”。 value设置为什么? 锁定的值是随机生成的UUID。 我们可以先设定为1。 锁定的伪代码如下:

setnx(key,1 )如果一个线程运行setnx并返回1,则key原本就不存在,表示线程已成功锁定。 如果线程执行setnx并返回0,则表示key已存在,并且线程解锁失败。

2.解锁

有锁的话就需要解锁。 锁定线程完成任务后,必须解锁该任务,以便其他线程可以访问。 解锁的最简单方法是运行del命令。 伪代码如下。

当Del(key )解除锁定时,其他线程可以继续运行setnx命令获取锁定。

3.锁超时

锁定超时是什么意思? 如果锁定的线程在执行任务时锁定,无法显式解锁,则此资源将被永久锁定,其他线程不应再进入。

因此,即使没有明确释放setnx的密钥,锁定也会在一定时间后自动释放,为了避免死锁,必须设置second单位的超时时间。 setnx不支持超时参数,因此需要额外的指令。 伪代码如下。

综合expire(key,30 ),我们的分布式锁定实现的第一版伪代码如下。

if(setNX(key,1 )==1) expire (key,30 ) try (do something……) finally (del ) key ) }上的伪代码是分布式锁定的简单实现,实际响应

1.setnx和expire的非原子性

想象一下极端的情景。 假设一个线程运行了setnx,并且锁定成功。

setnx刚成功运行,还没有时间运行expire命令,节点1 Duang突然中断。

这样,该锁定就没有过期,成为“长生不老”,另一个线程将无法获得锁定。

怎么解决? setnx指令本身不支持来电超时时间。 幸运的是,redis2.6. 12或更高版本在set指令中添加了可选参数,伪代码如下:

set(key,1,30,NX )这样就代替了setnx指令。

2.del 导致误删

此外,这是一个极端场景,其中一个线程成功锁定,设置的超时时间为30秒。

如果线程a因为任何原因运行缓慢,并且30秒后仍无法运行,则锁定将过期并自动释放

线程B得到了锁。

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

怎么避免这种情况呢?可以在del释放锁之前做一个判断,验证当前的锁是不是自己加的锁。

至于具体的实现,可以在加锁的时候把当前的线程ID当做value,并在删除之前验证key对应的value是不是自己线程的ID。

加锁:

String threadId = Thread.currentThread().getId()set(key,threadId ,30,NX)

解锁:

if(threadId .equals(redisClient.get(key))){    del(key)}

也可以在释放锁的时候,通过锁的默认value值UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

但是,这样做又隐含了一个新的问题,判断和释放锁是两个独立操作,不是原子性的

要想实现验证和删除过程的原子性,可以使用Lua脚本来实现。这样就能保证验证和删除过程的正确性了。

3. 出现并发的可能性

还是刚才第二点所描述的场景,虽然我们避免了线程A误删掉key的情况,但是同一时间有A,B两个线程在访问代码块,仍然是不完美的。

怎么办呢?我们可以让获得锁的线程开启一个守护线程,用来给快要过期的锁“续航”。

当过去了29秒,线程A还没执行完,这时候守护线程会执行expire指令,为这把锁“续命”20秒。守护线程从第29秒开始执行,每20秒执行一次。

当线程A执行完任务,会显式关掉守护线程。

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

关于Redis分布式锁的内容就介绍到这里啦。 

参考:程序员zjdby—《什么是分布式锁》

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。