当前位置:网站首页>使用自定义注解实现Redis分布式锁
使用自定义注解实现Redis分布式锁
2022-06-29 14:42:00 【一恍过去】
1、RedisLockServer
定义Redis上锁、解锁实现类
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class RedisLockServer {
@Resource
private StringRedisTemplate stringRedisTemplate;
public Boolean setLock(String lockKey, String value, long time) {
if(time<=0){
// 不设置过期时间
return stringRedisTemplate.opsForValue().setIfAbsent(lockKey, value);
}
return stringRedisTemplate.opsForValue().setIfAbsent(lockKey, value, time, TimeUnit.MILLISECONDS);
}
public void deleteLock(String lockKey, String value) {
List<String> lockKeys = Collections.singletonList(lockKey);
String lua = "if redis.call('get', KEYS[1]) == ARGV[1] then redis.call('del', KEYS[1]) return 1 else return 0 end";
RedisScript<Long> luaScript = RedisScript.of(lua, Long.class);
// 删除锁
stringRedisTemplate.execute(luaScript, lockKeys, value);
}
}
2、RedisLock
定义“自定义注解”,用于切面实现分布式锁
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {
/** * 上锁的方法中,key所在参数的位置,索引从0开始 * @return */
int keyNum();
/** * 上锁时长,默认设置时间 * @return */
long lockTime() default 0;
/** * 尝试时间,设置时间内通过自旋一致尝试获取锁,默认0ms * @return */
long tryTime() default 0;
}
字段说明:
- keyNum:
keyNum表示在被@RedisLock修饰的方法上,第几个参数表示上锁的key,默认第一个参数,也可以指定key的位置,并且参数的名称必须要为keyName
// key为第一个参数值
void test(String keyName){
}
// key为第三个参数值
@RedisLock(keyNum=2)
void test(int a,int b,String keyName){
}
- lockTime:
lockTime表示上锁过期时间,默认为0,表示不进行上锁处理;
public Boolean setLock(String lockKey, String value, long time) {
if(time<=0){
// 不设置过期时间
return stringRedisTemplate.opsForValue().setIfAbsent(lockKey, value);
}
return stringRedisTemplate.opsForValue().setIfAbsent(lockKey, value, time, TimeUnit.MILLISECONDS);
}
- tryTime:
tryTime表示尝试获取锁的时间,当设置的时间内,通过自旋的方式一直获取锁
3、RedisLockAspect
切面类定义,代码如下:
import lhz.lx.config.RedisLockServer;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.UUID;
@Aspect
@Component
@Slf4j
public class RedisLockAspect {
@Resource
private RedisLockServer redisLockServer;
private static final ThreadLocal<String> VALUE_THREAD = new ThreadLocal<>();
private static final ThreadLocal<String> KEY_THREAD = new ThreadLocal<>();
private static final ThreadLocal<Boolean> LOCK_THREAD = new ThreadLocal<>();
@Pointcut("@annotation(lhz.lx.aspect.RedisLock)")
public void lockPoint() {
}
/** * 环绕通知,调用目标方法 */
@Around("lockPoint()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 记录方法执行开始时间
long startTime = System.currentTimeMillis();
Object[] args = proceedingJoinPoint.getArgs();
if(args.length<=0){
throw new RuntimeException("keyName不存在!");
}
String[] argNames = ((CodeSignature) proceedingJoinPoint.getSignature()).getParameterNames();
Signature signature = proceedingJoinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
RedisLock lock = method.getAnnotation(RedisLock.class);
int keyNum = lock.keyNum();
if(!"keyName".equals(argNames[keyNum])){
throw new RuntimeException("keyName不存在!");
}
String key = args[keyNum].toString();
long lockTime = lock.lockTime();
long tryTime = lock.tryTime();
String value = UUID.randomUUID().toString();
VALUE_THREAD.set(value);
KEY_THREAD.set(key);
log.info("分布式锁上锁,key:{},value:{},lockTime:{}",key,value,lockTime);
Boolean setLock = redisLockServer.setLock(key, value, lockTime);
while (!setLock){
// 重试
setLock = redisLockServer.setLock(key, value, lockTime);
if(System.currentTimeMillis()-startTime>tryTime) {
LOCK_THREAD.set(false);
log.error("上锁失败");
throw new RuntimeException("上锁失败!");
}
}
log.info("分布式锁上锁成功,key:{},value:{},lockTime:{}",key,value,lockTime);
LOCK_THREAD.set(true);
// 调用目标方法
return proceedingJoinPoint.proceed();
}
/** * 处理完请求后执行 * * @param joinPoint 切点 */
@AfterReturning(value = "lockPoint()", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
handleData();
}
/** * 拦截异常操作 * * @param joinPoint 切点 * @param e 异常 */
@AfterThrowing(value = "lockPoint()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
handleData();
}
private void handleData() {
try {
String value = VALUE_THREAD.get();
String key = KEY_THREAD.get();
if(LOCK_THREAD.get()) {
log.info("分布式锁解锁,key:{},value:{}", key, value);
redisLockServer.deleteLock(key, value);
}
} catch (Exception exception) {
exception.printStackTrace();
} finally {
VALUE_THREAD.remove();
KEY_THREAD.remove();
LOCK_THREAD.remove();
}
}
}
4、开启自动代理
因为说明AOP无法拦截类内部的方法之间的调用,需要对启动类加上@EnableAspectJAutoProxy配置,代码如下:
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class RedisDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RedisDemoApplication.class, args);
}
}
比如方法直接的调用,A方法调用B方法,使用方式如下:
public class RedisServiceImpl implements RedisService{
public void A() {
// 方法类调用
RedisServiceImpl service = (RedisServiceImpl) AopContext.currentProxy();
service.B();
}
@RedisLock
public void B() {
}
}
5、使用
注意: 在调用使用分布式锁的方法时,需要进try…catch…,并且在catch中处理上锁失败的情况;
TestController:
@RestController
@RequestMapping
@Slf4j
public class TestController {
@Resource
private RedisService redisService;
@GetMapping(value = "/test")
public String test() {
redisService.test();
return "success";
}
@GetMapping(value = "/test2")
public String test2() {
String key = UUID.randomUUID().toString();
redisService.test2(key);
return "success";
}
}
RedisService :
public interface RedisService {
/** * 方法内部调用使用锁 */
void test();
/** * 方法直接调用使用锁 * @param keyName */
void test2(String keyName);
}
RedisServiceImpl:
@Service
@Slf4j
public class RedisServiceImpl implements RedisService{
/** * 方法内部调用使用锁 */
@Override
public void test() {
log.info("方法内部调用使用锁");
// 调用内部方法
RedisServiceImpl service = (RedisServiceImpl) AopContext.currentProxy();
// 在调用使用分布式锁的方法时,需要进try...catch...,并且在catch中处理上锁失败的情况
try {
service.testLock("test11");
}catch (Exception e){
e.printStackTrace();
}
}
/** * 方法直接调用使用锁 */
@Override
@RedisLock(keyNum = 0)
public void test2(String keyName) {
log.info("方法直接调用使用锁");
}
/** * 被上锁的方法中,一定要存在一个叫"keyName"的参数 * @param keyName */
@RedisLock
public void testLock(String keyName) throws InterruptedException {
log.info(keyName);
}
}
6、测试
测试一:
正常上锁情况
测试二:设置tryTime,并且tryTime小于lockTime;当这样配置时,在第一个线程没有结束时,第二个线程,超过tryTime就会出现上锁失败;
修改代码如下:
@RedisLock(keyNum = 0,lockTime = 3000,tryTime = 2000)
public void testLock(String keyName) throws InterruptedException {
log.info(keyName);
TimeUnit.SECONDS.sleep(5);
}
快速请求两次接口,截图如下:
通过截图可以看到,在第一个线程上锁后,过了2000ms出现了上锁失败的提示;
测试三:
设置tryTime,并且tryTime大于lockTime;当这样配置时,不会出现上锁失败,并且第二个线程会一直等到第一个线程结束;
修改代码如下:
@RedisLock(keyNum = 0,lockTime = 3000,tryTime = 4000)
public void testLock(String keyName) throws InterruptedException {
log.info(keyName);
TimeUnit.SECONDS.sleep(5);
}
快速请求两次接口,截图如下:
通过截图可以看到,在第一个线程上锁后,超过了3000ms后,第二个线程开始上锁成功
边栏推荐
- JS 会有变量提升和函数提升
- Jet hydrogen technology rushes to the scientific innovation board: SAIC Group is the major shareholder to raise 1.06 billion yuan
- Unity C# 基础复习27——委托示例(P448)
- 知乎热议:一个程序员的水平能差到什么程度?
- ModStartBlog 现代化个人博客系统 v5.2.0 主题开发增强,新增联系方式
- 云上第一课 | 建个小破站有多简单?云计算老司机带你一小时搞定
- 重磅!2022最新SCI影响因子发布,三大名刊NCS及国内期刊TOP10排名有变化 (内附2022年最新影响因子)
- idea输出台输出中文乱码问题
- Using polymorphism to realize simple calculator
- 信息学奥赛一本通1000:入门测试题目
猜你喜欢

Jet hydrogen technology rushes to the scientific innovation board: SAIC Group is the major shareholder to raise 1.06 billion yuan

卫龙更新招股书:年营收48亿 创始人刘卫平家族色彩浓厚

驱动器实际运用案例

论文学习——考虑场次降雨年际变化特征的年径流总量控制率准确核算

Mysql database - general syntax DDL DML DQL DCL

You need to know about project procurement management

June 27 talk SofiE

【牛客网刷题系列 之 Verilog快速入门】~ 移位运算与乘法

【Try to Hack】vulnhub DC2

Chapter 12 other applications of canvas
随机推荐
Source code of campus secondary market
中国三氧化二砷行业研究与未来预测报告(2022版)
Class template case - array class encapsulation
阿里云体验有奖:使用PolarDB-X与Flink搭建实时数据大屏
如果我在佛山,到哪里开户比较好?究竟网上开户是否安全么?
精品商城拼团秒杀优惠折扣全功能完美双端自适应对接个人免签网站源码
模电 2个NPN管组成的恒流源电路分析
携程季报图解:净营收41亿 与疫情前相比已被“腰斩”
synchronized 与多线程的哪些关系
微信公众号—菜单
URL encoding in Delphi7
信息学奥赛一本通1194:移动路线
揭秘百度智能测试在测试自动执行领域实践
威高血液净化冲刺香港:年营收29亿 净利降12.7%
中国二氧化硫脲行业研究与发展前景研究报告(2022版)
Digital IC code -- traffic lights
Netease strict selection offline data warehouse quality construction practice
If I am in Foshan, where can I open an account? Is it safe to open an account online?
两个字的名字如何变成有空格的3个字符的名字
自动注入@Resource和@Autowired注解的区别: