这可能是最好的Redis分布式锁实现了

Posted Java小果

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了这可能是最好的Redis分布式锁实现了相关的知识,希望对你有一定的参考价值。

前言

Hello 大家好,今天给大家分享redisson实现的多类型锁、支持几乎所有加锁场景的redis分布式锁的实现,还支持小型MQ和redis的各种数据操作,完整源码可以关注我私聊。

理论部分

在之前的文章中,介绍了通过redis实现分布锁的两种方式,分别是:

  1. 通过redis自带的命令:setNX
  2. 通过redis的客户端:redisson

作者更加推荐使用redisson客户端的方式,因为redisson支持更多的锁类型,譬如联锁、红锁、读写锁、公平锁等,而且redisson的实现更加简单,开发者只需要调用响应的API即可,无需关心底层加锁的过程和解锁的原子性问题。列出了redisson对于多种的锁类型的简单实现,即编程式实现。这样的实现完全能够满足我们的日常开发需求,但是缺点也很明显。

譬如:

  • 代码嵌入较多,不够优雅
  • 重复代码
  • 对锁的参数运用不直观
  • 容易忘掉解锁的步骤

使用过Spring的同学,肯定都知道@Transactional注解,Spring即支持编程式事务,也支持注解式(声明式)事务。

我们是否也可以参考这样的实现呢?

答案是:完全OK!

AOP就是专门干这种事的。

实战部分

1、引入redisson依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.16.2</version>
</dependency>Copy to clipboardErrorCopied
复制代码

2、自定义注解

/**
 * 分布式锁自定义注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lock 

    /**
     * 锁的模式:如果不设置自动模式,当参数只有一个.使用 REENTRANT 参数多个 MULTIPLE
     */
    LockModel lockModel() default LockModel.AUTO;

    /**
     * 如果keys有多个,如果不设置,则使用 联锁
     *
     * @return
     */
    String[] keys() default ;

    /**
     * key的静态常量:当key的spel的值是LIST、数组时使用+号连接将会被spel认为这个变量是个字符串,只能产生一把锁,达不到我们的目的,
     * 而我们如果又需要一个常量的话。这个参数将会在拼接在每个元素的后面
     *
     * @return
     */
    String keyConstant() default "";

    /**
     * 锁超时时间,默认30000毫秒(可在配置文件全局设置)
     *
     * @return
     */
    long watchDogTimeout() default 30000;

    /**
     * 等待加锁超时时间,默认10000毫秒 -1 则表示一直等待(可在配置文件全局设置)
     *
     * @return
     */
    long attemptTimeout() default 10000;

复制代码

3、常量类

/**
 * Redisson常量类
 */
public class RedissonConst 
    /**
     * redisson锁默认前缀
     */
    public static final String REDISSON_LOCK = "redisson:lock:";
    /**
     * spel表达式占位符
     */
    public static final String PLACE_HOLDER = "#";

复制代码

4、枚举

/**
 * 锁的模式
 */
public enum LockModel 
    /**
     * 可重入锁
     */
    REENTRANT,
    /**
     * 公平锁
     */
    FAIR,
    /**
     * 联锁
     */
    MULTIPLE,
    /**
     * 红锁
     */
    RED_LOCK,
    /**
     * 读锁
     */
    READ,
    /**
     * 写锁
     */
    WRITE,
    /**
     * 自动模式,当参数只有一个使用 REENTRANT 参数多个 RED_LOCK
     */
    AUTO

复制代码

5、自定义异常

/**
 * 分布式锁异常
 */
public class ReddissonException extends RuntimeException 

    public ReddissonException() 
    

    public ReddissonException(String message) 
        super(message);
    

    public ReddissonException(String message, Throwable cause) 
        super(message, cause);
    

    public ReddissonException(Throwable cause) 
        super(cause);
    

    public ReddissonException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) 
        super(message, cause, enableSuppression, writableStackTrace);
    

复制代码

6、AOP切面

   /**
 * 分布式锁aop
 */
@Slf4j
@Aspect
public class LockAop 

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RedissonProperties redissonProperties;

    @Autowired
    private LockStrategyFactory lockStrategyFactory;

    @Around("@annotation(lock)")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint, Lock lock) throws Throwable 
        // 需要加锁的key数组
        String[] keys = lock.keys();
        if (ArrayUtil.isEmpty(keys)) 
            throw new ReddissonException("redisson lock keys不能为空");
        
        // 获取方法的参数名
        String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) proceedingJoinPoint.getSignature()).getMethod());
        Object[] args = proceedingJoinPoint.getArgs();
        // 等待锁的超时时间
        long attemptTimeout = lock.attemptTimeout();
        if (attemptTimeout == 0) 
            attemptTimeout = redissonProperties.getAttemptTimeout();
        
        // 锁超时时间
        long lockWatchdogTimeout = lock.watchdogTimeout();
        if (lockWatchdogTimeout == 0) 
            lockWatchdogTimeout = redissonProperties.getLockWatchdogTimeout();
        
        // 加锁模式
        LockModel lockModel = getLockModel(lock, keys);
        if (!lockModel.equals(LockModel.MULTIPLE) && !lockModel.equals(LockModel.RED_LOCK) && keys.length > 1) 
            throw new ReddissonException("参数有多个,锁模式为->" + lockModel.name() + ",无法匹配加锁");
        
        log.info("锁模式->,等待锁定时间->毫秒,锁定最长时间->毫秒", lockModel.name(), attemptTimeout, lockWatchdogTimeout);
        boolean res = false;
        // 策略模式获取redisson锁对象
        RLock rLock = lockStrategyFactory.createLock(lockModel, keys, parameterNames, args, lock.keyConstant(), redissonClient);
        //执行aop
        if (rLock != null) 
            try 
                if (attemptTimeout == -1) 
                    res = true;
                    //一直等待加锁
                    rLock.lock(lockWatchdogTimeout, TimeUnit.MILLISECONDS);
                 else 
                    res = rLock.tryLock(attemptTimeout, lockWatchdogTimeout, TimeUnit.MILLISECONDS);
                
                if (res) 
                    return proceedingJoinPoint.proceed();
                 else 
                    throw new ReddissonException("获取锁失败");
                
             finally 
                if (res) 
                    rLock.unlock();
                
            
        
        throw new ReddissonException("获取锁失败");
    

    /**
     * 获取加锁模式
     *
     * @param lock
     * @param keys
     * @return
     */
    private LockModel getLockModel(Lock lock, String[] keys) 
        LockModel lockModel = lock.lockModel();
        // 自动模式:优先匹配全局配置,再判断用红锁还是可重入锁
        if (lockModel.equals(LockModel.AUTO)) 
            LockModel globalLockModel = redissonProperties.getLockModel();
            if (globalLockModel != null) 
                lockModel = globalLockModel;
             else if (keys.length > 1) 
                lockModel = LockModel.RED_LOCK;
             else 
                lockModel = LockModel.REENTRANT;
            
        
        return lockModel;
    

复制代码

这里使用了策略模式来对不同的锁类型提供实现。

7、锁策略的实现

先定义锁策略的抽象基类(也可以用接口):

/**
 * 锁策略抽象基类
 */
@Slf4j
abstract class LockStrategy 

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 创建RLock
     *
     * @param keys
     * @param parameterNames
     * @param args
     * @param keyConstant
     * @return
     */
    abstract RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient);

    /**
     * 获取RLock
     *
     * @param keys
     * @param parameterNames
     * @param args
     * @param keyConstant
     * @return
     */
    public RLock[] getRLocks(String[] keys, String[] parameterNames, Object[] args, String keyConstant) 
        List<RLock> rLocks = new ArrayList<>();
        for (String key : keys) 
            List<String> valueBySpel = getValueBySpel(key, parameterNames, args, keyConstant);
            for (String s : valueBySpel) 
                rLocks.add(redissonClient.getLock(s));
            
        
        RLock[] locks = new RLock[rLocks.size()];
        int index = 0;
        for (RLock r : rLocks) 
            locks[index++] = r;
        
        return locks;
    

    /**
     * 通过spring Spel 获取参数
     *
     * @param key            定义的key值 以#开头 例如:#user
     * @param parameterNames 形参
     * @param args           形参值
     * @param keyConstant    key的常亮
     * @return
     */
    List<String> getValueBySpel(String key, String[] parameterNames, Object[] args, String keyConstant) 
        List<String> keys = new ArrayList<>();
        if (!key.contains(PLACE_HOLDER)) 
            String s = REDISSON_LOCK + key + keyConstant;
            log.info("没有使用spel表达式value->", s);
            keys.add(s);
            return keys;
        
        // spel解析器
        ExpressionParser parser = new SpelExpressionParser();
        // spel上下文
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < parameterNames.length; i++) 
            context.setVariable(parameterNames[i], args[i]);
        
        Expression expression = parser.parseExpression(key);
        Object value = expression.getValue(context);
        if (value != null) 
            if (value instanceof List) 
                List valueList = (List) value;
                for (Object o : valueList) 
                    keys.add(REDISSON_LOCK + o.toString() + keyConstant);
                
             else if (value.getClass().isArray()) 
                Object[] objects = (Object[]) value;
                for (Object o : objects) 
                    keys.add(REDISSON_LOCK + o.toString() + keyConstant);
                
             else 
                keys.add(REDISSON_LOCK + value.toString() + keyConstant);
            
        
        log.info("spel表达式key=,value=", key, keys);
        return keys;
    

复制代码

再提供各种锁模式的具体实现:

  • 可重入锁:
/**
 * 可重入锁策略
 */
public class ReentrantLockStrategy extends LockStrategy 

    @Override
    public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) 
        List<String> valueBySpel = getValueBySpel(keys[0], parameterNames, args, keyConstant);
        //如果spel表达式是数组或者集合 则使用红锁
        if (valueBySpel.size() == 1) 
            return redissonClient.getLock(valueBySpel.get(0));
         else 
            RLock[] locks = new RLock[valueBySpel.size()];
            int index = 0;
            for (String s : valueBySpel) 
                locks[index++] = redissonClient.getLock(s);
            
            return new RedissonRedLock(locks);
        
    

复制代码
  • 公平锁:
/**
 * 公平锁策略
 */
public class FairLockStrategy extends LockStrategy 

    @Override
    public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) 
        return redissonClient.getFairLock(getValueBySpel(keys[0], parameterNames, args, keyConstant).get(0));
    

复制代码
  • 联锁
/**
 * 联锁策略
 */
public class MultipleLockStrategy extends LockStrategy 

    @Override
    public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) 
        RLock[] locks = getRLocks(keys, parameterNames, args, keyConstant);
        return new RedissonMultiLock(locks);
    

复制代码
  • 红锁
/**
 * 红锁策略
 */
public class RedLockStrategy extends LockStrategy 

    @Override
    public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) 
        RLock[] locks = getRLocks(keys, parameterNames, args, keyConstant);
        return new RedissonRedLock(locks);
    

复制代码
  • 读锁
/**
 * 读锁策略
 */
public class ReadLockStrategy extends LockStrategy 

    @Override
    public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) 
        RReadWriteLock rwLock = redissonClient.getReadWriteLock(getValueBySpel(keys[0], parameterNames, args, keyConstant).get(0));
        return rwLock.readLock();
    

复制代码
  • 写锁
/**
 * 写锁策略
 */
public class WriteLockStrategy extends LockStrategy 

    @Override
    public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) 
        RReadWriteLock rwLock = redissonClient.getReadWriteLock(getValueBySpel(keys[0], parameterNames, args, keyConstant).get(0));
        return rwLock.writeLock();
    

复制代码

最后提供一个策略工厂初始化锁策略:

/**
 * 锁的策略工厂
 */
@Service
public class LockStrategyFactory 

    private LockStrategyFactory() 
    

    private static final Map<LockModel, LockStrategy> STRATEGIES = new HashMap<>(6);

    static 
        STRATEGIES.put(LockModel.FAIR, new FairLockStrategy());
        STRATEGIES.put(LockModel.REENTRANT, new ReentrantLockStrategy());
        STRATEGIES.put(LockModel.RED_LOCK, new RedLockStrategy());
        STRATEGIES.put(LockModel.READ, new ReadLockStrategy());
        STRATEGIES.put(LockModel.WRITE, new WriteLockStrategy());
        STRATEGIES.put(LockModel.MULTIPLE, new MultipleLockStrategy());
    

    public RLock createLock(LockModel lockModel, String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) 
        return STRATEGIES.get(lockModel).createLock(keys, parameterNames, args, keyConstant, redissonClient);
    

复制代码

8、使用方式

    @Lock(keys = "#query.channel") // 支持spel
    @ApiOperation("分页列表")
    @GetMapping
    public ApiPageResult list(VendorProjectItemQuery query, Pagination pagination) 
        return ApiPageResult.success(pagination, vendorProjectItemService.list(query, pagination), vendorProjectItemService.count(query));
    
复制代码

    希望能对你有所帮助!

    需要领取完整源码资料的兄弟们,关注私信“需要”,即可免费领取哦! 

以上是关于这可能是最好的Redis分布式锁实现了的主要内容,如果未能解决你的问题,请参考以下文章

大厂面试题详解:如何用Redis实现分布式锁?

基于Redis的分布式锁的简单实现

redis 分布式锁

Java实现基于Redis的分布式锁

这可能是最详细的分布式锁设计方案了

利用多写Redis实现分布式锁原理与实现分析