基于Redission实现分布式锁(注解版)

POM依赖坐标

关键引用如下

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

RedissionConfig 配置

主要是将RedissonClient与Redis数据库关联起来

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;

/**
 * 初始化Redission Client
 *
 * @author huxiang
 */
@Configuration
@Order(value = 3)
@Slf4j
public class RedissionConfig {

    @Value("${console.redis.host}")
    private String host;
    @Value("${console.redis.port}")
    private int port;
    @Value("${console.redis.database}")
    private int database;
    @Value("${console.redis.password}")
    private String password;


    @Bean
    public RedissonClient initRedisson() {
        Config config = new Config();
        //单机模式自动装配
        config.useSingleServer().setAddress("redis://" + host + ":" + port)
                .setDatabase(database);
        if (StringUtils.isNotEmpty(password)) {
            config.useSingleServer().setAddress("redis://" + host + ":" + port).setDatabase(database)
                    .setPassword(password);
        } else {
            config.useSingleServer().setAddress("redis://" + host + ":" + port).setDatabase(database);
        }

//         设置全局默认看门狗机续期时间,如果在使用时不设置,则使用全局的,如果全局不设置,则使用默认的30000,单位毫秒
//         为啥要看门狗:
//            如果业务超长,运行期间自动给锁续上新的有效时间,不用担心业务时间长,锁自动过期被删掉,其实就是可重入锁

        config.setLockWatchdogTimeout(2000);
        log.debug("--------------Redission Client Created------------");
        return Redisson.create(config);
    }
}

注解类

import javax.validation.constraints.NotNull;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁注解
 *
 * @author huxiang
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Dislock {

    /**
     * 分布式锁的key,一般可以用用户id,用户token这种作为唯一的key,达到自己的锁只能被自己解的效果,如#userId
     */
    @NotNull
    String localKey();

    /**
     * 业务分类 默认为All不分类,建议分类
     *
     * @return
     */
    String biz() default "ALL";

    /**
     * 锁等待时间 默认2秒
     *
     * @return
     */
    long waitTime() default 2 * 1000;

    /**
     * 锁释放时间 默认2秒
     *
     * @return
     */
    long leaseTime() default 2 * 1000;

    /**
     * 时间格式 默认:毫秒
     *
     * @return
     */
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;

}
 

Lock Key 解析器

生成lockKey,保证锁定资源的唯一性,并且只能解锁自己加的锁

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.CodeSignature;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

/**
 * lock key 解析器
 *
 * @author huxiang
 */
public class LockKeyParser {

    /**
     * 解析缓存的key
     *
     * @param proceedingJoinPoint 切面
     * @param localKey            lock key
     * @param biz                 业务分类
     * @return String
     * @throws IllegalAccessException 异常
     */
    public static String parse(ProceedingJoinPoint proceedingJoinPoint, String localKey,
                               String biz) throws IllegalAccessException {
        // 解析实际参数的key
        String key = localKey.replace("#", "");
        StringTokenizer stringTokenizer = new StringTokenizer(key, ".");

        Map<String, Object> nameAndValue = getNameAndValue(proceedingJoinPoint);
        Object actualKey = null;

        while (stringTokenizer.hasMoreTokens()) {
            if (actualKey == null) {
                actualKey = nameAndValue.get(stringTokenizer.nextToken());
            } else {
                actualKey = getPropValue(actualKey, stringTokenizer.nextToken());
            }
        }

        return biz + "-" + actualKey;
    }

    /**
     * 获取参数Map集合
     *
     * @param joinPoint 切面
     *
     * @return
     */
    private static Map<String, Object> getNameAndValue(ProceedingJoinPoint joinPoint) {
        Object[] paramValues = joinPoint.getArgs();
        String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        Map<String, Object> param = new HashMap<>(paramNames.length);

        for (int i = 0; i < paramNames.length; i++) {
            param.put(paramNames[i], paramValues[i]);
        }
        return param;
    }

    /**
     * 获取指定参数名的参数值
     *
     * @param obj
     * @param propName
     * @return
     *
     * @throws IllegalAccessException
     */
    public static Object getPropValue(Object obj, String propName) throws IllegalAccessException {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field f : fields) {
            if (f.getName().equals(propName)) {
                //在反射时能访问私有变量
                f.setAccessible(true);
                return f.get(obj);
            }
        }
        return null;
    }

}

AOP切面处理类

我们想通过注解方式无感实现分布式锁,必须要用到AOP的功能,而且是环绕通知(有do 有 return)

import com.paratera.console.biz.lock.annotation.Dislock;
import com.paratera.console.biz.lock.parse.LockKeyParser;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
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.stereotype.Component;

/**
 * 分布式锁AOP切面类
 *
 * @author huxiang
 */
@Aspect
@Component
public class DislockAspect {

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 通过环绕通知实现加锁解锁
     * 切入@Dislock注解的point
     * @param proceedingJoinPoint
     * @return
     *
     * @throws Throwable
     */
    @Around("@annotation(com.paratera.console.biz.lock.annotation.Dislock)")
    public Object arround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object object = null;
        RLock lock = null;
        boolean status = false;
        try {
            Dislock dislock = getDislockInfo(proceedingJoinPoint);
            //生成lockKey,保证锁定资源的唯一性,并且只能解锁自己加的锁
            String lockKey = LockKeyParser.parse(proceedingJoinPoint, dislock.localKey(), dislock.biz());
            lock = redissonClient.getLock(lockKey);
            if (lock != null) {
                //试图加锁
                status = lock.tryLock(dislock.waitTime(), dislock.leaseTime(), dislock.timeUnit());
                if (status) {
                    //如果加锁成功,执行切点业务
                    object = proceedingJoinPoint.proceed();
                }
            }
        } finally {
            //判断是否需要解锁
            if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        return object;
    }

    public Dislock getDislockInfo(ProceedingJoinPoint proceedingJoinPoint) {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        return methodSignature.getMethod().getAnnotation(Dislock.class);
    }

}

测试

最后测试:
起两个或者更多的服务实例,操作同一个数据库(比如扣库存),然后通过nginx将这些服务实例进行负载均衡配置,通过jmeter或者其他压力测试工具实现并发访问,比较Service方法上加@Dislock和不加的区别

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值