Implemente bloqueos distribuidos utilizando anotaciones personalizadas basadas en Redisson
1. Principio de implementación
-
Basado en Redisson;
-
Implementada una anotación, el código es el siguiente:
@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; }
-
Implementado un aspecto. Cuando un método usa la anotación @DistributedLock, adquirirá el bloqueo distribuido antes de ejecutar el método. El código se muestra a continuación:
@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); } } }
-
En esencia, es
lock.tryLock(distributedLock.acquireTimeoutMillis(),distributedLock.expireMillis(),TimeUnit.MILLISECONDS);
un candado distribuido adquirido mediante; -
Debido a
distributedLock.acquireTimeoutMillis()
que no lo es0
, si un hilo no obtiene el bloqueo, continuará esperando el tiempo especificado y continuará intentando adquirir el bloqueo durante el período; el valor predeterminado actual es ese, ya que generalmente no tomadistributedLock.acquireTimeoutMillis()
tanto10s
. Si lleva mucho tiempo ejecutar la lógica empresarial, un hilo normalmente no esperará mucho tiempo10s
, que se utiliza principalmente para el resultado final;
el tiempo de vencimiento del bloqueo esdistributedLock.expireMillis()
actualmente el predeterminado8s
, porque generalmente no lleva tanto tiempo ejecutar el negocio. lógica, por lo que en circunstancias normales un hilo no retendrá el bloqueo durante mucho tiempo8s
, principalmente para el resultado final;
se utiliza paraSPEL
obtenerlockName
;
2. Cómo utilizar
-
En un método de cualquier capa, agregue
@DistributedLock(name = "XXX")即可对"XXX"
un candado; -
Ejemplo 1:
@DistributedLock(name = "'get'+#codes") public void get(String codes) { List<String> codeList = Arrays.stream(orgCodes.split(",")).collect(Collectors.toList()); ext(codeList); }
Si el parámetro de entrada del método anterior
codes
es111,222,333,444
, entoncesSPEL
el analizadolockName
esget111,222,333,444
; -
Ejemplo 2:
@DistributedLock(name = "'attendance_info'+#attendanceInfo.orderId+#attendanceInfo.date") public Integer insertDedup(ConstructionAttendanceInfo attendanceInfo, AtomicInteger dmlType) { // 此处省略 return 0; }
Si el parámetro de entrada del método anterior
attendanceInfo
esorderId
,aaa
esdate
,2023-08-01
entonces elSPEL
analizadolockName
esattendance_infoaaa2023-08-01
;
3. Precauciones
Cuando se utiliza, name
se debe especificar name = "'<方法名/唯一标识名>'+#<入参/入参的某个字段>"
para evitar diferentes métodos para generar lo mismo lockName
;