這篇文章跟大家介紹一下Redis分散式鎖的實作方法,希望對大家有幫助!
Hello 大家好,今天跟大家分享redisson實現的多類型鎖定、支援幾乎所有加鎖場景的redis分散式鎖的實現,還支援小型MQ和redis的各種數據操作。 【相關推薦:Redis影片教學】
在先前的文章中,介紹了透過redis實現分佈鎖定的兩種方式,分別是:
透過redis自帶的指令:setNX
透過redis的客戶端:redisson
#作者更推薦使用redisson客戶端的方式,因為redisson支援更多的鎖類型,譬如聯鎖、紅鎖、讀寫鎖、公平鎖等,而且redisson的實現更加簡單,開發者只需要呼叫回應的API
即可,無需關心底層加鎖的過程和解鎖的原子性
問題。
在Redis分散式鎖定中,列出了redisson對於多種的鎖類型的簡單實現,即編程式實現。這樣的實現完全能夠滿足我們的日常開發需求,但是缺點也很明顯。
譬如:
使用過Spring的同學,肯定都知道@Transactional註解,Spring即支援編程式事務,也支援註解式(聲明式)事務。
我們是否也可以參考這樣的實作呢?
答案是:完全OK!
AOP
就是專門做這種事的。
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.16.2</version> </dependency>Copy to clipboardErrorCopied
/** * 分布式锁自定义注解 */ @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; }
/** * Redisson常量类 */ public class RedissonConst { /** * redisson锁默认前缀 */ public static final String REDISSON_LOCK = "redisson:lock:"; /** * spel表达式占位符 */ public static final String PLACE_HOLDER = "#"; }
/** * 锁的模式 */ public enum LockModel { /** * 可重入锁 */ REENTRANT, /** * 公平锁 */ FAIR, /** * 联锁 */ MULTIPLE, /** * 红锁 */ RED_LOCK, /** * 读锁 */ READ, /** * 写锁 */ WRITE, /** * 自动模式,当参数只有一个使用 REENTRANT 参数多个 RED_LOCK */ AUTO }
/** * 分布式锁异常 */ 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); } }
/** * 分布式锁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; } }
這裡使用了策略模式
來對不同的鎖定類型提供實作。
先定義鎖定策略的抽象基底類別(也可以用介面) :
/** * 锁策略抽象基类 */ @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中怎麼實現支援幾乎所有加鎖場景的分散式鎖的詳細內容。更多資訊請關注PHP中文網其他相關文章!