Redis分布式锁
分布式锁
概念
对某个操作上了锁后,这个锁对所有节点都有效,也就是保证所有节点互斥访问。
传统的锁只对自己的节点有效。
基于Redis实现分布式锁
原理
使用Redis的setnx命令,setnx在设置键值对的时候,先判断键值对是否存在,存在返回0,不存在返回1,根据这个原理,需要加锁的时候使用setnx设置一个lock,在解锁之前,其他人上锁lock会返回0,表示上锁失败,也就是这个锁已经存在。在解锁的时候使用del删除这个锁即可。这就达成了加锁解锁的目的。
产生的问题
如果上锁后,上锁的服务器挂掉了,其他服务器就没办法解锁,就导致死锁。
解决方案
上锁后,再设置锁的过期时间,过了期限这个锁自动失效。
使用setnx和expire命令。
setnx <key> <value>
expire <key> <seconds>
方案的优化——原子操作
需要优化原因
这涉及到原子操作,若上锁操作执行后,还没设置过期时间,此时服务器挂掉了,也会导致死锁。
所以需要上锁的同时设置过期时间。
解决方案
可以使用命令set <key><value>nx ex <time>
方案的继续优化——UUID防止误删
需要优化原因
假设A上锁后,做了操作,然后服务器卡顿了,A的锁过期后A还没反应过来。
此时B上锁做操作,还没等到B解锁,此时A反应过来进行解锁,这时候解锁解的是B的锁。
解决方案
使用uuid表示不同的操作,首先做操作的时候自己随机生成一个自己的uuid
set lock uuid nx ex 10
释放锁的时候,首先判断自己的uuid和要释放锁的uuid是否一致
代码
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("testLock")
public void testLock(){
String uuid = UUID.randomUUID().toString(); //随机生成当前操作的uuid
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,3, TimeUnit.SECONDS); //上锁,lock保存返回信息
if(lock){ //如果上锁成功
//----------------------对数据操作------------------------------------------
Object value = redisTemplate.opsForValue().get("num");
if(StringUtils.isEmpty((String)value)) return;
int num = Integer.parseInt(value+"");
redisTemplate.opsForValue().set("num",++num);
//----------------------开始判定自己的uuid与锁的uuid是否一致---------------------------------------
String lockUuid= (String) redisTemplate.opsForValue().get("lock");
if(lockUuid.equals(uuid)) //如果uuid一致,则解锁
redisTemplate.delete("lock");
}
else{ //如果上锁失败,则等一等再试着上锁
try {
Thread.sleep(100);
testLock();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
方案最终优化——LUA保证删除的原子性
需要优化原因
A上锁,做了具体操作,在正准备删除的时候(比较了uuid,且一致),A刚好过期,此时B上锁,A再删除时删除的是B的锁。
解决方案
可以使用LUA脚本保证删除操作的原子性
代码
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("testLock")
public void testLock(){
String uuid = UUID.randomUUID().toString(); //随机生成uuid
String skuId= "25";
String locKey = "lock:"+skuId;
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,3, TimeUnit.SECONDS);
if(lock){
//-----------------------------对数据操作--------------------------------------
Object value = redisTemplate.opsForValue().get("num");
if(StringUtils.isEmpty((String)value)) return;
int num = Integer.parseInt(value+"");
redisTemplate.opsForValue().set("num",++num);
//------------------------------删除操作--------------------------------------
String script="if redis.call('get',KEYS[1]) == ARGV[1] " +
"then return redis.call('del',KEYS[1]) else return 0 end"; //脚本内容
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script); //加载脚本
redisScript.setResultType(Long.class);
redisTemplate.execute(redisScript, Arrays.asList(locKey),uuid); //执行
}
else{
try {
Thread.sleep(100);
testLock();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
本文来自博客园,作者:Laplace蒜子,转载请注明原文链接:https://www.cnblogs.com/RedNoseBo/p/16255137.html

浙公网安备 33010602011771号