> 文档中心 > 看完这篇,你还不明白Redis分布式锁?

看完这篇,你还不明白Redis分布式锁?


何为分布式锁?

       分布式应用进行逻辑处理时经常会遇到并发问题,而处理并发问题的方式之一就是分布式锁。在很多场景中,我们为了保证数据的最终一致性,就会选择很多技术方案来支撑,例如分布式事务、分布式锁等,那么什么是分布式锁,分布式锁又会应用到哪些业务场景呢?

业务场景举例

  • 一个操作要修改用户的状态,那这个操作又该如何实现分布式锁呢?

那么上述的问题又该如何解决呢?
场景一:修改用户状态,首先分析如何去修改用户的状态,这一操作需要先读出用户的状态,在内存里进行修改,改完了再存回去。但是问题随着就显现出来了,如果这样的操作同时进行,那么就会出现并发问题,因为“读取”"保存状态"这两个操作都不是原子操作

原子操作是指不会被线程调度机制打断的操作。这种操作一旦开始,就会一直运行到结束,中间不会有任何线程切换。

分布式锁

       分布式锁的本质上要实现的最终目的就是在Redis里面占一个"座位",当别的线程也要来占座位时,发现那里已经有一位"同学"了,就只好选择放弃或者稍后再试。
"占座位"一般使用setnx(set if not exists)命令,只允许被一个同学占座位,先来先占,下课了,再调用del命令释放"座位"

127.0.0.1:6379> setnx lock-star 1(integer) 1... do something ...127.0.0.1:6379> del lock-star(integer) 1

但是问题又来了,如果逻辑执行到中间出现异常了,可能就会导致del命令没有被调用,这样就会一直卡在这(死锁),锁就永远不会得到释放。那么该如何解决呢?我们可以在拿到锁之后,再给锁设置一个过期时间,比如 5s 这样即使中间出现异常也可以保证在5s后自动将锁释放掉。

127.0.0.1:6379> setnx lock-star 1(integer) 1127.0.0.1:6379> expire lock-star 5... do something ...127.0.0.1:6379> del lock-star(integer) 1

给锁加上过期时间,看似解决了,其实逻辑依旧存在漏洞,如果在setnxexpire中间服务器进程挂掉了,就会导致expire无法被调用,也就意味着死锁。
在这里插入图片描述
出现这种问题的根源在于setnxexpire是两条指令而不是原子指令,如果这两条指令可以一起执行就不会出现问题。那么如果使用Redis事务来解决这种问题是否可行呢?

浅提Redis事务过程
Redis提供的事务是将多个命令进行打包,然后一次性的、类似队列先进先出(FIFO)的特点有序的执行。在执行的过程中不会被打断
,当事务队列中的所有指令都被执行(无论是成功还是失败)之后,事务才会结束!

简单了解完事务的执行过程后会发现,其实在这里运用事务并不可行,因为expire是依赖于setnx的执行结果,如果setnx没有抢到”座位“expore是不应该执行的,这种操作就相当于Java中的if-else分支逻辑过程,而Redis事务的特点是一气呵成,执行过程中不会被打断,要么全部执行,要么一个都不执行。

如何解决?

为了解决上述问题的出现,在Redis2.8中set指令新加了扩展参数,使得setnxexpire可以一起执行,就可以解决分布式锁的问题。

127.0.0.1:6379> setnx lock-star 1 ex 5 nxOK... do something ...127.0.0.1:6379> del lock-star(integer) 1

上面这个指令就是setnxexpire组合一起的原子指令