Redis 系列笔记:
第一篇:Redis 基础命令
第二篇:Redis 常见应用场景
第三篇:Redis Cluster集群搭建
第四篇:Redis 主从及哨兵搭建
第五篇:Redis 主从及集群
第六篇:Redis 持久化
第七篇:Redis 分布式锁
第八篇:Redis 底层数据存储结构
第九篇:Redis 面试常问问题
文章目录
前言
Redis分布式锁应用。
提示:以下是本篇文章正文内容,下面案例可供参考
一、为什么需要分布式锁
因为Redis分布式环境中所有的服务器都可以向Redis发送写入命令,且只有一个可以写入命令成功,那么这个写入命令成功的服务器即获得了锁,可以进行后续对资源的操作,其他未写入成功的服务,则进行其他处理。为了保证一个方法在高并发情况下的同一时间只能被同一个线程执行,才需要分布式锁。
二、分布式锁具备哪些条件
互斥性
:锁的目的是获取资源的使用权,所以任意时刻只让一个客户端持有锁,这一点要尽可能保证;只有一个客户端能持有锁。
安全性
:锁只能被持有的客户端删除,不能被其他客户端删除
锁失效机制
:避免死锁情况发生。当一个客户端在持有锁期间内,由于意外崩溃而导致未能主动解锁,其持有的锁也能够被正常释放,并保证后续其它客户端也能加锁;
可重入性
:当一个客户端获取了锁之后,可以再次对其请求加锁。
高性能和高可用
:加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效。
可靠性
:需要有一定程度的异常处理能力、容灾能力。
三、实现分布式锁的几种方法
1. 单实例分布式锁
setnx
: 如果key不存在,则会将key设置为value,并返回1;如果key存在,不会有任务影响,返回0。
两种简单使用方法:
1、
set
时加上nx
和ex|px
参数
2、setnx + expire
组合。
1. SET NX EX|PX
和下面使用LUA
脚本一样,SET NX EX|PX
是一个原子性操作。虽然是原子性操作,但也有可能会出现下面这种情况:
由于网路延迟导等原因致业务还没执行完,锁就过期释放了。这时其他进程来获取锁,获取成功,但是之前的业务代码还没有执行完导致数据异常。
对于锁过期释放,业务没执行完的问题,redisson框架解决了这个问题。redisson 是 redis 官方的分布式锁组件。上面的这个问题 ——> 失效时间设置多长时间为好?这个问题在 redisson 的做法是:每获得一个锁时,只设置一个很短的超时时间,同时起一个线程【watch dog看门狗
】,它是一个后台线程,在每次快要到超时时间时去刷新锁的超时时间。在释放锁的同时结束这个线程。
2. SETNX + EXPIRE
先用 setnx
来抢锁,如果抢到之后,再用 expire
给锁设置一个过期时间,防止锁忘记了释放。但是这个方案中,setnx
和 expire
两个命令分开了,「不是原子操作」。如果执行完setnx加锁,正要执行expire设置过期时间时,服务重启维护了,那么这个锁就变成死锁了。
为了解决上面的这种死锁的情况,这里提供了两种解决方法:
1、
setnx
设置的value
值是系统时间
+过期时间
。如果加锁失败,再拿出value值校验一下。当前做法需要注意:
- 分布式环境下,每个客户端的时间必须同步
- 如果锁过期的时候,并发多个客户端同时请求过来,最终只能有一个客户端加锁成功,但是该客户端锁的过期时间,可能被别的客户端覆盖。
- 该锁没有保存持有者的唯一标识,可能被别的客户端释放/解锁。(可以设置一个标记当前线程唯一的随机数,删除时验证)
2、使用
LUA
脚本来保证原子性
2. 多机实现的分布式锁
当客户端A 在Redis的master节点上拿到了锁,但是加锁的key还没同步到slave节点。恰好这时,master节点发生故障,一个slave节点就会升级为master节点。客户端B 就可以获取同个key的锁,但客户端A 也已经拿到锁了,锁的安全性就没了。
1. Redlock
为了解决这个问题,Redis作者 antirez提出一种高级的分布式锁算法:Redlock
。RedLock的实现步骤:如下:
1、获取当前时间,以毫秒为单位。
2、按顺序向5个master节点请求加锁。客户端设置网络连接和响应超时时间,并且超时时间要小于锁的失效时间。如果超时,跳过该master节点,尽快去尝试下一个master节点。
- 比如:TTL(失效时间)为5s,设置获取锁最多用1s(请求所有master时间和),所以如果一秒内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁
3、用客户端获取所有能获取的锁后的时间减去开始获取锁时间(即步骤1记录的时间),得到获取锁使用的时间。当且仅当超过一半(
N/2+1
)的Redis master节点都获得锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
4、.如果成功获取锁,则锁的真正有效时间是 TTL减去第三步的时间差 的时间;
- 比如:TTL 是5s,获取所有锁用了1s,则真正锁有效时间为4s(其实应该再减去时钟漂移【系统时间重新对时或者被修改】,可以给
watch dog
间隔时间延长,可以极大概率上解决问题);
5、如果获取锁失败(获取到锁的master实例至少N/2+1个,有或者获取锁时间已经超过了有效时间),客户端要在所有的master节点上解锁(即便有些master节点根本就没有加锁成功,也需要解锁,以防止有些漏网之鱼)。
具体使用存在争议,不太推荐使用。Redisson 的开发者认为 Redis 的红锁也存在争议(参考下面链接),但是为了保证可用性,RLock 对象执行的每个 Redis 命令执行都通过 Redis 3.0 中引入的 WAIT 命令进行同步。
WAIT 命令会阻塞当前客户端,直到所有以前的写命令都成功的传输并被指定数量的副本确认。如果达到以毫秒为单位指定的超时,则即使尚未达到指定数量的副本,该命令也会返回。 WAIT 命令同步复制也并不能保证强一致性,不过在主节点宕机之后,只不过会尽可能的选择最佳的副本(slaves)。如果考虑高可用并发推荐使用Redisson,考虑一致性推荐使用zookeeper。
Redisso争议参考:Redisson 分布式锁源码 09:RedLock 红锁的故事
2. zookeeper
参考:Redis实现分布式锁
总结
在极端的情况下,都会有锁失效或锁冲突的情况出现,建议对分布式锁不要强依赖,没有绝对可靠的分布式锁,分布式锁需要与业务的联动配合更加切实可行,不要造成不必要的损失。