使用redisson实现分布式锁

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

在微服务项目中使用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;
    /**
     * 获取到锁的持有时间(过期时间) 默认60秒 单位毫秒
     */
    public static final long LEASE_TIME = 60000L;

    /**
     * 时间单位
     */
    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;
}

在这里插入图片描述

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: redis