大家好,我是程序员阿药。今天和大家分享的是一个面试题,Redis如何实现分布式锁。话不多说,发车!
获取Redis分布式锁的流程
多个客户端同时竞争锁,当其中一个客户端获得锁后开始执行业务逻辑,其他未得到锁的客户端等待重试,待拥有锁的客户端执行完业务逻辑后释放锁,然后其他等待重试的客户端中的一个即可获得。如图:
代码实现(一)
public void testLock(){
//1获取锁,setnx
Boolean lock = redisTemplate.opsForValue()
.setIfAbsent("lock", "value");
//2获取锁成功、查询num的值(执行业务逻辑)
if(lock){
Object value = redisTemplate.opsForValue().get("num");
//2.1判断num为空return
if(null == value){
return;
}
//2.2有值就转成成int
int num = Integer.parseInt(value+"");
//2.3把redis的num加1
redisTemplate.opsForValue().set("num", ++num);
//2.4释放锁,del
redisTemplate.delete("lock");
}else{
//3获取锁失败、每隔0.1秒再获取
try {
Thread.sleep(100);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
采用代码实现(一)带来的问题和解决办法
问题:假设某个客户端刚好获得锁开始执行业务逻辑,但是执行时出现异常导致锁无法释放。
解决:给锁设置过期时间,自动释放锁。设置过期时间的方式如下。
-
通过expire设置,但是缺乏原子性,如果在setnx和expire之间出现异常,锁也无法释放。
-
在set时设置过期时间,推荐使用这种方式。
代码实现(二)
public void testLock(){
//1获取锁,setnx
Boolean lock = redisTemplate.opsForValue()
.setIfAbsent("lock", "value", 3, TimeUnit.SECONDS);
//2获取锁成功、查询num的值(执行业务逻辑)
if(lock){
......
//2.4释放锁,del
redisTemplate.delete("lock");
}else{
//3获取锁失败、每隔0.1秒再获取
......
}
}
采用代码实现(二)带来的问题和解决办法
问题:如果客户端1的业务逻辑没有执行完,但是锁已经过期自动释放;然后客户端2获取到锁,当客户端1执行释放锁时,此时释放的是客户端2的锁(因为锁一直都是同一把锁)。
解决:setnx设置锁时,设置一个指定的唯一值uuid,释放锁之前获取这个值,判断是不是自己的锁(客户端1和客户端2中的uuid是不同的)。
代码实现(三)
public void testLock(){
String uuid = UUID.randomUUID().toString();
//1获取锁,setnx
Boolean lock = redisTemplate.opsForValue()
.setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
//2获取锁成功、查询num的值(执行业务逻辑)
if(lock){
......
//2.4释放锁,del
//判断比较uuid值是否一样
String lockUuid = (String) redisTemplate.opsForValue().get("lock");
if (null == lockUuid) {
return;
}
if (lockUuid.equals(uuid)) {
//是自己的锁,再释放
redisTemplate.delete("lock");
}
}else{
//3获取锁失败、每隔0.1秒再获取
......
}
}
采用代码实现(三)带来的问题和解决办法
问题:如果客户端1执行完对uuid的判断后,刚刚要删除锁但是这个时候锁自动释放;然后客户端2获得了锁,此时客户端1将会删除客户端2的锁(无法保证删除的原子性)。
解决:通过LUA脚本保证删除的原子性。
代码实现(四)
public void testLockLua() {
String uuid = UUID.randomUUID().toString();
// 1.获取锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
// 2.获取锁成功、查询num的值(执行业务逻辑)
if (lock) {
......
// 2.4使用lua脚本来释放锁,del
// 定义lua脚本
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// 使用redis执行lua执行
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
// 设置一下返回值类型 为Long
// 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
// 那么返回字符串与0 会有发生错误。
redisScript.setResultType(Long.class);
// 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
} else {
// 3.获取锁失败、每隔0.1秒再获取
......
}
}
到此为止,Redis实现分布式锁结束。如果哪里写的不好希望大家批评指正。如果有帮助的话,希望各位小伙伴可以点赞收藏支持一下,我们下篇再见。