feat: 基于redis 的分布式锁的实现
diff --git a/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/AbstractDistributedLockHandler.java b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/AbstractDistributedLockHandler.java
new file mode 100644
index 0000000..8cbca67
--- /dev/null
+++ b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/AbstractDistributedLockHandler.java
@@ -0,0 +1,30 @@
+package com.supwisdom.institute.backend.common.core.distributedlock;
+
+public abstract class AbstractDistributedLockHandler implements DistributedLockHandler {
+
+ @Override
+ public boolean lock(String key) {
+ return lock(key, TIMEOUT_MILLIS, RETRY_TIMES, SLEEP_MILLIS);
+ }
+
+ @Override
+ public boolean lock(String key, int retryTimes) {
+ return lock(key, TIMEOUT_MILLIS, retryTimes, SLEEP_MILLIS);
+ }
+
+ @Override
+ public boolean lock(String key, int retryTimes, long sleepMillis) {
+ return lock(key, TIMEOUT_MILLIS, retryTimes, sleepMillis);
+ }
+
+ @Override
+ public boolean lock(String key, long expire) {
+ return lock(key, expire, RETRY_TIMES, SLEEP_MILLIS);
+ }
+
+ @Override
+ public boolean lock(String key, long expire, int retryTimes) {
+ return lock(key, expire, retryTimes, SLEEP_MILLIS);
+ }
+
+}
diff --git a/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DefaultDistributedLockHandler.java b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DefaultDistributedLockHandler.java
new file mode 100644
index 0000000..950a510
--- /dev/null
+++ b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DefaultDistributedLockHandler.java
@@ -0,0 +1,17 @@
+package com.supwisdom.institute.backend.common.core.distributedlock;
+
+public class DefaultDistributedLockHandler extends AbstractDistributedLockHandler {
+
+ private ThreadLocal<String> lockFlag = new ThreadLocal<String>();
+
+ @Override
+ public boolean lock(String key, long expire, int retryTimes, long sleepMillis) {
+ return true;
+ }
+
+ @Override
+ public boolean releaseLock(String key) {
+ return true;
+ }
+
+}
diff --git a/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DistributedLockAutoConfiguration.java b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DistributedLockAutoConfiguration.java
new file mode 100644
index 0000000..68df599
--- /dev/null
+++ b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DistributedLockAutoConfiguration.java
@@ -0,0 +1,21 @@
+package com.supwisdom.institute.backend.common.core.distributedlock;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Slf4j
+@Configuration
+public class DistributedLockAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean(DistributedLockHandler.class)
+ public DistributedLockHandler defaultDistributedLockHandler() {
+ DistributedLockHandler defaultDistributedLockHandler = new DefaultDistributedLockHandler();
+ log.debug("DistributedLockAutoConfiguration.defaultDistributedLockHandler is {}", defaultDistributedLockHandler);
+
+ return defaultDistributedLockHandler;
+ }
+}
diff --git a/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DistributedLockHandler.java b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DistributedLockHandler.java
new file mode 100644
index 0000000..e989da9
--- /dev/null
+++ b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DistributedLockHandler.java
@@ -0,0 +1,83 @@
+package com.supwisdom.institute.backend.common.core.distributedlock;
+
+public interface DistributedLockHandler {
+ public static final long TIMEOUT_MILLIS = 30000;
+
+ public static final int RETRY_TIMES = Integer.MIN_VALUE;
+
+ public static final long SLEEP_MILLIS = 500;
+
+ /**
+ * 获取锁<br>
+ * 超时:30000 ms (30 s) {@link DistributedLockHandler.TIMEOUT_MILLIS}<br>
+ * 重试次数:不重试 {@link DistributedLockHandler.RETRY_TIMES}<br>
+ * 重试等待:500 ms {@link DistributedLockHandler.SLEEP_MILLIS}<br>
+ * <br>
+ * @param key
+ * @return
+ */
+ public boolean lock(String key);
+
+ /**
+ * 获取锁<br>
+ * 超时:30000 ms (30 s) {@link DistributedLockHandler.TIMEOUT_MILLIS}<br>
+ * 重试等待:500 ms {@link DistributedLockHandler.SLEEP_MILLIS}<br>
+ * <br>
+ * @param key
+ * @param retryTimes 重试次数
+ * @return
+ */
+ public boolean lock(String key, int retryTimes);
+
+ /**
+ * 获取锁<br>
+ * 超时:30000 ms (30 s) {@link DistributedLockHandler.TIMEOUT_MILLIS}<br>
+ * <br>
+ * @param key
+ * @param retryTimes 重试次数
+ * @param sleepMillis 重试等待 单位 ms
+ * @return
+ */
+ public boolean lock(String key, int retryTimes, long sleepMillis);
+
+ /**
+ * 获取锁<br>
+ * 重试次数:不重试 {@link DistributedLockHandler.RETRY_TIMES}<br>
+ * 重试等待:500 ms {@link DistributedLockHandler.SLEEP_MILLIS}<br>
+ * <br>
+ * @param key
+ * @param expire 超时 单位 ms
+ * @return
+ */
+ public boolean lock(String key, long expire);
+
+ /**
+ * 获取锁<br>
+ * 重试等待:500 ms {@link DistributedLockHandler.SLEEP_MILLIS}<br>
+ * <br>
+ * @param key
+ * @param retryTimes 重试次数
+ * @param expire 超时 单位 ms
+ * @return
+ */
+ public boolean lock(String key, long expire, int retryTimes);
+
+ /**
+ * 获取锁<br>
+ * <br>
+ * @param key
+ * @param expire 超时 单位 ms
+ * @param retryTimes 重试次数
+ * @param sleepMillis 重试等待 单位 ms
+ * @return
+ */
+ public boolean lock(String key, long expire, int retryTimes, long sleepMillis);
+
+ /**
+ * 释放锁<br>
+ * <br>
+ * @param key
+ * @return
+ */
+ public boolean releaseLock(String key);
+}
diff --git a/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/EnableDistributedLock.java b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/EnableDistributedLock.java
new file mode 100644
index 0000000..89053c9
--- /dev/null
+++ b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/EnableDistributedLock.java
@@ -0,0 +1,17 @@
+package com.supwisdom.institute.backend.common.core.distributedlock;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Import;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Import({DistributedLockAutoConfiguration.class})
+public @interface EnableDistributedLock {
+
+}
diff --git a/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/aop/DistributedLock.java b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/aop/DistributedLock.java
new file mode 100644
index 0000000..52c2485
--- /dev/null
+++ b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/aop/DistributedLock.java
@@ -0,0 +1,36 @@
+package com.supwisdom.institute.backend.common.core.distributedlock.aop;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface DistributedLock {
+
+ /** 锁的资源,redis的key */
+ String value() default "default";
+
+ /** 持锁时间,单位毫秒 */
+ long keepMills() default 30000;
+
+ /** 当获取失败时候动作 */
+ LockFailAction action() default LockFailAction.CONTINUE;
+
+ public enum LockFailAction {
+ /** 放弃 */
+ GIVEUP,
+ /** 继续 */
+ CONTINUE;
+ }
+
+ /** 重试的间隔时间,设置GIVEUP忽略此项 */
+ long sleepMills() default 200;
+
+ /** 重试次数 */
+ int retryTimes() default 5;
+
+}
diff --git a/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/aop/DistributedLockAspectConfiguration.java b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/aop/DistributedLockAspectConfiguration.java
new file mode 100644
index 0000000..fb67688
--- /dev/null
+++ b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/aop/DistributedLockAspectConfiguration.java
@@ -0,0 +1,67 @@
+package com.supwisdom.institute.backend.common.core.distributedlock.aop;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.StringUtils;
+
+import com.supwisdom.institute.backend.common.core.distributedlock.DistributedLockAutoConfiguration;
+import com.supwisdom.institute.backend.common.core.distributedlock.DistributedLockHandler;
+import com.supwisdom.institute.backend.common.core.distributedlock.aop.DistributedLock.LockFailAction;
+
+@Aspect
+@Configuration
+@ConditionalOnClass({ProceedingJoinPoint.class, DistributedLockHandler.class})
+@AutoConfigureAfter(DistributedLockAutoConfiguration.class)
+public class DistributedLockAspectConfiguration {
+
+ private final Logger logger = LoggerFactory.getLogger(DistributedLockAspectConfiguration.class);
+
+ @Autowired
+ private DistributedLockHandler distributedLockHandler;
+
+ @Pointcut("@annotation(com.supwisdom.institute.backend.common.core.distributedlock.aop.DistributedLock)")
+ private void lockPoint(){
+
+ }
+
+ @Around("lockPoint()")
+ public Object around(ProceedingJoinPoint pjp) throws Throwable{
+ Method method = ((MethodSignature) pjp.getSignature()).getMethod();
+ DistributedLock distributedLockAnnotation = method.getAnnotation(DistributedLock.class);
+ String key = distributedLockAnnotation.value();
+ if(StringUtils.isEmpty(key)){
+ Object[] args = pjp.getArgs();
+ key = Arrays.toString(args);
+ }
+ int retryTimes = distributedLockAnnotation.action().equals(LockFailAction.CONTINUE) ? distributedLockAnnotation.retryTimes() : 0;
+ boolean lock = distributedLockHandler.lock(key, distributedLockAnnotation.keepMills(), retryTimes, distributedLockAnnotation.sleepMills());
+ if(!lock) {
+ logger.debug("get lock failed : " + key);
+ return null;
+ }
+
+ //得到锁,执行方法,释放锁
+ logger.debug("get lock success : " + key);
+ try {
+ return pjp.proceed();
+ } catch (Exception e) {
+ logger.error("execute locked method occured an exception", e);
+ } finally {
+ boolean releaseResult = distributedLockHandler.releaseLock(key);
+ logger.debug("release lock : " + key + (releaseResult ? " success" : " failed"));
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/common/core/src/main/resources/META-INF/spring.factories b/common/core/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..b94673d
--- /dev/null
+++ b/common/core/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,4 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+ com.supwisdom.institute.backend.common.core.distributedlock.DistributedLockAutoConfiguration,\
+ com.supwisdom.institute.backend.common.core.distributedlock.aop.DistributedLockAspectConfiguration
+
diff --git a/common/framework/pom.xml b/common/framework/pom.xml
index 18d0ac5..1cf7bc7 100644
--- a/common/framework/pom.xml
+++ b/common/framework/pom.xml
@@ -47,11 +47,35 @@
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-aop</artifactId>
+ <optional>true</optional>
+ </dependency>
+
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<optional>true</optional>
</dependency>
+
+
+ <!-- <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-data-redis</artifactId>
+ <optional>true</optional>
+ </dependency> -->
+ <dependency>
+ <groupId>org.springframework.data</groupId>
+ <artifactId>spring-data-redis</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>redis.clients</groupId>
+ <artifactId>jedis</artifactId>
+ <optional>true</optional>
+ </dependency>
<dependency>
diff --git a/common/framework/src/main/java/com/supwisdom/institute/backend/common/framework/distributedlock/RedisDistributedLockAutoConfiguration.java b/common/framework/src/main/java/com/supwisdom/institute/backend/common/framework/distributedlock/RedisDistributedLockAutoConfiguration.java
new file mode 100644
index 0000000..edf8505
--- /dev/null
+++ b/common/framework/src/main/java/com/supwisdom/institute/backend/common/framework/distributedlock/RedisDistributedLockAutoConfiguration.java
@@ -0,0 +1,33 @@
+package com.supwisdom.institute.backend.common.framework.distributedlock;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import redis.clients.jedis.Jedis;
+
+import com.supwisdom.institute.backend.common.core.distributedlock.DistributedLockAutoConfiguration;
+import com.supwisdom.institute.backend.common.core.distributedlock.DistributedLockHandler;
+
+@Slf4j
+@Configuration
+@ConditionalOnClass({ Jedis.class })
+@AutoConfigureAfter(RedisAutoConfiguration.class)
+@AutoConfigureBefore(DistributedLockAutoConfiguration.class)
+public class RedisDistributedLockAutoConfiguration {
+
+ @Bean
+ public DistributedLockHandler redisDistributedLockHandler(RedisTemplate<Object, Object> redisTemplate) {
+ DistributedLockHandler redisDistributedLockHandler = new RedisDistributedLockHandler(redisTemplate);
+ log.debug("RedisDistributedLockAutoConfiguration.redisDistributedLockHandler is {}", redisDistributedLockHandler);
+
+ return redisDistributedLockHandler;
+ }
+
+}
diff --git a/common/framework/src/main/java/com/supwisdom/institute/backend/common/framework/distributedlock/RedisDistributedLockHandler.java b/common/framework/src/main/java/com/supwisdom/institute/backend/common/framework/distributedlock/RedisDistributedLockHandler.java
new file mode 100644
index 0000000..59afa38
--- /dev/null
+++ b/common/framework/src/main/java/com/supwisdom/institute/backend/common/framework/distributedlock/RedisDistributedLockHandler.java
@@ -0,0 +1,117 @@
+package com.supwisdom.institute.backend.common.framework.distributedlock;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.dao.DataAccessException;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.util.StringUtils;
+
+import com.supwisdom.institute.backend.common.core.distributedlock.AbstractDistributedLockHandler;
+
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.JedisCommands;
+
+public class RedisDistributedLockHandler extends AbstractDistributedLockHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(RedisDistributedLockHandler.class);
+
+ private RedisTemplate<Object, Object> redisTemplate;
+
+ private ThreadLocal<String> lockFlag = new ThreadLocal<String>();
+
+ public static final String UNLOCK_LUA;
+
+ static {
+ StringBuilder sb = new StringBuilder();
+ sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
+ sb.append("then ");
+ sb.append(" return redis.call(\"del\",KEYS[1]) ");
+ sb.append("else ");
+ sb.append(" return 0 ");
+ sb.append("end ");
+ UNLOCK_LUA = sb.toString();
+ }
+
+ public RedisDistributedLockHandler(RedisTemplate<Object, Object> redisTemplate) {
+ super();
+ this.redisTemplate = redisTemplate;
+ }
+
+ @Override
+ public boolean lock(String key, long expire, int retryTimes, long sleepMillis) {
+ boolean result = setRedis(key, expire);
+ // 如果获取锁失败,按照传入的重试次数进行重试
+ while ((!result) && retryTimes-- > 0) {
+ try {
+ logger.debug("lock failed, retrying..." + retryTimes);
+ Thread.sleep(sleepMillis);
+ } catch (InterruptedException e) {
+ return false;
+ }
+ result = setRedis(key, expire);
+ }
+ return result;
+ }
+
+ private boolean setRedis(String key, long expire) {
+ try {
+ String result = redisTemplate.execute(new RedisCallback<String>() {
+ @Override
+ public String doInRedis(RedisConnection connection) throws DataAccessException {
+ JedisCommands commands = (JedisCommands) connection.getNativeConnection();
+ String uuid = UUID.randomUUID().toString();
+ lockFlag.set(uuid);
+ return commands.set(key, uuid, "NX", "PX", expire);
+ }
+ });
+ return !StringUtils.isEmpty(result);
+ } catch (Exception e) {
+ logger.error("set redis occured an exception", e);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean releaseLock(String key) {
+ // 释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期,此时有可能已经被另外一个线程持有锁,所以不能直接删除
+ try {
+ List<String> keys = new ArrayList<String>();
+ keys.add(key);
+ List<String> args = new ArrayList<String>();
+ args.add(lockFlag.get());
+
+ // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
+ // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本
+
+ Long result = redisTemplate.execute(new RedisCallback<Long>() {
+ public Long doInRedis(RedisConnection connection) throws DataAccessException {
+ Object nativeConnection = connection.getNativeConnection();
+ // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
+ // 集群模式
+ if (nativeConnection instanceof JedisCluster) {
+ return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
+ }
+
+ // 单机模式
+ else if (nativeConnection instanceof Jedis) {
+ return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
+ }
+ return 0L;
+ }
+ });
+
+ return result != null && result > 0;
+ } catch (Exception e) {
+ logger.error("release lock occured an exception", e);
+ }
+ return false;
+ }
+
+}
diff --git a/common/framework/src/main/resources/META-INF/spring.factories b/common/framework/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..53d2c7c
--- /dev/null
+++ b/common/framework/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+ com.supwisdom.institute.backend.common.framework.distributedlock.RedisDistributedLockAutoConfiguration