一键使用分布式锁

148 阅读3分钟

1 前言

     在我们项目开发中,分布式锁基本上都会用到。现在用的最多的就是redission,但是在实际开发中加锁和释放锁稍不注意就会出问题。为了规避低级错误和方便使用,我最近基于redission封装了一个工具类,可以优雅的对我们方法加分布式锁。

2 使用方式

       举例,我们有这样一个方法: 会员余额变更,为了防止多实例并发情况下余额错乱问题,在做会员余额变更时 我们需要对当前会员加锁。加锁时我们只用在方法上加自定义的lock注解 ,在方法参数中加lockkey注解。根据业务我们这里是需要对当前会员加锁,所以我们在memberid上加lockkey注解,加上这两个注解后,加锁就完成了。

3 代码实现原理

3.1 目录结构

3.2 首先定义了三个注解

  1.  EnableRedisson,它可以放到我们springboot的启动类上面,表示我们当前项目需要用到分布式锁,根据配置会把我们当前分布式锁的相关类初始化到我们spring容器中。代码如下:

    import java.lang.annotation.Documented;
    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;
    
    import org.springframework.context.annotation.Import;
    
    import com.mrxu.framework.starter.redisson.LockAutoConfiguration;
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(LockAutoConfiguration.class)
    public @interface EnableRedisson {
    
    }
    
  2. lock 主要作用于方法上,定义锁的一些相关属性,可以设置锁的类型为:自旋锁、阻塞锁。锁的持续时间,自旋时间,锁的前缀,获取锁失败后的提示。代码如下:

    import com.mrxu.framework.starter.redisson.bean.LockPolicy;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Lock {
    
    	/**
    	 * 锁的策略
    	 */
        LockPolicy policy() default LockPolicy.SpinLock;
    
        /**
         * 锁持续时间 单位秒 默认1分钟
         */
        int leaseTime() default 60;
        
        /**
         * 自旋锁超时时间 单位毫秒
         */
        long waitTime() default 2000L;
    
        /**
         * redis锁的key前缀 如果为空,则默认为类名+方法名
         */
        String keyPrex() default "";
        
        /**
         * 未获取资源锁提示
         */
        String failMsg() default "请勿重复操作";
        
    }
    

    锁类型枚举:

    public enum LockPolicy {
    	
        /**
         * 默认锁 失败立即返回
         */
        Lock,
        /**
         * 自旋锁 超时时间timeout
         */
        SpinLock,
        /**
         * 阻塞锁 无超时自旋
         */
        BlockLock
        
    }
    
  3. lockkey 作用于方法的参数上,用于缩小锁的范围。代码如下:

    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;
    
    @Target({ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface LockKey {
    	
        /**
         * 用在model参数前时, 需指定用作key的字段名。
         */
        String[] keyField() default {};
        
    }
    

3.3 核心实现类

     LockManager,项目通过EnableRedisson,会把它引入初始化到spring容器中,它拦截了所有lock注解的方法,做了个切面,然后根据注解计算出分布式锁的key 然后在方法调用前进行加锁,finally方法里 释放锁。代码如下:

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

import com.mrxu.framework.common.util.MrxuAssert;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.mrxu.framework.starter.redisson.anno.Lock;
import com.mrxu.framework.starter.redisson.anno.LockKey;
import com.mrxu.framework.starter.redisson.bean.LockPolicy;

/**
 * @version v1
 * @summary 分布式锁管理
 @Lock(policy= LockPolicy.SpinLock,leaseTime=60,waitTime=2000l,keyPrex="",failMsg="测试")
 public ResponseObj<PageResult<StoreInfo>> list(@LockKey @RequestParam("tenantId")String tenantId,@LockKey(keyField="tenantId") @RequestBody StoreDto dto)
 */
@Aspect
@Component
public class LockManager {

    private static Logger logger = LoggerFactory.getLogger(LockManager.class);

    @Autowired
    private RedissonClient redissonClient;

    @Around(value = "@annotation(lock)", argNames = "pjp, lock")
    public Object around(ProceedingJoinPoint pjp,Lock lock) throws Throwable {
        Class<? extends Object> clazz = pjp.getTarget().getClass();
        String methodName = pjp.getSignature().getName();
        //获取参数
        Object[] args = pjp.getArgs();
        MethodSignature ms = (MethodSignature) pjp.getSignature();
        Method m = ms.getMethod();

        //获取锁前缀,如果没配置 取方法名
        String keyPrix = lock.keyPrex();
        if ("".equals(keyPrix)) {
            keyPrix = clazz.getName() + ":" + methodName + ":";
        }
        //分布式锁的key
        StringBuilder lockKey = new StringBuilder("lock:" + keyPrix);
        //获取加注解的方法参数的值
        Annotation[][] parameterAnnotations = m.getParameterAnnotations();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            Annotation[] annotations = parameterAnnotations[i];
            for (Annotation annotation : annotations) {
                if (annotation.annotationType() == LockKey.class) {
                    LockKey anno = (LockKey) annotation;
                    if (anno.keyField() != null && anno.keyField().length > 0) {
                        lockKey.append(genModelKey(args[i], anno.keyField()));
                    }
                    else {
                        lockKey.append("_");
                        lockKey.append(args[i]);
                    }
                }
            }
        }
        logger.debug("分布式锁key:{}",lockKey.toString());
        LockPolicy policy = lock.policy();
        RLock fairLock = redissonClient.getFairLock(lockKey.toString());
        boolean getLock = false;
        if(policy.equals(LockPolicy.Lock)) {
        	getLock = fairLock.tryLock();
        }
        else if(policy.equals(LockPolicy.SpinLock)) {
        	getLock = fairLock.tryLock(lock.waitTime(),lock.leaseTime(),TimeUnit.SECONDS);
        }
        else {
        	getLock = fairLock.tryLock(Long.MAX_VALUE,lock.leaseTime(),TimeUnit.SECONDS);
        }
        //获取不到锁直接终止流程
        MrxuAssert.isTrue(getLock,lock.failMsg());
        try {
        	return pjp.proceed();
        }
		finally {
			fairLock.unlock();
            logger.debug("解锁key:{}",lockKey.toString());
		}
    }

    private String genModelKey(Object model, String[] fields) throws Exception {
        StringBuilder sb = new StringBuilder();
        for (String field : fields) {
            sb.append("_");
            try {
                sb.append(model.getClass().getMethod(buildGetMethod(field)).invoke(model));
            }
            catch (Exception e) {
                logger.error("redisLock error: keyField '{}' 不存在", field, e);
                throw e;
            }
        }
        return sb.toString();
    }

    private String buildGetMethod(String field) {
        StringBuilder sb = new StringBuilder("get");
        sb.append(Character.toUpperCase(field.charAt(0)));
        sb.append(field.substring(1, field.length()));
        return sb.toString();
    }

}

3.4 项目引入方式说明

    项目启动类通过EnableRedisson引入LockAutoConfiguration实例然后通过它上面的注解@Import(LockAutoConfiguration.class) 引入LockAutoConfiguration类,这个类里面定义了Spring扫描分布式锁的相关类的包名。代码如下:

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@ComponentScan({"com.mrxu.framework.starter.redisson"})
@EnableAspectJAutoProxy
public class LockAutoConfiguration {

}

4 总结

  整个过程就是这样的,如果你也写这样一个工具类欢迎点赞收藏,方面以后参考。