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。
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐