探索——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",1,30,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Ⅲ