大家好,我是飘渺。
在DDD&微服务系列《并发与幂等实现方案》一文中我封装了一个公共的幂等组件,业务模块只需要加入pom依赖并在对应接口加上@Idemponent
注解即可使用封装好的幂等功能。
近期,有粉丝反馈在项目中使用Token机制的幂等方案时会遇到一个问题:如果业务参数校验失败,由于幂等Key被删除,就会导致后续请求无法正常提交。今天我们来详细说明一下这个问题以及解决方案。
在DailyMart中,基于Token的幂等方案包括以下步骤:
其完整的处理流程如下图所示:
在使用Token方案时会存在一个核心问题:是先完成业务操作后删除token,还是先删除token后执行业务操作呢?
先看第一种:
在高并发下,可能出现第一次访问时token存在,完成具体业务操作,但在还没有删除token时,客户端又携带token发起请求。此时,因为token还存在,第二次请求也会验证通过,执行具体业务操作。
对于这个问题有如下两种解决方案:
INCR
原子性特性,在获取token时对其进行自增操作。当客户端携带token访问执行业务代码时,继续对其进行自增,如果自增后的返回值为2,则是一个合法请求允许执行,否则认为是非法请求,直接返回。另一种方案是先删除token再执行业务,但这种方式也存在问题。如果业务执行超时或失败,没有向客户端返回明确结果,客户端就会进行重试,但此时之前的token已经被删除,导致被认为是重复请求,不再进行业务处理。
这种方案无需额外处理,一个token只能代表一次请求。一旦业务执行出现异常,则让客户端重新获取令牌,重新发起一次访问即可。
在DailyMart中采用的是先删除token后执行业务逻辑的方案。
无论是先删除token还是后删除token,都会导致每次业务请求都产生一个额外的请求去获取token。然而,在生产环境中,业务失败或超时的情况并不多见,大多数请求都能成功完成。因此,为了处理这少数失败的请求,让绝大多数请求都产生额外的请求也算是一种资源的浪费。
核心代码如下
1、在aop中通过注解确认幂等方案
@Around("pointcut()")
public Object idempotentHandler(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method targetMethod = methodSignature.getMethod();
Idempotent idempotent = targetMethod.getAnnotation(Idempotent.class);
// 支持多种模式,所以使用工厂模式确定使用哪种
IdempotentHandler instance = IdempotentHandlerFactory.getInstance(idempotent.type());
try {
instance.handler(idempotent, joinPoint);
return joinPoint.proceed();
} finally {
// 处理后置逻辑,比如关闭分布式锁
instance.afterProcess();
}
}
2、在具体handler中处理幂等实现逻辑,以token机制为例只需要直接删除token。
@Override
public void handler(Idempotent idempotent, ProceedingJoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String token = request.getHeader(TOKEN_KEY);
if (StrUtil.isBlank(token)) {
token = request.getParameter(TOKEN_KEY);
if (StrUtil.isBlank(token)) {
throw new IdempotentException(IDEMPOTENT_TOKEN_ERROR);
}
}
Boolean deleteFlag = distributedCache.delete(token);
// 如果redis中已经过期,需要提示重新操作
if (!deleteFlag) {
throw new IdempotentException(IDEMPOTENT_TOKEN_DELETE_ERROR);
}
}
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8