原理
set key value [expiration EX seconds|PX milliseconds] [NX|XX]
EX seconds:将键的过期时间设置为 seconds 秒
SET key value EX seconds === SETEX key seconds value
PX milliseconds:将键的过期时间设置为 milliseconds 毫秒
SET key value PX milliseconds === PSETEX key milliseconds value
NX:只在键不存在时,才对键进行设置操作。
SET key value NX === SETNX key value. (SET if Not eXists)
XX : 只在键已经存在时, 才对键进行设置操作。
Redis 跟 zk
Redis: ap
1.线程 A setnx(上锁的对象,超时时的时间戳 t1),如果返回 true,获得锁。
2.线程 B 用 get 获取 t1,与当前时间戳比较,判断是是否超时,没超时 false,若超时执行第 3 步;
3.计算新的超时时间 t2,使用 getset 命令返回 t3(该值可能其他线程已经修改过),如果t1==t3,获得锁,如果 t1!=t3 说明锁被其他线程获取了。
4.获取锁后,处理完业务逻辑,再去判断锁是否超时,如果没超时删除锁,如果已超时,不用处理(防止删除其他线程的锁)。
Zk: cp
1.客户端对某个方法加锁时,在 zk 上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点 node1;
2.客户端获取该路径下所有已经创建的子节点,如果发现自己创建的 node1 的序号是最小的,就认为这个客户端获得了锁。
3.如果发现 node1 不是最小的,则监听比自己创建节点序号小的最大的节点,进入等待。4.获取锁后,处理完逻辑,删除自己创建的 node1 即可。
区别:zk 性能差一些,开销大,实现简单。
Redis实现分布式锁问题
1.如果setnx是成功的,但是expire设置失败,那么后面如果出现了释放锁失败的问题,那么这个锁永远也不会被得到,业务将被锁死?
解决的办法:使用set的命令,同时设置锁和过期时间
set key value [EX seconds] [PX milliseconds] [NX|XX]
低版本的jedis并不支持多参数的set()方法
2.如果a线程业务处理时间超多设置的过期时间,导致b线程获取锁,a线程处理完业务删除b线上获取的锁。
3.删除分布式锁要保证原子性
a.使用LUA脚本
b. 使用redis事务 (multi,exec,watch,unwatch)
4. redis怎么实现缓存续期(不确定业务处理时间,多于过期时间怎么处理)
- redisson
- redission是对原生redis操作进行封装的客户端,redistemplate是springboot提供的简化redis操作的模板。redisson监控锁,锁快超时了自动加超时时间
- 业务执行完手动释放:上锁的时候key是唯一,但value可以不同,设置一个随机数,lua脚本 判断value是否是当前value,是的话删除,两步操作要保持原子性
- 让获得锁的线程开启一个守护线程,用来给快要过期的锁“续约”
5. 用于redis的服务肯定不能是单机,因为单机就不是高可用了,一量挂掉整个分布式锁就没用了。在集群场景下,如果A在master拿到了锁,在没有把数据同步到slave时,master挂掉了。B再拿锁就会从slave拿锁,而且会拿到。又出现了两个线程同时拿到锁。
采用RedLock机制RedLock
假设有5个redis节点,这些节点之间既没有主从,也没有集群关系。客户端用相同的key和随机值在5个节点上请求锁,请求锁的超时时间应小于锁自动释放时间。当在3个(超过半数)redis上请求到锁的时候,才算是真正获取到了锁。如果没有获取到锁,则把部分已锁的redis释放掉。
根本原因就是redis的集群属于AP,分布式锁属于CP,用AP去实现CP是不可能的。
1.获取当前时间(单位是毫秒)。
2.轮流用相同的key和随机值在N个节点上请求锁,在这一步里,客户端在每个master上请求锁时,会有一个和总的锁释放时间相比小的多的超时时间。比如如果锁自动释放时间是10秒钟,那每个节点锁请求的超时时间可能是5-50毫秒的范围,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,我们应该尽快尝试下一个master节点。
3.客户端计算第二步中获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁((N/2) +1),而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了。
4.如果锁获取成功了,那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。
5.如果锁获取失败了,不管是因为获取成功的锁不超过一半(N/2+1)还是因为总消耗时间超过了锁释放时间,客户端都会到每个master节点上释放锁,即便是那些他认为没有获取成功的锁。
Redisson
加锁机制
互斥锁机制
watch dog 自动延期机制
可重入加锁机制
释放锁机制