三、详解Redis分布式锁&Redisson分布式锁

一、什么是分布式锁?

在传统的java进程中,我们常常用Synchronized三、详解Synchronized-CSDN博客或者ReentrantLock五、详解ReentrantLock-CSDN博客来对临界区进行加锁,防止多个线程之间并行访问,导致数据读写异常。但是这种锁的粒度仅限于当前jvm中,在工业生产环境下,往往一个web项目会部署多台机器,也就意味着会有多个jvm。那么这几个jvm是独立的,这就导致上述的锁失效。

分布式锁是一种在分布式系统环境下,通过多个节点对共享资源进行访问控制的机制。在分布式系统中,为了防止多个节点同时操作同一份数据,从而导致数据的不一致,需要使用分布式锁来确保在同一时刻只有一个节点能够操作数据。

二、常见的分布式锁实现方式

分布式锁的实现方式有很多种,常见的有基于数据库的分布式锁、基于缓存(如Redis)的分布式锁、基于ZooKeeper的分布式锁等。

而在互联网工业生产环境中,由于对性能的要求,基本上都在使用Redis来进行分布式锁。

三、Redis分布式锁的实现

3.1 setnx和expire命令

在redis的命令 三、详解Synchronized-CSDN博客 中,我们学到过setnx命令。setnx命令常用于实现分布式锁。具体来说,多个客户端可以尝试使用setnx命令来创建相同的锁(key),只有一个客户端的操作会成功,成功的客户端将获得锁,失败的客户端将不会获得锁。这样可以确保同时只有一个客户端能够操作被锁住的资源,避免并发操作导致的数据不一致。

但是setnx的命令没有设置超时时间,因此当前的key会一直存放在redis中,除非有线程主动的del key。因此,在添加setnx key 的时候,需要在后面设置expire key 过期时间。

如果 setnx key 和 expire key 中间,服务挂了,那么当前key将一直存在Redis,导致其他线程无法加锁。

因此,可以使用set命令,因为set命令后面有很多参数:

set key value ex 10 nx。这样,设置值和设置过期时间将是一个原子的操作。

3.2 setnx分布式锁的缺点

1、setnx需要手动释放,即使设置了过期时间,这个时间也没有一个标准。如果时间过短,其他线程就可以获取锁,如果时间太长,其他线程就得等。

2、不支持重入。相同的线程重复进行加锁的时候,会被阻塞住。

3、锁误删。这个和redis过期时间有关,例如:a线程加了锁,然后执行逻辑,由于redis的过期时间很短,a还没执行完,锁就过期了。此时b线程加锁成功,开始处理逻辑。那么这个时候a线程执行结束,执行finally的锁释放。这就导致a线程释放了b线程加的锁。

下面给出一个简单的demo:

java 复制代码
class LockTest{

	private RedisClient redisClient;

	private static final UUID = UUID.uuid();

	//加锁

	public boolean tryLock(String lockName, long expireTime){

		String value = UUID + System.currentThread(); //防止锁误删

		return redisClient.set(lockName, value, expireTime); //设置redis的key和value以及过期时间 
	}

	//解锁

	public void unLock(String lockName){
		String targetValue = UUID + System.currentThread(); 
		Strign value = redisClient.get(lockName);
		if(targetValue.equals(value)){
			redisClient.delete(locaName);
		}
	}

}

四、Redisson分布式锁

4.1 lua脚本

在上述demo中,尤其是在unlock的方法中,redisClient.get() 和redisClient.delete()方法是隔开的。如果get方法之后,线程被挂起,那么delete()方法还是可能被误删。

根本原因就是读写操作不是原子的。如果get和delete处于原子操作中,那么就不会出现误删的现象。

在redis中,支持lua脚本语言,而lua脚本在redis执行过程是一个原子的,也就意味着,要么整个lua脚本全执行,要么全不执行。

具体的lua脚本的语法这里不做多说明,大家可以自行查询。

这里将上面的delete方法进行修改:

java 复制代码
public void unLock(String lockName){
	String targetValue = UUID + System.currentThread(); 
		
   	redisClient.eval("local id = redis.call('get', KEYS[1]) 
   		local targetId = ARGV[1]if(id == targetId) then 
   		return redis.call('del', KEYS[1])end return 0", 
   		Lists.newArrayList(lockName), Lists.newArrayList(targetValue));
}

4.2 源码解析

相关推荐
落叶的悲哀26 分钟前
mysql tidb like查询有换行符内容问题解决
数据库·mysql·tidb
回家路上绕了弯32 分钟前
服务器大量请求超时?从网络到代码的全链路排查指南
分布式·后端
wangchen_037 分钟前
MySQL索引
数据库·mysql
哈__40 分钟前
数据库迁移实操与金仓数据库技术优势:从语法兼容到自动化落地
数据库
蟹至之1 小时前
增删查改(其一) —— insert插入 与 select条件查询
数据库·mysql·增删查改
Yeats_Liao1 小时前
时序数据库系列(七):性能监控实战指标收集
数据库·后端·时序数据库
无心水2 小时前
【中间件:Redis】1、Redis面试核心:线程模型深度解析(6.0前后变化+工作流程)
数据库·redis·面试·redis面试·redis原理·redis线程模型·后端技术
milanyangbo2 小时前
从同步耦合到异步解耦:消息中间件如何重塑系统间的通信范式?
java·数据库·后端·缓存·中间件·架构
陈辛chenxin2 小时前
【大数据技术06】大数据技术
大数据·hadoop·分布式·python·信息可视化
执笔论英雄2 小时前
【大模型训练】megatron分布式并行训练的调用流程,关键函数forward_backward_func
分布式