【Redis】 分布式锁

1.分布式锁的工作原理

分布式锁是控制分布式系统间同步访问共享资源的一种方式,其可以保证共享资源在并发场景下的数据一致性。

为了达到同步访问,让这些线程在访问共享资源之前先要获取到一个令牌 token,只有具有令牌的线程才可以访问共享资源。这个令牌就是通过各种技术实现的分布 式锁。而这个分布锁是一种"互斥资源",即只有一个。只要有线程抢到了锁,那么其它线 程只能等待,直到锁被释放或等待超时

2.setnx 实现方式

2.1.原理

setnx只有在指定key不存在时才能执行成功,分布式系统中的哪个节点抢先成功执行setnx,谁就抢到了锁,谁就拥有了对共享资源的操作权限,其它节点只能等待锁的释放。一旦拥有锁的节点对共享资源操作完毕,其就可以主动删除该key,即释放锁。然后其它节点就可重新使用setnx命 令抢注该key,即抢注锁

2.2.实现

在Controller 类中添加一个String常量,作为Redis锁的key

2.3.问题

若处理当前请求的APP节点主机在执行完"添加锁"语句后突然宕机,其finally中的释放锁代码没有执行,则其它客户端通过其它节点主机申请资源时,将会无法获得到锁而永久性阻塞

3.为锁添加过期时间

3.1.原理

为了解决 setnx 实现方式中存在的问题,可以为锁添加过期时间,这样就不会出现锁被某节点主机永久性占用的情况,即不会出现节点被永久性阻塞的情况

为key添加过期时间的方式:1)通过expire命令为key指定过期时间,setnx与expire命令是分别执行的,不具备原子性,仍然可能会出现问题;2)在setnx命令中直接给出该key的过期时间,直接在setnx中完成了两步操作,具有原子性。应采用第二种

3.2.问题

因为所有客户端添加的锁的value 值完全相同。若请求a业务复杂,超过锁设定时间,锁自动过期,请求b则可申请到锁,当请求a处理完业务继续执行程序,请求a就会把请求b设置的锁给删除了,导致其它请求就可申请到锁,并与请求b同时访问共享资源,很可能会引发数据的不一致

4.为锁添加标识

4.1.原理

谁添加的锁,该锁只能由谁来删。,为每个申请锁的客户端随机生成一个UUID,使用这个UUID作为该 客户端标识,然后将该UUID作为该客户端申请到的锁的value。在删除锁时,只有在发起当 前删除操作的客户端的UUID与锁的value相同时才可以

4.2.问题

在finally{}中对于删除锁的客户端身份的判断与删除锁操作是两个语句,不具有原子性, 在并发场景下还是会出现a删除b的锁的问题

5.添加Lua脚本

5.1.原理

对客户端身份的判断与删除锁操作的合并,可以通过 Lua 脚本来实现它们的原子性,然后通过eval命令来执行 Lua 脚本。在代码中首先获取到Jedis客户端,然后调用jedis.eval()

5.2.问题

请求a的锁过期,但其业务还未执行完毕;请求b申请到了锁,并在处理业务。若此时两个请求都同时修改了共享的库存数据,那么就又会出现数据不一致的问题,即仍然存在并发问题

对此采用"锁续约"方式解决。在当前业务进程开始执行时,fork出一个子进程,用于启动一个定时任务。该定时任务的定时时间小于锁的过期时间,其会定时查看处理当前请求的业务进程的锁是否已被删除。如果已被删除,则子进程结束;如果未被删除,说明当前请求的业务还未处理完毕,则将锁的时间重新设置为"原过期时间"

6.Redisson 可重入锁

6.1.原理

Redisson 内部使用Lua脚本实现对可重入锁的添加、重入、续约(续命)、释放。Redisson 需要用户为锁指定一个key,但无需为锁指定过期时间(有默认时间),也可制定。由于该锁具有"可重入"功能,所以Redisson会为该锁生成一个计数器,记录一个 线程重入锁的次数

6.2.问题

若在Redis主从集群中,仍存在锁丢失问题。主从集群中,假设节点A为master,节点B、C为slave。若请求a向节点A添加一个key,A收到请求后写入key成功,然后会立即向 处理a请求的应用服务器Sa响应,然后会向slave同步该key。但在同步还未开始时, 节点A宕机,节点B晋升为master。而此时请求b申请锁,节点B中并没有该key,又添加key然后会立即向处理b请求的应用服务器Sb响应。由于Sa与 Sb 都收到了key写入成功的响应,所以它们都可同时对共享数据进行处理

由于主从集群丢失了请求a的锁申请,对于新的master节点B来说,不知道有过请求a的锁申请,因此同意请求b的锁申请,导致主从集群的锁丢失问题

7.Redisson 红锁

7.1.原理

可以防止主从集群锁丢失问题。要求必须要构建出至少三 个Redis 主从集群。若一个请求要申请锁,必须向所有主从集群中提交key写入请求,只有当大多数集群锁写入成功后,该锁才算申请成功

7.2.问题

将所有请求通过锁实现串行化,引发性能问题

8.分段锁

将要共享访问的一个资源, 拆分为多个共享访问资源,这样就会将一把锁的需求转变为多把锁,实现并行化

9.Redisson 详解

9.1.可重入锁

当一个线程获取到锁之后,这个线程可以 再次获取本对象上的锁,而其他的线程是不可以的

JDK中的ReentrantLock 是可重入锁,其是通过**AQS(抽象队列同步器)**实现的锁机制

synchronized 也是可重入锁,其是通过**监视器模式(本质是OS的互斥锁)**实现的锁机制

9.2.公平锁

Redisson 的可重入锁RLock默认是一种非公平锁,但也支持可重入公平锁FailLock。当有多个线程同时申请锁时,这些线程会进入到一个FIFO队列,只有队首元素才会获取到锁, 其它元素等待。只有当锁被释放后,才会再将锁分配给当前的队首元素

9.3.联锁

Redisson 分布式锁可以实现联锁MultiLock。当一个线程需要同时处理多个共享资源时, 可使用联锁。即一次性申请多个锁,同时锁定多个共享资源。联锁可预防死锁。相当于对共 享资源的申请实现了原子性:要么都申请到,只要缺少一个资源,则将申请到的所有资源全部释放。其是OS底层原理中AND型信号量机制的典型应用

9.4.红锁

Redisson 分布式锁可以实现红锁RedLock。红锁由多个锁构成,只有当这些锁中的大部 分锁申请成功时,红锁才申请成功。红锁一般用于解决Redis主从集群锁丢失问题

红锁与联锁的区别是红锁实现的是对一个共享资源的同步访问控制,而联锁实现的是多个共享资源的同步访问控制

9.5.读写锁

通过Redisson 可以获取到读写锁RReadWriteLock。通过RReadWriteLock实例可分别获取到读锁RedissonReadLock与写锁RedissonWriteLock。读锁与写锁分别是实现了RLock的可重入锁。一个共享资源,在没有写锁的情况下,允许同时添加多个读锁。只要添加了写锁,任何读锁与写锁都不能再次添加。即读锁是共享锁,写锁为排他锁

9.6.信号量

通过Redisson可以获取到信号量RSemaphore。RSemaphore的常用场景有两种:1)无论谁添加的锁,任何其它线程都可以解锁,就可以使用RSemaphore;2)当一个线程 需要一次申请多个资源时,可使用RSemaphore。RSemaphore是信号量机制的典型应用

9.7.可过期信号量

通过Redisson 可以获取到可过期信号量PermitExpirableSemaphore。信号量在 RSemaphore 基础上,为每个信号增加了一个过期时间,且每个信号都可以通过独立的ID来 辨识。释放时也只能通过提交该ID才能释放

一个线程每次只能申请一个信号量,当然每次了只会释放一个信号量。这是与 RSemaphore 不同的地方。该信号量为互斥信号量时,其就等同于可重入锁。或者说,可重入锁就相当于信号量为 1 的可过期信号量

可过期信号量与可重入锁的区别:1)可重入锁相当于用户每次只能申请1个信号量,仅一个用户可以申请成功;2)可过期信号量用户每次只能申请1个信号量,但可以有多个用户申请成功

9.8.分布式闭锁

通过Redisson 可以获取到分布式闭锁RCountDownLatch,其与JDK的JUC中的闭锁 CountDownLatch 原理相同,用法类似。其常用于一个或者多个线程的执行必须在其它某些 任务执行完毕的场景

闭锁中定义了一个计数器和一个阻塞队列。阻塞队列中存放着待执行的线程。每当一个 并行任务执行完毕,计数器就减1。当计数器递减到0时就会唤醒阻塞队列的所有线程。通常使用Barrier队列解决该问题,而Barrier队列通常使用Zookeeper实现

相关推荐
零炻大礼包37 分钟前
【SQL server】数据库远程连接配置
数据库
不能再留遗憾了40 分钟前
RabbitMQ 高级特性——消息分发
分布式·rabbitmq·ruby
zmgst1 小时前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
随心............1 小时前
python操作MySQL以及SQL综合案例
数据库·mysql
€☞扫地僧☜€1 小时前
docker 拉取MySQL8.0镜像以及安装
运维·数据库·docker·容器
茶馆大橘1 小时前
微服务系列六:分布式事务与seata
分布式·docker·微服务·nacos·seata·springcloud
CopyDragon1 小时前
设置域名跨越访问
数据库·sqlite
xjjeffery1 小时前
MySQL 基础
数据库·mysql
写bug的小屁孩1 小时前
前后端交互接口(三)
运维·服务器·数据库·windows·用户界面·qt6.3
恒辉信达1 小时前
hhdb数据库介绍(8-4)
服务器·数据库·mysql