From 3619d8de54fadbb61c1b10d4a369d768bfeb1f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=81=E7=90=A6=E6=B6=9B?= Date: Tue, 11 Apr 2023 16:32:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=88=86=E5=B8=83=E5=BC=8F?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jero/common/aspect/LockMethodAspect.java | 57 +++++++++ .../common/aspect/annotation/RedisLock.java | 24 ++++ .../com/jero/common/util/RedisLockHelper.java | 116 ++++++++++++++++++ .../java/com/jero/common/util/RedisUtil.java | 35 ++++++ .../modules/quartz/job/SampleParamJob.java | 5 + 5 files changed, 237 insertions(+) create mode 100644 jero-boot/jero-boot-base/jero-boot-base-core/src/main/java/com/jero/common/aspect/LockMethodAspect.java create mode 100644 jero-boot/jero-boot-base/jero-boot-base-core/src/main/java/com/jero/common/aspect/annotation/RedisLock.java create mode 100644 jero-boot/jero-boot-base/jero-boot-base-core/src/main/java/com/jero/common/util/RedisLockHelper.java diff --git a/jero-boot/jero-boot-base/jero-boot-base-core/src/main/java/com/jero/common/aspect/LockMethodAspect.java b/jero-boot/jero-boot-base/jero-boot-base-core/src/main/java/com/jero/common/aspect/LockMethodAspect.java new file mode 100644 index 00000000..ee0b7be8 --- /dev/null +++ b/jero-boot/jero-boot-base/jero-boot-base-core/src/main/java/com/jero/common/aspect/LockMethodAspect.java @@ -0,0 +1,57 @@ +package com.jero.common.aspect; + +import com.jero.common.aspect.annotation.RedisLock; +import com.jero.common.exception.JeroBootException; +import com.jero.common.util.RedisLockHelper; +import com.jero.common.util.RedisUtil; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.lang.reflect.Method; +import java.util.UUID; + +/** + * 分布式部署定时任务 + * @Author lqt + */ +@Slf4j +@Aspect +@Component +public class LockMethodAspect { + @Resource + private RedisLockHelper redisLockHelper; + @Resource + private RedisUtil redisUtis; + + @Around("@annotation(com.jero.common.aspect.annotation.RedisLock)") + public Object around(ProceedingJoinPoint joinPoint) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + RedisLock redisLock = method.getAnnotation(RedisLock.class); + String value = UUID.randomUUID().toString(); + log.info("====value=====" + value + "==========="); + String key = redisLock.key(); + log.info("====key======" + key + "==========="); + try { + final boolean islock = redisLockHelper.lock(redisUtis, key, value, redisLock.expire(), redisLock.timeUnit()); + log.info("isLock : {}", islock); + if (!islock) { + log.error("获取锁失败"); + throw new JeroBootException("获取锁失败"); + } + try { + return joinPoint.proceed(); + } catch (Throwable throwable) { + throw new JeroBootException("系统异常"); + } + } finally { + log.info("释放锁"); + redisLockHelper.unlock(redisUtis, key, value); + } + } +} diff --git a/jero-boot/jero-boot-base/jero-boot-base-core/src/main/java/com/jero/common/aspect/annotation/RedisLock.java b/jero-boot/jero-boot-base/jero-boot-base-core/src/main/java/com/jero/common/aspect/annotation/RedisLock.java new file mode 100644 index 00000000..60ece522 --- /dev/null +++ b/jero-boot/jero-boot-base/jero-boot-base-core/src/main/java/com/jero/common/aspect/annotation/RedisLock.java @@ -0,0 +1,24 @@ +package com.jero.common.aspect.annotation; + +import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; +/** + * @author LQT + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface RedisLock { + /*** 业务键** @return*/ + String key(); + + /*** 锁的过期秒数,默认是5秒** @return*/ + int expire() default 5; + + /*** 尝试加锁,最多等待时间** @return*/ + long waitTime() default Long.MIN_VALUE; + + /*** 锁的超时时间单位** @return*/ + TimeUnit timeUnit() default TimeUnit.SECONDS; +} diff --git a/jero-boot/jero-boot-base/jero-boot-base-core/src/main/java/com/jero/common/util/RedisLockHelper.java b/jero-boot/jero-boot-base/jero-boot-base-core/src/main/java/com/jero/common/util/RedisLockHelper.java new file mode 100644 index 00000000..fda1c243 --- /dev/null +++ b/jero-boot/jero-boot-base/jero-boot-base-core/src/main/java/com/jero/common/util/RedisLockHelper.java @@ -0,0 +1,116 @@ +package com.jero.common.util; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +public class RedisLockHelper { + + private long sleepTime = 100; + /** + * 直接使用setnx + expire方式获取分布式锁 + * 非原子性 + * + * @param key + * @param value + * @param timeout + * @return + */ + public boolean lockSetNx(RedisUtil redisTemplate, String key, String value, int timeout) { + if ( redisTemplate.setnx(key, value)) { + return redisTemplate.expire(key, timeout); + } else { + return false; + } + } + + /** + * 使用Lua脚本,脚本中使用setnex+expire命令进行加锁操作 + * + * @param redisTemplate + * @param key + * @param uniqueId + * @param seconds + * @return + */ + public boolean lockWithLua(RedisUtil redisTemplate, String key, String uniqueId, int seconds) { + if(!Objects.isNull(seconds)){ + log.info("未使用参数:" + seconds); + } + String luaScript = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" + + "redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end"; + Object result = redisTemplate.eval(RedisScript.of(luaScript), Collections.singletonList(key), uniqueId); + //判断是否成功 + return result.equals(1L); + } + + /** + * 在Redis的2.6.12及以后中,使用 set key value [NX] [EX] 命令 + * + * @param key + * @param value + * @param timeout + * @return + */ + public boolean lock(RedisUtil redisTemplate, String key, String value, int timeout, TimeUnit timeUnit) { + long seconds = timeUnit.toSeconds(timeout); + return redisTemplate.setnx(key, value, seconds); + } + + /** + * 自定义获取锁的超时时间 + * + * @param redisTemplate + * @param key + * @param value + * @param timeout + * @param waitTime + * @param timeUnit + * @return + * @throws InterruptedException + */ + public boolean lockWithWaitTime(RedisUtil redisTemplate, String key, String value, int timeout, long waitTime, TimeUnit timeUnit) throws InterruptedException { + long seconds = timeUnit.toSeconds(timeout); + while (waitTime >= 0) { + if (redisTemplate.setnx(key, value, seconds)) { + return true; + } + waitTime -= sleepTime; + Thread.sleep(sleepTime); + } + return false; + } + /** + * 错误的解锁方法—直接删除key + * + * @param key + */ + public void unlockWithDel(RedisUtil redisTemplate, String key) { + redisTemplate.del(key); + } + + /** + * 使用Lua脚本进行解锁操纵,解锁的时候验证value值 + * + * @param redisTemplate + * @param key + * @param value + * @return + */ + public boolean unlock(RedisUtil redisTemplate, String key, String value) { + String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1] then " + + "return redis.call('del',KEYS[1]) else return 0 end"; + DefaultRedisScript redisScript =new DefaultRedisScript<> (); + redisScript.setScriptText(luaScript); + // 这个值类型要跟lua返回值类型一致才行,否则就会报 java.lang.IllegalStateException + redisScript.setResultType(Long.class); + return redisTemplate.eval(redisScript, Collections.singletonList(key), value); + } +} diff --git a/jero-boot/jero-boot-base/jero-boot-base-tools/src/main/java/com/jero/common/util/RedisUtil.java b/jero-boot/jero-boot-base/jero-boot-base-tools/src/main/java/com/jero/common/util/RedisUtil.java index ce8c468d..43719482 100644 --- a/jero-boot/jero-boot-base/jero-boot-base-tools/src/main/java/com/jero/common/util/RedisUtil.java +++ b/jero-boot/jero-boot-base/jero-boot-base-tools/src/main/java/com/jero/common/util/RedisUtil.java @@ -1,10 +1,12 @@ package com.jero.common.util; +import java.time.Duration; import java.util.*; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.*; +import org.springframework.data.redis.core.script.RedisScript; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; @@ -609,4 +611,37 @@ public class RedisUtil { e.printStackTrace(); } } + + /** + * setnx + */ + public boolean setnx(String key, String value) { + try { + return redisTemplate.opsForValue().setIfAbsent(key, value); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * setnx + */ + public boolean setnx(String key, String value,long time) { + try { + return redisTemplate.opsForValue().setIfAbsent(key,value, Duration.ofSeconds(time)); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * eval + */ + public Boolean eval(RedisScript luaScripts, List keys, String values) { + Long flag = redisTemplate.execute(luaScripts, keys, values); + //判断是不是为1 + return flag == 1L; + } } diff --git a/jero-boot/jero-boot-module-system/src/main/java/com/jero/modules/quartz/job/SampleParamJob.java b/jero-boot/jero-boot-module-system/src/main/java/com/jero/modules/quartz/job/SampleParamJob.java index 78ad9036..6c98a7ef 100644 --- a/jero-boot/jero-boot-module-system/src/main/java/com/jero/modules/quartz/job/SampleParamJob.java +++ b/jero-boot/jero-boot-module-system/src/main/java/com/jero/modules/quartz/job/SampleParamJob.java @@ -1,12 +1,16 @@ package com.jero.modules.quartz.job; +import com.jero.common.aspect.annotation.RedisLock; import com.jero.common.util.DateUtils; +import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import lombok.extern.slf4j.Slf4j; +import java.util.concurrent.TimeUnit; + /** * 示例带参定时任务 * @@ -25,6 +29,7 @@ public class SampleParamJob implements Job { } @Override + @RedisLock(key = "lock----TestJob",expire=60,timeUnit= TimeUnit.SECONDS) public void execute(JobExecutionContext jobExecutionContext) { log.info(" Job Execution key:"+jobExecutionContext.getJobDetail().getKey()); log.info("welcome " + this.parameter + " Jero-Boot 带参数定时任务 SampleParamJob ! 时间:" + DateUtils.now());