【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实现

相关推荐
power-辰南31 分钟前
高并发系统架构设计全链路指南
分布式·系统架构·高并发·springcloud
qq_5298353535 分钟前
对计算机中缓存的理解和使用Redis作为缓存
数据库·redis·缓存
月光水岸New3 小时前
Ubuntu 中建的mysql数据库使用Navicat for MySQL连接不上
数据库·mysql·ubuntu
狄加山6753 小时前
数据库基础1
数据库
我爱松子鱼3 小时前
mysql之规则优化器RBO
数据库·mysql
chengooooooo3 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
Rverdoser4 小时前
【SQL】多表查询案例
数据库·sql
Galeoto4 小时前
how to export a table in sqlite, and import into another
数据库·sqlite
希忘auto5 小时前
详解Redis在Centos上的安装
redis·centos
人间打气筒(Ada)5 小时前
MySQL主从架构
服务器·数据库·mysql