ホームページ > データベース > Redis > ほぼすべてのロック シナリオをサポートする分散ロックを Redis に実装する方法について説明します。

ほぼすべてのロック シナリオをサポートする分散ロックを Redis に実装する方法について説明します。

青灯夜游
リリース: 2021-11-02 11:02:41
転載
1813 人が閲覧しました

この記事ではRedis分散ロックの実装方法を紹介しますので、お役に立てれば幸いです。

ほぼすべてのロック シナリオをサポートする分散ロックを Redis に実装する方法について説明します。

みなさん、こんにちは。今日は、redisson によって実装されたマルチタイプのロックを共有します。これは、ほぼすべてのロック シナリオをサポートし、小規模なロック シナリオもサポートする redis 分散ロックの実装です。 MQ および Redis のさまざまなデータ操作。 [関連する推奨事項: Redis ビデオ チュートリアル ]

理論部分

前の記事では、Redis を介して分散ロックを実装する 2 つの方法を紹介しました。メソッドは次のとおりです:

  • redis に付属のコマンド経由: setNX

  • Redis クライアント経由: redisson

著者は、redisson クライアントの使用を推奨しています。これは、redisson が、interlock、red lock、read-write lock、fair lock などのより多くのロック タイプをサポートしているためです。 また、redisson の実装は、より多くの種類のロックをサポートしているためです。シンプルです。開発者は、対応する API を呼び出すだけでよく、基礎となるロック プロセスやロック解除の 原子性 の問題について心配する必要はありません。

Redis 分散ロックでは、redisson によるさまざまなロック タイプの簡単な実装、つまりプログラムによる実装がリストされています。このような実装は、日々の開発ニーズを完全に満たすことができますが、欠点も明らかです。

例:

  • 埋め込まれたコードが多すぎて、十分にエレガントではありません
  • コードが重複しています
  • ロック パラメータの使用は直感的ではありません
  • ロック解除の手順を忘れがちです

Spring を使用した学生は、@Transactional アノテーションを理解している必要があります。Spring はプログラムによるトランザクションとアノテーション付き (宣言的) トランザクションの両方をサポートします。

そのような実装も参照できますか?

答えは「まったく問題ありません!」です。

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. ロック戦略の実装

最初にロック戦略の

抽象基本クラスを定義します

(interface# を使用することもできます) ##) :

/**
 * 锁策略抽象基类
 */
@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();
        }
    }
    ログイン後にコピー
  • ついに、Strategy Factory初期化ロック戦略を提供します:
  • /**
     * 锁的策略工厂
     */
    @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 に実装する方法について説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:juejin.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート