Реализация распределенных блокировок с использованием пользовательских аннотаций на основе Redisson.
1. Принцип реализации
-
На основе Редиссона;
-
Реализована аннотация, код следующий:
@Target({ ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface DistributedLock { long TEN_THOUSAND = 10000L; long EIGHT_THOUSAND = 8000L; /** * 锁的名称 */ String name() default ""; /** * 锁的过期时间,单位ms,默认8000 */ long expireMillis() default EIGHT_THOUSAND; /** * 获取锁的超时时间,单位ms,默认10000 */ long acquireTimeoutMillis() default TEN_THOUSAND; }
-
Реализован аспект: когда метод использует аннотацию @DistributedLock, он получает распределенную блокировку перед выполнением метода. код показан ниже:
@Aspect @Component @Slf4j public class DistributedLockAspect { @Resource private RedissonClient redissonClient; @Pointcut("@annotation(com.xxx.xxx.xxx.xxx.DistributedLock)") public void myPointcut() { } @Around(value = "myPointcut()") public Object around(ProceedingJoinPoint pjp) throws Throwable { DistributedLock distributedLock = getAnnotation(pjp); String lockName = resolveKey(distributedLock.name(), pjp); RLock lock = redissonClient.getLock(lockName); try { Stopwatch stopwatch = Stopwatch.createStarted(); boolean success = lock.tryLock(distributedLock.acquireTimeoutMillis(), distributedLock.expireMillis(), TimeUnit.MILLISECONDS); LOGGER.info("tryLock, result:{}, cost:{}", success, stopwatch.stop().elapsed(TimeUnit.MILLISECONDS)); if (success) { LOGGER.info("获取锁成功,lockName={}", lockName); } else { LOGGER.info("获取锁失败,lockName={}", lockName); } return pjp.proceed(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); LOGGER.info("释放锁成功,lockName={}", lockName); } } } private DistributedLock getAnnotation(ProceedingJoinPoint pjp) { MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); return methodSignature.getMethod().getAnnotation(DistributedLock.class); } private String resolveKey(String key, ProceedingJoinPoint pjp) { if (StringUtils.isBlank(key)) { return pjp.getSignature().toLongString(); } else { StandardEvaluationContext context = new StandardEvaluationContext(); Object[] args = pjp.getArgs(); MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); String[] params = methodSignature.getParameterNames(); for (int i = 0; i < params.length; i++) { context.setVariable(params[i], args[i]); } return new SpelExpressionParser().parseExpression(key).getValue(context, String.class); } } }
-
По сути, это
lock.tryLock(distributedLock.acquireTimeoutMillis(),distributedLock.expireMillis(),TimeUnit.MILLISECONDS);
распределенная блокировка, полученная посредством; -
Поскольку
distributedLock.acquireTimeoutMillis()
это не так0
, то если поток не получит блокировку, он продолжит ждать указанное время и будет продолжать попытки получить блокировку в течение этого периода; текущее значение по умолчанию таково, поскольку обычноdistributedLock.acquireTimeoutMillis()
это10s
не требуется. долго выполнять бизнес-логику, поток обычно не будет ждать в течение длительного времени10s
, что в основном используется для получения прибыли;
время истечения срока действия блокировкиdistributedLock.expireMillis()
в настоящее время является значением по умолчанию8s
, потому что обычно выполнение бизнеса не занимает так много времени логика, поэтому при нормальных обстоятельствах поток не будет удерживать блокировку в течение длительного времени8s
, в основном для чистой прибыли;
используйте дляSPEL
полученияlockName
;
2. Как использовать
-
К методу любого слоя добавьте
@DistributedLock(name = "XXX")即可对"XXX"
блокировку; -
Пример 1:
@DistributedLock(name = "'get'+#codes") public void get(String codes) { List<String> codeList = Arrays.stream(orgCodes.split(",")).collect(Collectors.toList()); ext(codeList); }
Если входной параметр вышеуказанного метода
codes
равен111,222,333,444
, тоSPEL
анализируемый параметрlockName
равенget111,222,333,444
; -
Пример 2:
@DistributedLock(name = "'attendance_info'+#attendanceInfo.orderId+#attendanceInfo.date") public Integer insertDedup(ConstructionAttendanceInfo attendanceInfo, AtomicInteger dmlType) { // 此处省略 return 0; }
Если входной параметр вышеуказанного метода
attendanceInfo
равенorderId
,aaa
isdate
,2023-08-01
тоSPEL
анализируемый параметрlockName
равенattendance_infoaaa2023-08-01
;
3. Меры предосторожности
При использовании name
его следует указать name = "'<方法名/唯一标识名>'+#<入参/入参的某个字段>"
, чтобы избежать создания одного и того же lockName
;