Redis:分布式锁setnx(只 实现了 互斥性和容错性)
关键词
- 同时在redis上创建同一个key,只有一个能成功,即获得锁
- 获取锁:原子性操作 set方法(推荐),谁set成功谁获取到锁(过期时间,避免死锁)
- 释放锁:redis+lua脚本实现
- 问题1(同一性):主从架构,主机宕机,重复获得锁问题(可以使用红锁解决99%,降低重复获取概率,redis在分布式环境下是ap模型)
红锁:三个节点,往两个节点上set成功,才能获取锁 - 问题2(续租):无法续租问题(使用Redisson的看门狗进行续租)
- 问题3:(重入性)
一、实现原理
共享资源 互斥
共享资源 串行化
分布式应用中使用锁:(多进程多线程)
- 分布式锁是控制分布式系统之间同步访问共享资源的一种方式
- 利用Redis的单线程特性对共享资源进行串行化处理
单应用中使用锁:(单进程多线程)
- synchronized、ReentrantLock
二、实现方式
2.1 获取锁
方式1 (使用set命令实现) – 推荐
/**
* 使用redis的set命令实现获取分布式锁
* @param lockKey 可以就是锁
* @param requestId 请求ID,保证同一性 uuid+threadID
* @param expireTime 过期时间,避免死锁
* @return
*/
public boolean getLock(String lockKey,String requestId,int expireTime) {
// NX: 保证互斥性
// hset 原子性操作 只要lockKey有效 则说明有进程在使用分布式锁
String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
if("OK".equals(result)) {
return true;
}
return false;
}
方式2 (使用setnx命令实现) – 并发会产生问题
public boolean getLock(String lockKey,String requestId,int expireTime) {
Long result = jedis.setnx(lockKey, requestId); //如果挂了,没有设置expire,别人永远无法获取锁
if(result == 1) {
//成功设置 进程down 永久有效 别的进程就无法获得锁
jedis.expire(lockKey, expireTime);
return true;
}
return false;
}
2.2 释放锁
方式1 (del命令实现) – 并发
/**
* 释放分布式锁
* @param lockKey
* @param requestId
*/
public static void releaseLock(String lockKey,String requestId) {
if (requestId.equals(jedis.get(lockKey))) { //并发会有问题
jedis.del(lockKey);
}
}
问题:如果调用jedis.del()方法的时候,这把锁已经不属于当前客户端的时候会解除他人加的锁。
那么是否真的有这种场景?答案是肯定的,比如客户端A加锁,一段时间之后客户端A解锁,在执行jedis.del()之前,锁突然过期了,此时客户端B尝试加锁成功,然后客户端A再执行del()方法,则将客户端B的锁给解除了。
方式2 (redis+lua脚本实现)–推荐
public static boolean releaseLock(String lockKey, String requestId) {
// 获取到你自己的锁,则删除 ('get', KEYS[1]) == ARGV[1]
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return
redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (result.equals(1L)) {
return true;
}
return false;
}
三、存在问题
单机
- 无法保证高可用
主–从
- 无法保证数据的强一致性,在主机宕机时会造成 锁的重复获得。(红锁方案解决)
无法续租
- 超过expireTime后,不能继续使用
四、本质分析
CAP模型分析
在分布式环境下不可能满足三者共存,只能满足其中的两者共存,在分布式下P不能舍弃(舍弃P就是单机了)。
所以只能是CP(强一致性模型)和AP(高可用模型)。
分布式锁是CP模型,Redis集群是AP模型。 (base)
Redis集群不能保证数据的随时一致性,只能保证数据的最终一致性。
为什么还可以用Redis实现分布式锁?
与业务有关
- 当业务 不需要数据强一致性 时,比如:社交场景,就可以使用Redis实现分布式锁
- 当业务必须要数据的强一致性,即不允许重复获得锁,比如金融场景( 重复下单,重复转账 )就不要使用,可以使用CP模型实现,比如:zookeeper和etcd。