Redis-典型应用-分布式锁

发布于:2025-07-18 ⋅ 阅读:(16) ⋅ 点赞:(0)

目录

1.什么是分布式锁?

2.分布式锁的实现

3.引入过期时间

4.引入校验ID

5.引入lua脚本:

6.引入看门狗(watch dog)

7.引入redislock算法:


1.什么是分布式锁?

 在 分布式系统中,会出现多个节点同时访问同一个公共资源, 此时就需要通过锁来作互斥控制,避免出现类似于多线程中的"线程安全"问题.

2.分布式锁的实现

分布式锁实现的本质是 通过一个键值对来标识锁的状态.在redis上写入一个key-val.

mysql的事务也能起到避免线程安全问题实现查询+修改操作,但是在实际使用的过程中,不一定是mysql, 也可能是其他存储介质.

加锁: 不能存在时就设置,存在就设置失败,可以使用redis中的setnx 命令可以实现这个场景.

解锁: 使用del命令实现.

3.引入过期时间

当对一个数据加锁后,若还未执行解锁的逻辑,程序突然崩溃了,就会导致加了锁无法正常释放.

可以通过设置一个过期时间机制,达到过期时间,自动删除.达到锁被自动释放的效果.

通过 set ex nx命令来实现.

setnx ,expire这两个命令也能实现设置过期时间的功能,但不能通过这个命令来实现.

因为redis的命令的原子性,随能保证不允许被别的命令插队,但不能保证一个事务中的所有命令是全部执行成功,若中间有命令执行失败,就无法保证锁的正确设置了.

通过set nx ex一条命令,一定能保证要么执行成功,要么全部执行失败.

4.引入校验ID

当一个服务器对redis设置了一个key-val.,进行了加锁;

而另一个服务器对redis中的该值进行了误删除,这就出现了错误的逻辑.

为了解决这个问题,可以引入校验id,让key对应的val中存储加锁的服务器编号,当进行解锁时, 只有该编号的服务器对其解锁才能解锁成功,其他服务器对其解锁,都会解锁失败.

这样就避免了误删除操作.

5.引入lua脚本:

在一个服务器内部,可能有多个线程.

当两个线程前后对同一个redis进行解锁时,由于解锁的需要先判断是否是该服务器上锁的,然后进行解锁,不是原子性的,可能引发两次解锁.

在执行解锁操作时,服务器一的线程1先执行get命令,判断是否是该线程加的锁,在执行del命令之前, 然后又一个线程2,执行get,此时还未解锁,线程2的get是成功的,

然后线程1执行了del操作,锁释放成功,

线程2再执行del操作,就会执行失败.这就是锁被重复.

但若在3,4步之间,又有一个服务器2的线程执行set nx ex上操作,此时线程b的del命令由于已经通过了判定, 就会将服务器2刚上的锁给释放了,就会出现错误.这是释放锁的命令不是原子性造成的.

可以通过lua脚本来解决解锁时命令不是原子的的问题,

lua脚本是一个轻量级的编程语言,内嵌到redis中,使用lua编写一些程序,然后将这个脚本上传到redis服务器上,就可以让客户来控制redis执行上述lua脚本命令了.

redis执行lua脚本的过程是原子的,相当于一条命令在执行.

6.引入看门狗(watch dog)

设置过期时间,当过期时间到了,还需要继续使用呢,又要怎样将过期时间进行续约呢?

使用"动态续约"来实现.

初始情况下,设置一个过期时间,当过期时间马上到了,还未有线程来释放锁,就会自动续约,再加上一段过期时间,时间又快到了,任务还没执行完,就再续上一段时间.这样既能保证当任务还没执行完时,不会自动解锁,又能保证当程序中途出现崩溃,锁还未释放时,就无法自动续约了,当到了过期时间,就自动解锁了.

7.引入redislock算法:

这里有个问题,当用来加锁的redis出现故障了,突然挂了,那所有的加锁信息就没了.

这就要用之前的知识了,使用主从复制,引入多个从节点,进行主从信息同步,即使主节点挂了,从节点也保存了加锁的信息.

但这里又有一个问题,主从节点数据同步是存在一个延迟性的,万一主节点刚收到一个加锁信息,还没来得及同步给从节点,主节点就挂了,那样就出现数据丢失了.

为了解决这个问题,可以使用redislock 算法:使用集群的方法,引入多个redis主节点,每个主节点都创建多个从节点,当要写入数据时,对每个主节点都执行相同的写入命令,起到一个备份的作用.

 当执行成功的节点个数超过总主节点的一半时,才视为执行成功,即使有某个主节点挂了,还有别的主节点已经存储了数据.这样就能保证加锁,解锁的正确性了.


网站公告

今日签到

点亮在社区的每一天
去签到