目录
1. 如何实现分布式锁
Redis 天生就可以作为一个分布式系统来使用,所以它实现的锁都是分布式锁。
Redis 可以通过 setnx(set if not exists)命令实现分布式锁~
- setnx mylock true - 加锁
- del mylock - 释放锁
通过执行结果是否为 1 可以判断是否成功获取到锁~
2. Redis 分布式锁存在什么问题
Redis 分布式锁存在两个问题:
1.死锁问题:未设置过期时间,锁忘记释放,加锁后还没来的及释放锁就宕机了都会导致死锁问题.
2. 锁误删问题:设置了超时时间,但是线程执行超过超时时间后锁误删问题.
2.1 解决死锁问题
MySQL 中解决死锁问题是通过设置超时时间,Redis 也是如此,但是问题来了,第一步先加锁,然后再设置超时时间,那么就不满足原子性了,那么怎么办 ?
官方在 Redis 2.6.12 版本之后,新增了一个功能,我们可以使用一条命令既执行加锁操作,又设置超时时间:setnx 和 expire
- 第一条命令成功加锁,并设置 30 s 过期时间
- 第二条命令跟在第一条命令后,还没有超过 30s,所以获取失败
2.2 解决锁误删问题
锁误删问题是解决死锁问题带来的问题,如何理解 ?
两个线程同时获取锁,线程 1 获取到锁了,锁的过期时间为 10s,而线程 1 需要执行 12s,第 10s 的时候,线程 2 获取到锁了(并发执行),线程 2 在执行过程中,2s 后,线程 1 执行完,执行力释放锁的动作,导致把线程 2 的锁给释放了。
既然知道了什么是锁误删问题,那么如何解决 ?
答:可以通过添加锁标识来解决.
前面我们使用 set 命令的时候,只使用到了 key,那么可以给 value 设置一个标识,表示当前锁归属于那个线程,例如 value=thread1,value=thread2.....
但是这样解决依然存在问题,因为新增锁标识之后,线程在释放锁的时候,需要执行两步操作了:
- 判断锁是否属于自己
- 如果是,就删除锁
这样就不能保证原子性了,那该怎么办?
【解决方案】
- 使用 lua 脚本来解决 (Redis 本身就能保证 lua 脚本里面所有命令都是原子性操作)
- 使用 Redisson 框架来解决(主流)
那么 Redisson 如何实现分布式锁呢 ?
【代码示例】
1.引入 Redisson 依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.2</version>
</dependency>
2.创建 RedissonClient 对象
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
// 如果有密码需要设置密码
return Redisson.create(config);
}
}
3.调用分布式锁
@RestController
public class LockController {
@Resource
private RedissonClient redissonClient;
@RequestMapping("/lock")
public String lockResource() throws InterruptedException {
String lockKey = "myLock";
// 获取锁
RLock lock = redissonClient.getLock(lockKey);
try {
// 超时时间 10s, [tryLock 获取成功才需要释放锁,获取失败不需要释放锁]
boolean isLocked = lock.tryLock(20, TimeUnit.SECONDS);
if(isLocked) {
// 成功获取到锁
try {
TimeUnit.SECONDS.sleep(5);
return "成功获取到锁,并执行业务代码";
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
} else {
// 获取锁失败
return "获取锁失败";
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return "获取锁成功";
}
}
启动项目,使用 8080 端口访问接口: