一句话总结
就是加了AOP+自定义注解,业务结合下;分布式锁么? 首选Redis
背景和问题
XXX交易平台日均处理用户订单请求超百万次,但是存在以下的痛点:
- 重复请求激增
- 系统资源浪费
- 数据不一致的风险
如何判定两次接口就是重复的?
- 合适的间隔时间(可以适当加入50ms缓冲时间)
- 参数对比
整体架构设计
使用共享缓存Redis
代码
自定义注解RequestLock,实现可配置
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestLock {
String prefix() default "";
long expire() default 500;
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
String delimiter() default "#";
String errorMessage() default "";
}
自定义RequestParam,作用于核心业务字段,拼接唯一key
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestKeyParam {
}
自定义AOP切面RequestLockAspect
@Aspect
@Component
@Slf4j
public class RedisRequestLockAspect {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Around("@annotation(com.xx.RequestLock) && execution(public * * (..))")
public Object around(ProceedingJoinPoint joinPoint) {
Object result = null;
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
RequestLock requestLock = method.getAnnotation(RequestLock.class);
if (requestLock != null) {
final String lockKey = getLockKey(joinPoint);
final Boolean isSuccess = stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
return connection.set(
lockKey.getBytes(),
"1".getBytes(),
Expiration.from(requestLock.expire()+50, requestLock.timeUnit()),
RedisStringCommands.SetOption.ifAbsent());//setnx
});
if (!isSuccess) {
throw new RunTimeException("BIZ-002", requestLock.errorMessage()); // 这里走自定义注解中的errorMessage
}
}
try {
result = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
throw new RunTimeException("SYS-001", "系统异常:" + e.getCause());
}
return result;
}
/**
* 获取唯一的key
* @param joinPoint
* @return
*/
private String getLockKey(ProceedingJoinPoint joinPoint) {
// 伪代码: 获取唯一的lockKey
}
}
RedisCallback回调获取(核心代码)
@SpringBootTest
public class RedisCallbackTest {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Test
public void test1(){
Boolean isSuccess = stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
return connection.set(
"callback#k1".getBytes(),
"v1".getBytes(),
Expiration.from(10L, TimeUnit.SECONDS),
RedisStringCommands.SetOption.ifAbsent());//setnx
});
System.out.println(isSuccess);
}
}