1. 实现原理
-
基于Redisson;
-
实现了一个注解,代码如下:
@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
,date
为2023-08-01
,则经SPEL
解析的lockName
为attendance_infoaaa2023-08-01
;
3. 注意事项
使用时,name
应指定为name = "'<方法名/唯一标识名>'+#<入参/入参的某个字段>"
,避免不同的方法生成同样的lockName
;