何为分布式锁?

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

业务场景举例

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

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

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

分布式锁

       分布式锁的本质上要实现的最终目的就是在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) 1
127.0.0.1:6379> expire lock-star 5
... do something ...
127.0.0.1:6379> del lock-star
(integer) 1

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

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

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

如何解决?

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

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

上面这个指令就是​​setnx​​​和​​expire​​​组合一起的​​原子指令​​。