Implémentez des verrous distribués à l'aide d'annotations personnalisées basées sur Redisson
1. Principe de mise en œuvre
-
Basé sur Redisson;
-
Implémentation d'une annotation, le code est le suivant :
@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; }
-
Implémentation d'un aspect. Lorsqu'une méthode utilise l'annotation @DistributedLock, elle acquerra le verrou distribué avant d'exécuter la méthode. le code s'affiche comme ci-dessous :
@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); } } }
-
Essentiellement, il s’agit
lock.tryLock(distributedLock.acquireTimeoutMillis(),distributedLock.expireMillis(),TimeUnit.MILLISECONDS);
d’un verrou distribué acquis via ; -
Parce que
distributedLock.acquireTimeoutMillis()
ce n'est pas le cas0
, si un thread n'obtient pas le verrou, il continuera à attendre pendant le temps spécifié et continuera à essayer d'acquérir le verrou pendant la période ; la valeur par défaut actuelle est celle-ci, puisqu'il ne le prend généralementdistributedLock.acquireTimeoutMillis()
pas10s
. long pour exécuter la logique métier, un thread n'attend normalement pas longtemps10s
, ce qui est principalement utilisé pour le résultat net ;
le délai d'expiration du verrou estdistributedLock.expireMillis()
actuellement la valeur par défaut8s
, car l'exécution de l'activité ne prend généralement pas autant de temps Logique, donc dans des circonstances normales, un thread ne tiendra pas le verrou pendant une longue période8s
, principalement pour le résultat final ;
utiliser pourSPEL
obtenirlockName
;
2. Comment utiliser
-
Sur une méthode de n’importe quel calque, ajoutez
@DistributedLock(name = "XXX")即可对"XXX"
un verrou ; -
Exemple 1:
@DistributedLock(name = "'get'+#codes") public void get(String codes) { List<String> codeList = Arrays.stream(orgCodes.split(",")).collect(Collectors.toList()); ext(codeList); }
Si le paramètre d'entrée de la méthode ci-dessus
codes
est111,222,333,444
, alorsSPEL
celui analysélockName
estget111,222,333,444
; -
Exemple 2 :
@DistributedLock(name = "'attendance_info'+#attendanceInfo.orderId+#attendanceInfo.date") public Integer insertDedup(ConstructionAttendanceInfo attendanceInfo, AtomicInteger dmlType) { // 此处省略 return 0; }
Si le paramètre d'entrée de la méthode ci-dessus
attendanceInfo
estorderId
,aaa
estdate
,2023-08-01
alors celuiSPEL
analysélockName
estattendance_infoaaa2023-08-01
;
3. Précautions
Lorsqu'il est utilisé, name
il doit être spécifié name = "'<方法名/唯一标识名>'+#<入参/入参的某个字段>"
pour éviter que différentes méthodes ne génèrent le même lockName
;