溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Redis中如何實(shí)現(xiàn)支持幾乎所有加鎖場(chǎng)景的分布式鎖

發(fā)布時(shí)間:2021-11-02 11:34:59 來(lái)源:億速云 閱讀:147 作者:小新 欄目:關(guān)系型數(shù)據(jù)庫(kù)

小編給大家分享一下Redis中如何實(shí)現(xiàn)支持幾乎所有加鎖場(chǎng)景的分布式鎖,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

實(shí)戰(zhàn)部分

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 {

    /**
     * 鎖的模式:如果不設(shè)置自動(dòng)模式,當(dāng)參數(shù)只有一個(gè).使用 REENTRANT 參數(shù)多個(gè) MULTIPLE
     */
    LockModel lockModel() default LockModel.AUTO;

    /**
     * 如果keys有多個(gè),如果不設(shè)置,則使用 聯(lián)鎖
     *
     * @return
     */
    String[] keys() default {};

    /**
     * key的靜態(tài)常量:當(dāng)key的spel的值是LIST、數(shù)組時(shí)使用+號(hào)連接將會(huì)被spel認(rèn)為這個(gè)變量是個(gè)字符串,只能產(chǎn)生一把鎖,達(dá)不到我們的目的,
     * 而我們?nèi)绻中枰粋€(gè)常量的話。這個(gè)參數(shù)將會(huì)在拼接在每個(gè)元素的后面
     *
     * @return
     */
    String keyConstant() default "";

    /**
     * 鎖超時(shí)時(shí)間,默認(rèn)30000毫秒(可在配置文件全局設(shè)置)
     *
     * @return
     */
    long watchDogTimeout() default 30000;

    /**
     * 等待加鎖超時(shí)時(shí)間,默認(rèn)10000毫秒 -1 則表示一直等待(可在配置文件全局設(shè)置)
     *
     * @return
     */
    long attemptTimeout() default 10000;
}

3、常量類(lèi)

/**
 * Redisson常量類(lèi)
 */
public class RedissonConst {
    /**
     * redisson鎖默認(rèn)前綴
     */
    public static final String REDISSON_LOCK = "redisson:lock:";
    /**
     * spel表達(dá)式占位符
     */
    public static final String PLACE_HOLDER = "#";
}

4、枚舉

/**
 * 鎖的模式
 */
public enum LockModel {
    /**
     * 可重入鎖
     */
    REENTRANT,
    /**
     * 公平鎖
     */
    FAIR,
    /**
     * 聯(lián)鎖
     */
    MULTIPLE,
    /**
     * 紅鎖
     */
    RED_LOCK,
    /**
     * 讀鎖
     */
    READ,
    /**
     * 寫(xiě)鎖
     */
    WRITE,
    /**
     * 自動(dòng)模式,當(dāng)參數(shù)只有一個(gè)使用 REENTRANT 參數(shù)多個(gè) 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數(shù)組
        String[] keys = lock.keys();
        if (ArrayUtil.isEmpty(keys)) {
            throw new ReddissonException("redisson lock keys不能為空");
        }
        // 獲取方法的參數(shù)名
        String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) proceedingJoinPoint.getSignature()).getMethod());
        Object[] args = proceedingJoinPoint.getArgs();
        // 等待鎖的超時(shí)時(shí)間
        long attemptTimeout = lock.attemptTimeout();
        if (attemptTimeout == 0) {
            attemptTimeout = redissonProperties.getAttemptTimeout();
        }
        // 鎖超時(shí)時(shí)間
        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("參數(shù)有多個(gè),鎖模式為->" + lockModel.name() + ",無(wú)法匹配加鎖");
        }
        log.info("鎖模式->{},等待鎖定時(shí)間->{}毫秒,鎖定最長(zhǎng)時(shí)間->{}毫秒", lockModel.name(), attemptTimeout, lockWatchdogTimeout);
        boolean res = false;
        // 策略模式獲取redisson鎖對(duì)象
        RLock rLock = lockStrategyFactory.createLock(lockModel, keys, parameterNames, args, lock.keyConstant(), redissonClient);
        //執(zhí)行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();
        // 自動(dòng)模式:優(yōu)先匹配全局配置,再判斷用紅鎖還是可重入鎖
        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;
    }
}

這里使用了策略模式來(lái)對(duì)不同的鎖類(lèi)型提供實(shí)現(xiàn)。

7、鎖策略的實(shí)現(xiàn)

先定義鎖策略的抽象基類(lèi)(也可以用接口):

/**
 * 鎖策略抽象基類(lèi)
 */
@Slf4j
abstract class LockStrategy {

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 創(chuàng)建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;
    }

    /**
     * 通過(guò)spring Spel 獲取參數(shù)
     *
     * @param key            定義的key值 以#開(kāi)頭 例如:#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("沒(méi)有使用spel表達(dá)式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表達(dá)式key={},value={}", key, keys);
        return keys;
    }
}

再提供各種鎖模式的具體實(shí)現(xiàn):

  • 可重入鎖:

/**
 * 可重入鎖策略
 */
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表達(dá)式是數(shù)組或者集合 則使用紅鎖
        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));
    }
}
  • 聯(lián)鎖

/**
 * 聯(lián)鎖策略
 */
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();
    }
}
  • 寫(xiě)鎖

/**
 * 寫(xiě)鎖策略
 */
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();
    }
}

最后提供一個(gè)策略工廠初始化鎖策略:

/**
 * 鎖的策略工廠
 */
@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("分頁(yè)列表")
    @GetMapping
    public ApiPageResult list(VendorProjectItemQuery query, Pagination pagination) {
        return ApiPageResult.success(pagination, vendorProjectItemService.list(query, pagination), vendorProjectItemService.count(query));
    }

以上是“Redis中如何實(shí)現(xiàn)支持幾乎所有加鎖場(chǎng)景的分布式鎖”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI