Heim  >  Artikel  >  Datenbank  >  Lassen Sie uns darüber sprechen, wie Sie verteilte Sperren in Redis implementieren, die fast alle Sperrszenarien unterstützen.

Lassen Sie uns darüber sprechen, wie Sie verteilte Sperren in Redis implementieren, die fast alle Sperrszenarien unterstützen.

青灯夜游
青灯夜游nach vorne
2021-11-02 11:02:411730Durchsuche

Dieser Artikel stellt Ihnen die Implementierungsmethode der verteilten Redis-Sperre vor. Ich hoffe, er wird Ihnen hilfreich sein!

Lassen Sie uns darüber sprechen, wie Sie verteilte Sperren in Redis implementieren, die fast alle Sperrszenarien unterstützen.

Hallo zusammen, heute werde ich mit Ihnen die von Redisson implementierte Multi-Type-Sperre teilen, die Implementierung der verteilten Redis-Sperre, die fast alle Sperrszenarien unterstützt und auch verschiedene Datenoperationen von kleinen MQ und Redis unterstützt. [Verwandte Empfehlungen: Redis-Video-Tutorial]

Theoretischer Teil

Im vorherigen Artikel haben wir zwei Möglichkeiten zum Implementieren verteilter Sperren über Redis vorgestellt:

  • Über die mit Redis gelieferten Befehle: setNX

  • Über den Redis-Client: Redisson

Der Autor empfiehlt die Verwendung des Redisson-Clients, da Redisson mehr Sperrtypen unterstützt, z. B. Interlock, Red Lock, Lese-/Schreibsperre, Fair Lock usw Die Implementierung von Redisson ist einfacher. Entwickler müssen nur die entsprechende API aufrufen und müssen sich nicht um den zugrunde liegenden Sperrprozess und das Problem der Atomarität beim Entsperren kümmern. 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;
    }
}

这里使用了策略模式In der verteilten Redis-Sperre ist die einfache Implementierung verschiedener Sperrtypen durch Redisson aufgeführt, dh die programmgesteuerte Implementierung. Eine solche Implementierung kann unsere täglichen Entwicklungsanforderungen vollständig erfüllen, aber auch ihre Mängel liegen auf der Hand.

Zum Beispiel:

Es sind zu viele Codes eingebettet, was nicht elegant genug istDoppelter Code

    Die Verwendung von Sperrparametern ist nicht intuitiv
  • Man vergisst leicht die Entsperrung Schritte
  • Nutzte Spring-Studenten müssen alle die @Transactional-Annotation kennen. Spring unterstützt sowohl programmatische Transaktionen als auch annotierte (deklarative) Transaktionen.
  • Können wir uns auch auf eine solche Umsetzung beziehen?
    Die Antwort lautet: Völlig OK!
  • AOP wurde speziell dafür entwickelt.
Praktischer Teil
  • 1. Einführung der Redisson-Abhängigkeit

  • /**
     * 锁策略抽象基类
     */
    @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, konstante Klasse
  • /**
     * 可重入锁策略
     */
    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, Aufzählung
/**
 * 公平锁策略
 */
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. Benutzerdefinierte Ausnahme
  • /**
     * 联锁策略
     */
    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. AOP-Aspekte
/**
 * 红锁策略
 */
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);
    }
}

Strategiemodus wird hier verwendet, um verschiedene Sperrtypen zu erreichen. 7. Implementierung der Sperrstrategie

Reproduzierbare Eingabesperre:

/**
 * 读锁策略
 */
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();
    }
}
Lassen Sie uns darüber sprechen, wie Sie verteilte Sperren in Redis implementieren, die fast alle Sperrszenarien unterstützen.Lassen Sie uns darüber sprechen, wie Sie verteilte Sperren in Redis implementieren, die fast alle Sperrszenarien unterstützen.Lassen Sie uns darüber sprechen, wie Sie verteilte Sperren in Redis implementieren, die fast alle Sperrszenarien unterstützen.Gerechte Sperre:

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

🎜Interlock🎜🎜🎜
/**
 * 锁的策略工厂
 */
@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);
    }
}
🎜🎜🎜Rote Sperre🎜 🎜🎜
    @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));
    }
🎜🎜🎜Lesesperre 🎜🎜🎜rrreee🎜🎜🎜 Schreibsperre🎜 🎜🎜rrreee🎜Stellt endlich eine 🎜Strategy Factory🎜Initialisierungssperrstrategie bereit: 🎜rrreee🎜8. So verwenden Sie 🎜rrreee🎜Sie sind fertig, Sie sind erfolgreich fertig🎜🎜🎜🎜🎜Für weitere Programmierkenntnisse , besuchen Sie bitte: 🎜Programmiervideo🎜! ! 🎜

Das obige ist der detaillierte Inhalt vonLassen Sie uns darüber sprechen, wie Sie verteilte Sperren in Redis implementieren, die fast alle Sperrszenarien unterstützen.. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen