From 268e4204b7295402c537f582e99c05cd68a959c6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=E5=88=98=E6=B4=AA=E9=9D=92?= Date: Thu, 10 Oct 2019 22:01:33 +0800 Subject: [PATCH] =?utf8?q?feat:=20=E5=9F=BA=E4=BA=8Eredis=20=E7=9A=84?= =?utf8?q?=E5=88=86=E5=B8=83=E5=BC=8F=E9=94=81=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../AbstractDistributedLockHandler.java | 30 +++++ .../DefaultDistributedLockHandler.java | 17 +++ .../DistributedLockAutoConfiguration.java | 21 ++++ .../DistributedLockHandler.java | 83 +++++++++++++ .../EnableDistributedLock.java | 17 +++ .../distributedlock/aop/DistributedLock.java | 36 ++++++ .../DistributedLockAspectConfiguration.java | 67 ++++++++++ .../main/resources/META-INF/spring.factories | 4 + common/framework/pom.xml | 24 ++++ ...RedisDistributedLockAutoConfiguration.java | 33 +++++ .../RedisDistributedLockHandler.java | 117 ++++++++++++++++++ .../main/resources/META-INF/spring.factories | 2 + 12 files changed, 451 insertions(+) create mode 100644 common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/AbstractDistributedLockHandler.java create mode 100644 common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DefaultDistributedLockHandler.java create mode 100644 common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DistributedLockAutoConfiguration.java create mode 100644 common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DistributedLockHandler.java create mode 100644 common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/EnableDistributedLock.java create mode 100644 common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/aop/DistributedLock.java create mode 100644 common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/aop/DistributedLockAspectConfiguration.java create mode 100644 common/core/src/main/resources/META-INF/spring.factories create mode 100644 common/framework/src/main/java/com/supwisdom/institute/backend/common/framework/distributedlock/RedisDistributedLockAutoConfiguration.java create mode 100644 common/framework/src/main/java/com/supwisdom/institute/backend/common/framework/distributedlock/RedisDistributedLockHandler.java create mode 100644 common/framework/src/main/resources/META-INF/spring.factories 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 lockFlag = new ThreadLocal(); + + @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; + + /** + * 获取锁
+ * 超时:30000 ms (30 s) {@link DistributedLockHandler.TIMEOUT_MILLIS}
+ * 重试次数:不重试 {@link DistributedLockHandler.RETRY_TIMES}
+ * 重试等待:500 ms {@link DistributedLockHandler.SLEEP_MILLIS}
+ *
+ * @param key + * @return + */ + public boolean lock(String key); + + /** + * 获取锁
+ * 超时:30000 ms (30 s) {@link DistributedLockHandler.TIMEOUT_MILLIS}
+ * 重试等待:500 ms {@link DistributedLockHandler.SLEEP_MILLIS}
+ *
+ * @param key + * @param retryTimes 重试次数 + * @return + */ + public boolean lock(String key, int retryTimes); + + /** + * 获取锁
+ * 超时:30000 ms (30 s) {@link DistributedLockHandler.TIMEOUT_MILLIS}
+ *
+ * @param key + * @param retryTimes 重试次数 + * @param sleepMillis 重试等待 单位 ms + * @return + */ + public boolean lock(String key, int retryTimes, long sleepMillis); + + /** + * 获取锁
+ * 重试次数:不重试 {@link DistributedLockHandler.RETRY_TIMES}
+ * 重试等待:500 ms {@link DistributedLockHandler.SLEEP_MILLIS}
+ *
+ * @param key + * @param expire 超时 单位 ms + * @return + */ + public boolean lock(String key, long expire); + + /** + * 获取锁
+ * 重试等待:500 ms {@link DistributedLockHandler.SLEEP_MILLIS}
+ *
+ * @param key + * @param retryTimes 重试次数 + * @param expire 超时 单位 ms + * @return + */ + public boolean lock(String key, long expire, int retryTimes); + + /** + * 获取锁
+ *
+ * @param key + * @param expire 超时 单位 ms + * @param retryTimes 重试次数 + * @param sleepMillis 重试等待 单位 ms + * @return + */ + public boolean lock(String key, long expire, int retryTimes, long sleepMillis); + + /** + * 释放锁
+ *
+ * @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 @@ spring-boot-starter-web true + + + org.springframework.boot + spring-boot-starter-aop + true + + org.springframework.boot spring-boot-starter-data-jpa true + + + + + org.springframework.data + spring-data-redis + true + + + redis.clients + jedis + true + 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 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 redisTemplate; + + private ThreadLocal lockFlag = new ThreadLocal(); + + 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 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() { + @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 keys = new ArrayList(); + keys.add(key); + List args = new ArrayList(); + args.add(lockFlag.get()); + + // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁 + // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本 + + Long result = redisTemplate.execute(new RedisCallback() { + 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 -- 2.17.1