欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

探索——redis实现分布式锁

程序员文章站 2022-07-12 17:03:36
...

场景一
Redis最简单实现锁的方式就是使用setnx命令,原理其实就是当缓存中没有当前key就会设置成功(即成功获取锁)。
伪代码:

//获取redis锁
if(redisUtil.setnx("前缀+UUID",1)==1){
	try{
		//执行业务逻辑
		service.save();
	}finally{
		//释放锁
		redisUtil.del("前缀+UUID");
	}
}

场景二
但是这样的方式会有一个严重的问题,比如当在执行业务逻辑的时候,还没到完全释放锁系统就发生了宕机,那就会造成“死锁”这个问题,所以我们可以在获取锁的时候去设置锁的过期时间,到期后会自动释放锁。
伪代码:

//获取redis锁
if(redisUtil.setnx("前缀+UUID",1)==1){
	try{
		//设置过期时间30s
		redisUtil.expire("前缀+UUID",30);
		//执行业务逻辑
		service.save();
	}finally{
		//释放锁
		redisUtil.del("前缀+UUID");
	}
}




场景三
以上情景也还是会有一个问题,因为设置锁和设置过期时间并不是一个原子操作,所以他们之间的过程要是发生了某些问题,这样的锁也就会造成死锁。所以我们也可以修改setnx代码。
伪代码:

//获取redis锁
if(redisUtil.set("前缀+UUID"130,NX)==1){
	try{
		//执行业务逻辑
		service.save();
	}finally{
		//释放锁
		redisUtil.del("前缀+UUID");
	}
}

场景四
以上的代码还是不够完善,比如说当你设置了锁过期的时间为30s,但是业务逻辑的执行超过了30s。也就是说,当你A系统业务逻辑执行完,花费了50s,其实锁在30s的时候已经释放,但是在B系统30s进入时候,是已经成功获取了锁,然后A系统在50s释放的锁实际上是删除了B系统的锁,也就是误删操作。在这样的情况下,我们需要判断删除的锁是否为当前线程的锁。
伪代码:

//获取redis锁
int threadId=Thread.getId();
if(redisUtil.set("前缀+UUID",threadId,30,NX)==1){
	try{
		//执行业务逻辑
		service.save();
	}finally{
		//释放锁
		/*
		if(threadId==redisUtil.get("前缀+UUID")){
			redisUtil.del("前缀+UUID");
		}
		*/
		//在这样的情况下其实if的get判断和del操作并不是原子操作,所以我们需要使用到lua脚本改进
		String lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
		redisUtil.eval(lua, Collections.singletonList("前缀+UUID"), Collections.singletonList(threadId));
	}
}

场景五
在上面的情况下,还是不足以完善,还没解决A系统处理业务逻辑超时的问题,所以接下来也引入了守护线程这样的机制。什么是守护线程?在我们使用的线程中分为两种,一种是用户线程,一种是守护线程,守护线程其实就是服务于用户线程,只有当最后一个用户线程结束了的时候,守护线程才会退出。在这个场景下,就是当快要到30s,就是在29s时候,守护线程为锁加锁时间。

上一篇: 6.824 lab1

下一篇: lab1 partⅢ