0%

不推荐使用redis分布式锁的原因

[toc]

概述

目前比较主流的分布式锁有两种选择:一种是使用redis集群做分布式锁,另外一种是使用zookeeper,这两种分布式锁有着各自的特点,但是在技术选型上,我还是推荐使用zookeeper来做分布式锁,至于为什么不推荐redis集群来做分布式锁,我会在下面阐述。

CAP理论

1.Consistency 一致性

一致性指“all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,所以,一致性,说的就是数据一致性。分布式的一致性

对于一致性,可以分为从客户端和服务端两个不同的视角。从客户端来看,一致性主要指的是多并发访问时更新过的数据如何获取的问题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。

一致性是因为有并发读写才有的问题,因此在理解一致性的问题时,一定要注意结合考虑并发读写的场景。

从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了不同的一致性。

三种一致性策略

  • 对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性。
  • 如果能容忍后续的部分或者全部访问不到,则是弱一致性。
  • 如果经过一段时间后要求能访问到更新后的数据,则是最终一致性。

CAP中说,不可能同时满足的这个一致性指的是强一致性。

2.Availability 可用性

可用性指“Reads and writes always succeed”,即服务一直可用,而且是正常响应时间。

对于一个可用性的分布式系统,每一个非故障的节点必须对每一个请求作出响应。所以,一般我们在衡量一个系统的可用性的时候,都是通过停机时间来计算的。

3.Partition Tolerance分区容错性

分区容错性指“the system continues to operate despite arbitrary message loss or failure of part of the system”,即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。

分区容错性和扩展性紧密相关。在分布式应用中,可能因为一些分布式的原因导致系统无法正常运转。好的分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,或者是机器之间有网络异常,将分布式系统分隔未独立的几个部分,各个部分还能维持分布式系统的运作,这样就具有好的分区容错性。

为什么不建议使用redis分布锁

主从切换可能丢失锁信息

考虑一下这样的场景:在分布式环境中,很多并发需要锁来同步,当使用redis分布式锁,通用的做法是使用redis的setnx key value px 这样的命令,设置一个字段,当设置成功说明获取锁,设置不成功说明锁被占用,当获取所之后需要删除锁,也就是删除设置的锁字段,这是锁可以被其他占用。

这里在主从切换回出现问题,当第一个线程在主服务器上设置了锁,但是这时候从服务器并没有及时同步主服务器的状态,也就是没有同步主服务器中的锁字段,而此时,主服务器挂了,redis的哨兵模式升级从服务器为主服务器,如果在并发量大的情况下,虽然第一个线程获取了锁,其他线程会在当前的主服务器(之前的从服务器,但是并没有同步已经设置的锁字段)上设置锁字段,这样并不能保证锁的互斥性。

缓存易失性

假如第一个线程设置了锁,但是之后触发内存淘汰机制很不幸淘汰了设置的锁字段,接下来的线程在第一个线程没有释放锁的情况下,也是重新设置锁字段的,这样并不能保证锁的安全性。

其他

但是如果不在意上诉问题,其实可以用 redis 做分布式锁,毕竟简单。

redis 分布式锁的挑战

执行时间超过锁的过期时间

  • 客户 1 获取锁成功并设置 30 秒超时;
  • 客户 1 因为一些原因导致执行很慢(网络问题、发生 FullGC……),过了 30 秒依然没执行完,但是锁过期「自动释放了」;
  • 客户 2 申请加锁成功;
  • 客户 1 执行完成,执行 DEL 释放锁指令,这个时候就把 客户 2 的锁给释放了。

锁续期

我们可以让获得锁的线程开启一个守护线程,用来给快要过期的锁「续航」。加锁的时候设置一个过期时间,同时客户端开启一个「守护线程」,定时去检测这个锁的失效时间。如果快要过期,但是业务逻辑还没执行完成,自动对这个锁进行续期,重新设置过期时间。

避免释放别人的锁

在释放锁的时候,客户端将自己的「唯一标识」与锁上的「标识」比较是否相等,匹配上则删除,否则没有权利释放锁。通过 SET lock_resource_name $unique_id NX PX $expire_time,同时启动守护线程为快要过期单还没执行完毕的客户端的锁续命。由于是多个指令所以需要 lua 脚本来保证原子性。

解决 redis 主从切换的问题

如果客户端 1 刚往 master 节点写入一个分布式锁,此时这个指令还没来得及同步到 slave 节点。此时,master 节点宕机,其中一个 slave 被选举为新 master,这时候新 master 是没有客户端 1 写入的锁,锁丢失了。此刻,客户端 2 线程来获取锁,就成功了。

虽然这个概率极低,但是我们必须得承认这个风险的存在。Redis 的作者为了统一分布式锁的标准,搞了一个 Redlock,算是 Redis 官方对于实现分布式锁的指导规范,https://redis.io/topics/distlock,但是这个 Redlock 也被国外的一些分布式专家给喷了。

太麻烦不看了

参考文档

https://blog.csdn.net/MOVIE14/article/details/82053391

https://www.51cto.com/article/689646.html