reids实现分布式锁
由于(业务上锁和解锁异步),导致我需要使用redis 自定义锁.
如果上锁和解锁是同步,推荐使用Redisson组件,解决了锁的续时,服务宕机后的死锁问题,锁的可冲入,
这里有两个问题:
- 为什么要设置锁的过期时间?
- 回答:因为防止出现死锁。
- 锁的过期时间设置多久合适?
- 回答: 由于自己实现锁续期很麻烦,且场景bug很多,所以redisson有一个看门狗watchDog机制,源码默认是上锁是30s,当每次执行到 过期时间三分之一 (10s)则给锁进行续命。
- 解锁过程中服务器宕机导致没有解锁成功?
- 回答:正常异常情况finally可以保证解锁,但是服务器宕机造成死锁,redisson有一个watchDog机制当服务器宕机后watchDog则也随之死亡。
这不是重点,科普一下,只是想告诉大家,redisson确实很强大,能用则用,不要自己搞。
过程:
就是这么简单,我们只需要通过它的api中的lock和unlock即可完成分布式锁,他帮我们考虑了很多细节:
答案:核心内容
-
1:redisson所有指令都通过lua脚本执行,redis支持lua脚本原子性执行
-
2:redisson设置一个key的默认过期时间为30s,如果某个客户端持有一个锁超过了30s怎么办? redisson中有一个watchdog的概念,翻译过来就是看门狗,它会在你获取锁之后,每隔10秒帮你把key的超时时间设为30s 这样的话,就算一直持有锁也不会出现key过期了,其他线程获取到锁的问题了。
-
3:redisson的“看门狗”逻辑保证了没有死锁发生。 (如果机器宕机了,看门狗也就没了。此时就不会延长key的过期时间,到了30s之后就会自动过期了,其他线程可以获取到锁)
ap方法如下
开始正题 为什么要自定义锁,因为业务上异步解锁,要考虑地方是异常解锁情况
自定义Redis 实现分布式锁 需求实现 同一时间一条词条只允许一个管理编辑,其他管理员访问改词条则访问不成功
1.上锁 : 设置过期时间为了保证解决死锁问题,保证上锁和过期时间的原子性,此外还要考虑浏览器突然关闭没有正常执行解锁操作。由于设置了过期时间,但是用户没有在指定时间内提交业务,所以锁的续期问题需要解决。
2.锁续期:锁的续期 ,当停留在加锁页面,前端定时轮询调用锁的续期接口,保证用户提交正常。设置设置最长停留时间,超过则不调用续时解决锁无限制一直续期问题。
3.解锁:保证上锁的用户和解锁的用户是一个人同一次操作,如何保证,当上锁时候会自定义一个会话id(uuid),上锁时将会话id存放在value,解锁的时候需要带着会话id过来,判断会话id是否是一直一致,解决了多用户误解锁问题。
补充:有一些场景 解锁为什么要给先给锁续期 ,保证方法执行完时间够用,为了解决查到锁的一瞬锁失效了,B线程抢占到锁了,查到锁之后会执行删除锁。此时删除的锁是B的,做好解决办法就是在解锁 和提交更新sql 之前锁是百分之百存在的不会过期 就解决了。
maven
<!-- redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
package com.excel.batch.redislock.controller;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
/**
* @Author: 宋忠瑾
* @Description: redis实现分布式锁,因为业务拆分的
* @Date: Create in 17:33 2021/9/7
*/
@RestController
@Slf4j
public class RedisDistributedLock {
/**
* redis上锁到期时间
*/
public static final int EXPIRATION_TIME = 10;
/**
* redis上锁续期时间
*/
public static final int RENEWAL_TIME = 30;
/**
* redis操作模板
*/
@Autowired
private RedisTemplate redisTemplate;
/**
* redis上锁判断 是否存在
*
* @return 成功但会uuid 失败返回null
* @Description 上锁
* @Method 上锁判断 是否存在
* @Author 宋忠瑾
* @Date 2021/9/7 17:35
*/
@GetMapping("tryLock")
public String testLocktime() {
//每一次访问生成一个会话id
String uuid = UUID.fastUUID().toString();
//上锁
Boolean lock = redisTemplate.opsForValue()
.setIfAbsent("bravo1988_distributed_lock", uuid, EXPIRATION_TIME, TimeUnit.SECONDS);
if (lock) {
log.info("上锁");
return uuid;
} else {
log.error("上锁失败");
return null;
}
}
/**
* redis锁续期
*
* @param key 键值
* @return Boolean
* @Description 锁续期
* @Method 锁续期
* @Author 宋忠瑾
* @Date 2021/9/7 17:36
*/
@GetMapping("tryLockRenew")
public Boolean testLockSuccess(@RequestParam String key) {
if (redisTemplate.hasKey(key)) {
redisTemplate.expire(key, RENEWAL_TIME, TimeUnit.SECONDS);
//上锁
log.info("续期成功:{}", true);
return true;
} else {
//上锁失败
log.info("续期失败: 当前锁不存在");
return false;
}
}
/**
* 解锁
*
* @param key 键值
* @param value 存放的uuid
* @return Boolean
* @Description 判断用户生成的uuid
* @Method
* @Author 宋忠瑾
* @Date 2021/9/7 17:37
*/
@GetMapping("unLock")
public Boolean testUnLock(@RequestParam String key, @RequestParam String value) {
//先给锁续期 保证方法执行完时间够用,为了解决查到锁的一瞬锁失效了,B线程抢占到锁了,查到锁之后会执行删除锁
redisTemplate.expire(key, RENEWAL_TIME, TimeUnit.SECONDS);
//获取当前redis key的value
Object redisValue = redisTemplate.opsForValue().get(key);
if (ObjectUtil.isNotNull(redisValue) && redisValue.toString().equals(value)) {
//解锁 判断是否是同一个人
redisTemplate.delete(key);
log.info("解锁成功");
return true;
} else {
log.info("当前用户uuid不一致或当前不存在锁,解锁失败");
return false;
}
}
}