springboot使用Redisson+注解完成分布式锁

0.背景

我们项目一个服务部署了三台服务器,所以为避免在执行定时任务的时候重复执行,
需要引入分布式锁,本打算引入Quartz框架,后发现引入框架需要添加很多数据库,
为做到最小影响,故使用Redisson做分布式锁就行了

1.引入pom坐标
如果是springboot2.0.X使用如下pom坐标

<!--Redisson分布式锁-->
		<dependency>
			<groupId>org.redisson</groupId>
			<artifactId>redisson-spring-boot-starter</artifactId>
			<exclusions>
				<!--因为是springboot2.0.8排除21依赖-->
				<exclusion>
					<groupId>org.redisson</groupId>
					<artifactId>redisson-spring-data-21</artifactId>
				</exclusion>
			</exclusions>
			<version>3.10.6</version>
		</dependency>
		<dependency>
			<groupId>org.redisson</groupId>
			<artifactId>redisson-spring-data-20</artifactId>
			<version>3.10.6</version>
		</dependency>

如果是2.1.X , 直接引入就行了

<!--Redisson分布式锁-->
		<dependency>
			<groupId>org.redisson</groupId>
			<artifactId>redisson-spring-boot-starter</artifactId>
			<version>3.10.6</version>
		</dependency>

当然redis也需要引入

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

2.创建redisson注解

import com.sangfor.enums.LockTypeEnum;
import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 * Redisson锁(Redis分布式锁)注解
 *
 * @author :zhouJia
 * @date :2020-11-09 17:52
 **/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
//@Inherited
public @interface LockAction {
	/** 锁的资源,key。支持spring El表达式*/
	@AliasFor("key")
	String value() default "'default'";

	@AliasFor("value")
	String key() default "'default'";

	/** 锁类型*/
	LockTypeEnum lockType() default LockTypeEnum.REENTRANT_LOCK;

	/** 获取锁等待时间,默认10秒*/
	long waitTime() default 10000L;

	/** 锁自动释放时间,默认10秒*/
	long leaseTime() default 10000L;

	/** 时间单位(获取锁等待时间和持锁时间都用此单位)*/
	TimeUnit unit() default TimeUnit.MILLISECONDS;
}

3. aop切面类


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.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;


/**
 * LockAction注解切面类
 *
 * @author :zhouJia
 * @date :2020-11-10 19:52
 **/
@Aspect
@Component
//@ConditionalOnBean(RedissonClient.class)
//@AutoConfigureAfter(RedissonAutoConfiguration.class)
@Slf4j
public class RedissonDistributedLockAspectConfiguration {

	@Autowired
	private RedissonClient redissonClient;

	private ExpressionParser parser = new SpelExpressionParser();

	private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();

	@Pointcut("@annotation(com.sangfor.aop.LockAction)")
	public void lockPoint(){
	}

	@Around("lockPoint()")
	public Object around(ProceedingJoinPoint pjp) throws Throwable{
		Method method = ((MethodSignature) pjp.getSignature()).getMethod();
		LockAction lockAction = method.getAnnotation(LockAction.class);
		String key = lockAction.value();
		Object[] args = pjp.getArgs();
		//key = parse(key, method, args);

		RLock lock = getLock(key, lockAction);
		if(!lock.tryLock(lockAction.waitTime(), lockAction.leaseTime(), lockAction.unit())) {
			log.debug("get lock failed [{}]", key);
			log.info(String.format("get lock failed [%s]", LocalDateTime.now()));
			return null;
		}

		//得到锁,执行方法,释放锁
		log.debug("get lock success [{}]", key);
		try {
			log.info(String.format("获取锁%s执行时间为:%s", key, LocalDateTime.now()));
			return pjp.proceed();

		} catch (Exception e) {
			log.error("execute locked method occured an exception", e);
		} finally {
			lock.unlock();
			log.debug("release lock [{}]", key);
		}
		return null;
	}

	/**
	 * @description 解析spring EL表达式
	 * @author fuwei.deng
	 * @date 2018年1月9日 上午10:41:01
	 * @version 1.0.0
	 * @param key 表达式
	 * @param method 方法
	 * @param args 方法参数
	 * @return
	 */
	private String parse(String key, Method method, Object[] args) {
		String[] params = discoverer.getParameterNames(method);
		EvaluationContext context = new StandardEvaluationContext();
		for (int i = 0; i < params.length; i ++) {
			context.setVariable(params[i], args[i]);
		}
		return parser.parseExpression(key).getValue(context, String.class);
	}

	private RLock getLock(String key, LockAction lockAction) {
		switch (lockAction.lockType()) {
			case REENTRANT_LOCK:
				return redissonClient.getLock(key);

			case FAIR_LOCK:
				return redissonClient.getFairLock(key);

			case READ_LOCK:
				return redissonClient.getReadWriteLock(key).readLock();

			case WRITE_LOCK:
				return redissonClient.getReadWriteLock(key).writeLock();

			default:
				throw new RuntimeException("do not support lock type:" + lockAction.lockType().name());
		}
	}
}

3.实际使用

	/**
	 * 背景: temp_dd
	 * 定时方法作用: 自动从临时表表同步物料到物料表
	 */
	@Scheduled(cron = "0 10/30 0/1 * * ?")
	@LockAction(value = "LOCK:SCHEDULE:SYNC_TEMP", leaseTime= 15L, unit= TimeUnit.MINUTES)
	public void autoSyncTempToProductUnit() {
		String key = System.currentTimeMillis() + "";
		try {
			productUnitService.autoSyncTempToProductUnit(key);
		} catch (Exception e) {
			log.error("定时同步表失败", e);
			moaMessageSender.sendMsg(LocalDateTime.now()+", [同步数据异常]" + e, Arrays.asList(moaRemind.split(SymbolConstant.COMMA)));
		}
	}

你可能感兴趣的:(spring,boot,分布式,java)