Maison  >  Article  >  base de données  >  Parlons de la façon d'implémenter des verrous distribués dans Redis qui prennent en charge presque tous les scénarios de verrouillage.

Parlons de la façon d'implémenter des verrous distribués dans Redis qui prennent en charge presque tous les scénarios de verrouillage.

青灯夜游
青灯夜游avant
2021-11-02 11:02:411682parcourir

Cet article vous présentera la méthode d'implémentation du verrouillage distribué Redis. J'espère qu'il vous sera utile !

Parlons de la façon d'implémenter des verrous distribués dans Redis qui prennent en charge presque tous les scénarios de verrouillage.

Bonjour à tous, aujourd'hui je vais partager avec vous le verrou multi-type implémenté par redisson, l'implémentation du verrou distribué redis qui prend en charge presque tous les scénarios de verrouillage, et prend également en charge diverses opérations de données de petit MQ et redis. [Recommandations associées : Tutoriel vidéo Redis]

Partie théorique

Dans l'article précédent, nous avons présenté deux façons d'implémenter les verrous distribués via Redis, qui sont :

  • Grâce aux commandes fournies avec Redis : setNX

  • Grâce au client redisson : redisson

L'auteur recommande d'utiliser le client redisson car redisson prend en charge plus de types de verrous, tels que interlock, red lock, read-write lock, fair lock, etc. , et le l'implémentation de redisson est plus simple. Les développeurs n'ont qu'à appeler l'API correspondante et n'ont pas à se soucier du processus de verrouillage sous-jacent et du problème d'atomicité du déverrouillage. API即可,无需关心底层加锁的过程和解锁的原子性问题。

在Redis分布式锁中,列出了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;
    }
}

这里使用了策略模式

Dans le verrou distribué Redis, l'implémentation simple par Redisson de différents types de verrous est répertoriée, c'est-à-dire l'implémentation programmatique. Une telle mise en œuvre peut pleinement répondre à nos besoins quotidiens de développement, mais ses inconvénients sont également évidents.

Par exemple :

Il y a trop de codes intégrés, ce qui n'est pas assez élégant

Code en double

  • L'utilisation des paramètres de verrouillage n'est pas intuitive
Il est facile d'oublier le déverrouillage étapes
  • Les étudiants Spring utilisés doivent tous connaître l'annotation @Transactional Spring prend en charge à la fois les transactions programmatiques et les transactions annotées (déclaratives).
Peut-on également faire référence à une telle implémentation ?
  • La réponse est : tout à fait OK ! AOP est spécialement conçu pour faire ce genre de chose.
    Partie pratique
  • 1 Présentation de la dépendance redisson
/**
 * 锁策略抽象基类
 */
@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;
    }
}

    2. data-id="heading-5">
  • 3, classe constante
    /**
     * 可重入锁策略
     */
    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);
            }
        }
    }

  • 4, énumération
    /**
     * 公平锁策略
     */
    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));
        }
    }

  • 5. Exception personnalisée
    /**
     * 联锁策略
     */
    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);
        }
    }

  • 6. Aspects AOP

/**
 * 红锁策略
 */
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);
    }
}
Mode stratégie est utilisé ici pour fournir différents types de verrouillage.

7. Implémentation de la stratégie de verrouillage

Parlons de la façon d'implémenter des verrous distribués dans Redis qui prennent en charge presque tous les scénarios de verrouillage.Définissez d'abord la Parlons de la façon d'implémenter des verrous distribués dans Redis qui prennent en charge presque tous les scénarios de verrouillage.classe de base abstraiteParlons de la façon d'implémenter des verrous distribués dans Redis qui prennent en charge presque tous les scénarios de verrouillage. de la stratégie de verrouillage

(vous pouvez également utiliser l'

interface) :

/**
 * 读锁策略
 */
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();
    }
}

Fournissez ensuite l'implémentation spécifique des différents modes de verrouillage : 🎜 🎜🎜🎜Reproductible Enter lock: 🎜🎜🎜
/**
 * 写锁策略
 */
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();
    }
}
🎜🎜🎜Fair lock:🎜🎜🎜
/**
 * 锁的策略工厂
 */
@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);
    }
}
🎜🎜🎜Interlock🎜🎜🎜
    @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));
    }
🎜🎜🎜Red lock🎜🎜 🎜rrreee🎜🎜🎜Lire le verrouillage 🎜🎜🎜rrreee🎜🎜🎜 Write Lock🎜 🎜🎜rrreee🎜Fournit enfin une 🎜stratégie d'usine🎜stratégie de verrouillage d'initialisation : 🎜rrreee🎜8 Comment utiliser 🎜rrreee🎜Vous avez terminé, vous avez terminé avec succès🎜🎜🎜🎜🎜Pour plus de connaissances liées à la programmation. , veuillez visiter : 🎜Vidéo de programmation🎜 ! ! 🎜

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer