关于redis:Redis做分布式锁可能不那么简单

36次阅读

共计 2727 个字符,预计需要花费 7 分钟才能阅读完成。

在计算机世界里,对于锁大家并不生疏,在古代所有的语言中简直都提供了语言级别锁的实现,为什么咱们的程序有时候会这么依赖锁呢?这个问题还是要从计算机的倒退说起,随着计算机硬件的一直降级,多核 cpu,多线程,多通道等技术把计算机的计算速度大幅度晋升,原来同一时间只能执行一条 cpu 指令的时代曾经过来。随着多条 cpu 指令能够并行执行的起因,原来未曾呈现的资源竞争随着呈现,在程序中的体现就是随处可见的多线程环境。比方要更新数据库的一个信息,如果没有并发管制,多个线程同时操作的话,就会呈现相互笼罩的景象产生。

锁要解决的就是资源竞争的问题,也就是要把执行的指令程序化

为什么须要分布式锁

随着互联网的衰亡,古代软件产生了天翻地覆的变动,以前单机的程序,曾经支撑不了古代的业务。无论是在抗压,还是在高可用等方面都须要多台计算机协同工作来解决问题。古代的互联网零碎都是分布式部署的,分布式部署的确能带来性能和效率上的晋升,但为此,咱们就须要多解决一个分布式环境下,数据一致性的问题。

当某个资源在多零碎之间共享的时候,为了保障大家拜访这个资源数据是统一的,那么就必须要求在同一时刻只能被一个客户端解决,不能并发的执行,否者就会呈现同一时刻有人写有人读,大家拜访到的数据就不统一了。

在分布式系统的时代,传统线程之间的锁机制,就没作用了,零碎会有多份并且部署在不同的机器上,这些资源曾经不是在线程之间共享了,而是属于过程(服务器)之间共享的资源。

因而,为了解决这个问题,咱们就必须引入「分布式锁」。分布式锁,是指在分布式的部署环境下,通过锁机制来让多客户端互斥的对共享资源进行拜访。分布式锁的特点如下:

  • 互斥性:和咱们本地锁一样互斥性是最根本,然而分布式锁须要保障在不同节点的不同线程的互斥。
  • 可重入性:同一个节点上的同一个线程如果获取了锁之后那么也能够再次获取这个锁。
  • 锁超时:和本地锁一样反对锁超时,避免死锁。
  • 高效,高可用:加锁和解锁须要高效,同时也须要保障高可用避免分布式锁生效,能够减少降级。
  • 反对阻塞和非阻塞:和 ReentrantLock 一样反对 lock 和 trylock 以及 tryLock(long timeOut)。

基于 redis 分布式锁

如果你通过网络搜寻分布式锁,最多的就是基于 redis 的了。基于 redis 的分布式锁得益于 redis 的单线程执行机制,单线程在执行上就保障了指令的程序化,所以很大水平上升高了开发人员的思考设计老本。然而,基于 redis 做分布式锁难道真的这么容易吗?

原子操作

基于 redis 的分布式锁常用命令是

SETNX key value

只在键 key 不存在的状况下,将键 key 的值设置为 value。若键 key 曾经存在,则 SETNX 命令不做任何动作。SETNX 是『SET if Not eXists』(如果不存在,则 SET) 的简写。代码示例:


redis> SETNX redislock "redislock"    # redislock 设置胜利
(integer) 1

redis> SETNX redislock "redislock2"   # 尝试笼罩 redislock,失败
(integer) 0

redis> GET redislock                   # 没有被笼罩
"redislock"

胜利获取到锁之后,而后设置一个过期工夫 (这里防止了客户端 down 掉,锁得不到开释的问题)

redis> expire redislock 5

胜利拿到锁的客户端顺利进行本人的业务,业务代码执行完,而后再删除该 key

redis> DEL redislock

如果所有都想设想的那么顺利,程序员 TMD 就不必 996 了。如果客户端拿到锁之后,执行设置超时指令之前 down 掉了(事实总是那么喜剧),那这个锁就永远都开释不了. 兴许你会想到用 Redis 事务来解决。然而这里不行,因为 expire 是依赖于 setnx 的执行后果的,如果 setnx 没抢到锁,expire 是不应该执行的。事务里没有 if-else 分支逻辑,事务的特点是一口气执行,要么全副执行要么一个都不执行。公司几个亿的业务又被你耽搁了 …

以上状况的呈现是因为两个命令并非一个原子性操作,所以在 redis 2.8 版本之后呈现了新的命令

SETEX key seconds value

所以当初能够利用一条原子性操作的命令来获取锁了

redis> SETEX redislock 60 redislock
OK

redis> GET redislock  # 值
"redislock"

redis> TTL redislock  # 残余生存工夫
(integer) 49
超时问题

在失常的业务当中,当一个线程获取到锁并且设置了锁的过期工夫之后,会呈现因为业务代码执行工夫过长,锁因为达到超时工夫主动开释的状况。主动开释之后,其余的线程就会获取到分布式锁,导致业务代码不会串行执行。如果业务上容许这样的状况偶然产生,那程序员就开干吧,最初顶多人工干预一下,update 一下数据库。

为了防止这类状况产生,在应用 redis 分布式锁的时候,业务方应尽量避免长时间执行的代码工作。

如果设置锁的超时工夫比拟长,在肯定水平上能够缓解业务代码执行工夫长锁主动到期的问题,然而一旦业务代码 down 掉,其余期待锁的线程期待的工夫会比拟长,这种状况下,确保获取到锁的程序不会 down 成为了次要问题。

获取锁失败

当锁被一个调用方获取之后,其余调用方在获取锁失败之后,是持续轮询还是间接业务失败呢?如果是持续轮询的话,同步状况下以后线程会始终处于阻塞状态,所以这里轮询的状况还是倡议应用异步。

可重入性

可重入性是指曾经领有锁的客户端再次申请加锁,如果锁反对同一个客户端反复加锁,那么这个锁就是可重入的。如果基于 redis 的分布式锁要想反对可重入性,须要客户端封装,能够应用 threadlocal 存储持有锁的信息。这个封装过程会减少代码的复杂度,所以菜菜不举荐这样做。

redis 挂了

如果在多个客户端获取锁的过程中,redis 挂了怎么办呢?如果一个客户端曾经获取到了锁,这个时候 redis 挂了(如果是 redis 集群),其余的 redis 服务器会接着提供服务,这个时候其余客户端能够在新的服务器上获取到锁了,这也导致了锁意义的失落。有趣味的同学能够去看看 RedLock,这种计划以就义性能的代价解决了这个问题。

时钟跳跃问题

在某些时候,redis 的服务器工夫产生的跳跃,因为锁的过期工夫依赖于服务器工夫,所以也会呈现两个客户端同时获取到锁的状况产生。

当把以上问题都有解决方案了之后,基于 redis 的分布式锁才能够放心使用

基于 redis 设计简略分布式锁容易,然而设计完满分布式锁不易,还感觉基于 redis 的分布式锁好做吗?

更多精彩文章

  • 分布式大并发系列
  • 架构设计系列
  • 趣学算法和数据结构系列
  • 设计模式系列

正文完
 0