在微服务项目中使用redisson实现一个分布式锁
一、引入依赖
spring-boot版本2.3.12.RELEASE
<dependencies>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.8</version>
</dependency>
</dependencies>
二、配置redisson
1、RedissonConfig
@Configuration
public class RedissonConfig {
@Bean
public Redisson redisson() {
Config config = new Config();
//主从(单机)
config.useSingleServer()
.setAddress("redis://" +
SystemUtils.getProperty("spring.redis.host", "127.0.0.1") + ":" +
SystemUtils.getProperty("spring.redis.port", "6379"))
.setConnectTimeout(SystemUtils.getProperty("spring.redis.timeout", 0, Integer.class))
.setRetryInterval(3000)
.setPassword(SystemUtils.getProperty("spring.redis.password"))
.setDatabase(SystemUtils.getProperty("spring.redis.database", 0, Integer.class));
return (Redisson) Redisson.create(config);
}
}
2、SystemUtils工具类获取系统配置文件值
import cn.hutool.core.convert.Convert;
import cn.hutool.extra.spring.SpringUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SystemUtils {
/**
* 获取系统配置(动态刷新)
*
* @param key
* @return
*/
public static String getProperty(String key) {
return SpringUtil.getProperty(key);
}
/**
* 获取系统配置(动态刷新)
*
* @param key 键
* @param defaultValue 默认值
* @return
*/
public static String getProperty(String key, String defaultValue) {
String property = getProperty(key);
return StringUtils.isBlank(property) ? defaultValue : property;
}
/**
* 获取系统配置(动态刷新)
*
* @param key 键
* @param clazz 返回对象类型
* @return T
*/
public static <T> T getProperty(String key, Class<T> clazz) {
return Convert.convert(clazz, getProperty(key));
}
/**
* 获取系统配置(动态刷新)
*
* @param key 键
* @param defaultValue 默认值
* @param clazz 返回对象类型
* @return T
*/
public static <T> T getProperty(String key, T defaultValue, Class<T> clazz) {
return Convert.convert(clazz, getProperty(key), defaultValue);
}
}
3、AssertUtils 判空、抛异常工具类
public class AssertUtils {
private static final String NULL_STR = "null";
public static void throwException(String msg) {
throw new BusinessException(msg);
}
public static void throwException(boolean bool, String msg) {
if (bool) {
throw new BusinessException(msg);
}
}
public static void isEmpty(Object param, String msg) {
if (param == null) {
throw new BusinessException(msg);
}
}
}
4、LockUtils 锁工具类
@Slf4j
@Component
public class LockUtils {
/**
* 获取锁的等待时间、默认3秒 单位毫秒
*/
public static final long WAIT_TIME = 3000L;
/**
* 获取到锁的持有时间(过期时间)
* 默认-1 使用看门狗机制,30s会检查业务是否执行完成,未完成会自动续期;
* 若是大于1 不会使用看门狗机制,到时间就会释放锁
*/
public static final long LEASE_TIME = -1;
/**
* 时间单位
*/
public static final TimeUnit TIME_UNIT = TimeUnit.MILLISECONDS;
/**
* 锁前缀
*/
public static final String LOCK_PREFIX = "lock:";
/**
* 提示信息
*/
public static final String ERROR_MSG = "服务繁忙,请稍后重试!";
@Autowired
private Redisson redisson;
/**
* 获取锁对象
* @param key
*/
public RLock getLockObj(String key){
return redisson.getLock(LOCK_PREFIX + key);
}
/**
* 加锁-未获取到锁不抛出异常
* @param lock 锁的key
* @return
*/
public boolean getLock(RLock lock) {
return getLock(lock, WAIT_TIME, LEASE_TIME, TIME_UNIT);
}
/**
* 加锁
* @param lock 锁的key
* @param waitTime 获取锁的等待时间
* @param leaseTime 获取到锁的持有时间(过期时间)
*/
public boolean getLock(RLock lock, long waitTime, long leaseTime, TimeUnit unit) {
return doLock(lock, waitTime, leaseTime, unit, false);
}
/**
* 加锁-未获取到锁直接抛出异常
* @param lock 锁的key
*/
public void tryLock(RLock lock) {
tryLock(lock, WAIT_TIME, LEASE_TIME, TIME_UNIT);
}
/**
* 加锁-未获取到锁直接抛出异常
* @param lock 锁的key
*/
public void tryLock(RLock lock, long waitTime, long leaseTime, TimeUnit unit) {
doLock(lock, waitTime, leaseTime, unit, true);
}
/**
* 加锁
* @param lock 锁的key
* @param waitTime 获取锁的等待时间
* @param leaseTime 获取到锁的持有时间(过期时间)
*/
private boolean doLock(RLock lock, long waitTime, long leaseTime, TimeUnit unit, boolean isException) {
AssertUtils.isEmpty(lock, "锁对象不能为空!");
log.info("开始获取分布式锁key:{}", lock.getName());
try {
boolean leaseLock = lock.tryLock(waitTime, leaseTime, unit);
if (leaseLock) {
log.info("获取分布式锁成功key:{}", lock.getName());
} else {
log.info("获取分布式锁失败key:{}", lock.getName());
//是否需要直接抛出异常
AssertUtils.throwException(isException, ERROR_MSG);
}
return leaseLock;
} catch (InterruptedException e) {
log.info("获取分布式锁失败key:{}", lock.getName());
return false;
}
}
/**
* 解锁
* @param lock 锁对象
*/
public void unLock(RLock lock) {
//锁对象不为空 并且当前存在锁 并且是当前线程的锁
if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()){
log.info("分布式锁解锁key:{}", lock.getName());
lock.unlock();
}
}
}
4、手动使用分布式锁
(1)获取到分布式锁执行业务逻辑,若获取不到直接抛出异常
@Autowired
private LockUtils lockUtils;
public void testLock(String key){
//获取锁对象
RLock lockObj = lockUtils.getLockObj(key);
try {
//尝试获取锁、如果他获取到锁继续执行代码逻辑,若果获取不到会抛出异常
lockUtils.tryLock(lockObj);
System.out.println("获取到锁");
}finally {
//释放锁
lockUtils.unLock(lockObj);
}
}
(2)获取到分布式锁返回布尔值,true:获取到,false:未获取到
@Autowired
private LockUtils lockUtils;
public void testLock(String key){
//获取锁对象
RLock lockObj = lockUtils.getLockObj(key);
try {
//尝试获取锁、如果他获取到锁继续执行代码逻辑,若果获取不到会抛出异常
boolean lock = lockUtils.getLock(lockObj);
if (lock){
//获取到锁处理的事(业务逻辑)
System.out.println("获取到锁");
}else {
//没有获取到锁自己想处理的事
System.out.println("没有获取到锁");
}
}finally {
//释放锁
lockUtils.unLock(lockObj);
}
}
5、自定义注解方式使用(支持spring spel表达式获取值作为锁的key)
(1)自定义注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lock {
/**
* 锁的key 默认类型_方法名 spel表达式示例
* 对象: #{#obj.value} Map: #{#map['key']}
*/
String key() default "";
/**
* 获取到锁的持有时间(过期时间) 默认60秒
*/
long leaseTime() default LockUtils.LEASE_TIME;
/**
* 获取锁的等待时间 默认3秒
*/
long waitTime() default LockUtils.WAIT_TIME;
/**
* 时间单位 默认毫秒
*/
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
/**
* 未获取到锁的提示信息
*/
String errorMessage() default LockUtils.ERROR_MSG;
}
(2)、解析spel表达式
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Map;
public class SpelParserUtils {
public static String parse(String elString, JoinPoint point) {
return (String)parse(String.class, elString, point);
}
public static <R> R parse(Class<R> r, String elString, JoinPoint point) {
String[] paramNames = ((MethodSignature)point.getSignature()).getParameterNames();
Object[] args = point.getArgs();
EvaluationContext context = new StandardEvaluationContext();
for(int i = 0; i < paramNames.length; ++i) {
context.setVariable(paramNames[i], args[i]);
}
return parse(r, elString, (EvaluationContext)context);
}
public static String parse(String elString, Map<String, Object> map) {
EvaluationContext context = new StandardEvaluationContext();
map.forEach(context::setVariable);
return parse(elString, (EvaluationContext)context);
}
public static String parse(String elString, EvaluationContext context) {
return (String)parse(String.class, elString, context);
}
public static <R> R parse(Class<R> r, String elString, EvaluationContext context) {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(elString, new TemplateParserContext());
return expression.getValue(context, r);
}
}
(2) AOP解析注解
@Slf4j
@Aspect
@Component
@EnableAspectJAutoProxy
public class LockAspect {
@Autowired
private LockUtils lockUtils;
//com.base.annotation.Lock 是注解全包名
@Pointcut("@annotation(com.base.annotation.Lock)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method signatureMethod = signature.getMethod();
Lock lock = signatureMethod.getAnnotation(Lock.class);
Object result = null;
if (lock != null) {
//加锁
String key = lock.key();
if (StringUtils.isBlank(key)) {
String clazzName = joinPoint.getTarget().getClass().getName();
String methodName = signatureMethod.getName();
key = clazzName + "_" + methodName;
} else {
key = SpelParserUtils.parse(key, joinPoint);
}
RLock rLock = lockUtils.getLockObj(key);
try {
//获取不到锁会抛出异常
boolean getLock = lockUtils.getLock(rLock, lock.waitTime(), lock.leaseTime(), lock.timeUnit());
if (getLock) {
//执行方法
result = joinPoint.proceed();
} else {
AssertUtils.throwException(lock.errorMessage());
}
} finally {
//解锁
lockUtils.unLock(rLock);
}
} else {
//没有添加锁注解
result = joinPoint.proceed();
}
return result;
}
}
使用注解
//默认锁的key是类名_方法名
//使用时最好指定key
@Lock
public void testAnnotationLock() {
}
//自定义key
@Lock(key = "#{#obj.title}")
@ResponseBody
@PostMapping("/testAnnotationLock")
public void testAnnotationLock(@RequestBody Subject obj) {
}
@Data
@TableName("t_subject")
@ApiModel("题目")
public class Subject {
@TableId
@ApiModelProperty(value = "主键")
private int id;
@ApiModelProperty(value = "标题")
private String title;
@ApiModelProperty(value = "选项A")
private String option1;
@ApiModelProperty(value = "选项B")
private String option2;
@ApiModelProperty(value = "选项C")
private String option3;
@ApiModelProperty(value = "选项D")
private String option4;
@ApiModelProperty(value = "答案")
private String answer;
}