【Redis】Redis分布式锁

【Redis】Redis分布式锁

分布式应用进行逻辑处理时经常会遇到并发问题。如果一个操作要修改用户的状态。修改状态需要先读出用户的状态,在内存里进行修改,改完了再存回去。

如果这样的操作同时进行,就会出现并发问题,因为“读取”和“保存状态”这两个操作不是原子操作。(原子操作是指不会被线程调度机制打断的操作。这种操作一旦开始,就会一直运行到结束,中间不会有任何线程切换)

这个时候就要使用到分布式锁来限制程序的并发执行。Redis 分布式锁使用得非常广泛,它是面试的重要考点之一,很多同学都知道这个知识,也大致知道分布式锁的原理,但是具体到细节的掌握上,往往并不完全正确。

分布式锁是什么?

分布式锁本质上要实现的目标就是在 Redis 里面占一个“坑”,当别的进程也要来占坑时,发现那里已经有一根“大萝卜”了,就只好放弃或者稍后再试。

占坑一般使用setnx(set if not exists)指令,只允许被一个客户端占坑。先来先占用完了,再调用del指令释放“坑”

> setnx lock:codehole true
OK
... do something critical ...
> del lock:codehole
(integer) 1

但是有个问题,如果逻辑执行到中间出现异常了,可能会导致 del 指令没有被调用,这样就会陷入死锁,锁永远得不到释放。

于是我们在拿到锁之后,再给锁加上一个过期时间,比如 5s,这样即使中间出现异常也可以保证5s之后锁会自动释放。

> setnx lock:codehole true
OK
> expire lock:codehole 5
... do something critical ...
> del lock:codehole
(integer) 1

但是以上逻辑还有问题。如果在setnxexpire之间服务器进程突然挂掉了,可能是因为机器掉电或者是人为造成的,就会导致expire得不到执行也会造成死锁。

这种问题的根源就在于setnxexpire是两条指令而不是原子指令。如果这两条指令可以一起执行就不会出现问题。也许你会想到用 Redis 事务来解决,但在这里不行,因为expire是依赖于setnx 的执行结果的,如果setnx没抢到锁,expire是不应该执行的。事务里没有 if-else 分支逻辑,事务的特点是一口气执行,要么全部执行,要么一个都不执行。

为了解决这个疑难,Redis 开源社区涌现了许多分布式锁的 library,专门用来解决这个问题,实现方法极为复杂,小白用户一般要费很大的精力才可以弄懂。如果你需要使用分布式锁,意味着你不能仅仅使用 Jedis 或者 redis-py,还得引入分布式锁的 library。

为了治理这个乱象,在 Redis 2.8 版本中,作者加入了 set 指令的扩展参数,使得 setnx 和expire 指令可以一起执行,彻底解决了分布式锁的乱象。从此以后所有的第三方分布式锁 library 都可以休息了。

> set lock:codehole true ex 5 nx
OK
... do something critical ...
> del lock:codehole

上面这个指令就是setnxexpire组合在一起的原子指令,它就是分布式锁的奥义所在。

超时问题

Redis 的分布式锁不能解决超时问题,如果在加锁和释放锁之间的逻辑执行得长,以至于超出了锁的超时限制,就会出现问题。因为这时候第一个线程持有的锁过期了,临界区的逻辑还没有执行完,而同时第二个线程就提前重新持有了这把锁,导致临界区代码不能得到严格串行执行。

为了避免这个问题,Redis 分布式锁不要用于较长时间的任务。如果真的偶尔出现了问题,造成的数据小错乱可能需要人工介入解决。

有一个稍微安全一点的方案是将 set 指令的 value 参数设置为一个随机数,释放锁时先匹配随机数是否一致,然后再删除 key,这是为了确保当前线程占有的锁不会被其他线程释放,除非这个锁是因为过期了而被服务器自动释放的。

但是匹配 value 和删除 key 不是一个原子操作,Redis 也没有提供类似于delifequals这样的指令,这就需要使用 Lua 脚本来处理了,因为 Lua 脚本可以保证连续多个指令的原子性执行。

可重入性

可重入性是指线程在持有锁的情况下再次请求加锁,如果一个锁支持同一个线程的多次加锁,那么这个锁就是可重入的。比如Java语言里有个 ReentrantLock就是可重入锁。Redis 分布式锁如果要支持可重入,需要对客户端的 set 方法进行包装,使用线程的Threadlocal 变量存储当前持有锁的计数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值