在分布式系统以及高并发的项目中经常会遇到数据处理顺序被替换或者重复操作问题,为解决该问题引入“幂等”的概念。
1、数据库层面
在数据库层面防止重复操作可以引入一个 version 字段作为标记位,当更新数据操作时判断标记位和线程持有的是否一致,一致则允许修改,不一致则提示错误。
同理在系统中也可以使用状态位进行判断,一个需要审核的新闻其状态假如有编辑中、待审核、发布中、审核通过四种状态,设定是需要将新闻发布到另一个系统,另一个系统接收到该新闻则返回回执状态改为审核通过,其中发布状态在更改为发布中和审核中的时候是经过了其他系统其步骤会导致"发布中"状态覆盖"审核通过"状态,这里可以做状态设定,设定当状态更新为"发布中"状态时条件是状态"待审核",这样可以保证状态变更的线性。
2、接口层面
在接口层面的做法是在操作前对操作流程赋予一个token值,这个token值存入数据库,有一定的失效时间(具体需要看业务),当需要提交该操作流程时候带上该token进行验证,如果Redis中存在则逻辑继续,不存在则返回验证失败流程结束。
这可以避免重复操作和和设定流程超时。
基础代码实现(只记录关键代码)
public class IdemTokenServiceImpl implements IdemTokenService {
@Autowired
private RedisService redisService;
@Override
public AjaxResult createToken(long time) {
String token = UUID.randomUUID().toString().replace("-","");
//生成token存入Redis中
redisService.set(token,token,time);
//返回正确的结果信息
AjaxResult ajaxResult = new AjaxResult(0, token, null);
return ajaxResult;
}
@Override
public AjaxResult checkToken(HttpServletRequest request) throws IdemTokenException{
//从请求头中获取token
String token=request.getHeader("idemToken");
if (StringUtils.isBlank(token)){
//如果请求头token为空就从参数中获取
token=request.getParameter("idemToken");
//如果都为空抛出参数异常的错误
if (StringUtils.isBlank(token)){
throw new IdemTokenException(ResponseCodeEnums.ILLEGAL_ARGUMENT.getCode().toString(), ResponseCodeEnums.ILLEGAL_ARGUMENT.getMsg());
}
}
//如果redis中不包含该token,说明token已经被删除了,抛出请求重复异常
if (!redisService.exists(token)){
throw new IdemTokenException(ResponseCodeEnums.REPETITIVE_OPERATION.getCode().toString(),ResponseCodeEnums.REPETITIVE_OPERATION.getMsg());
}
//删除token
Boolean del = redisService.remove(token);
//如果删除不成功(已经被其他请求删除),抛出请求重复异常
if (!del){
throw new IdemTokenException(ResponseCodeEnums.REPETITIVE_OPERATION.getCode().toString(),ResponseCodeEnums.REPETITIVE_OPERATION.getMsg());
}
return new AjaxResult(0,"校验成功",null);
}
}
public class ApiIdempotentInterceptor implements HandlerInterceptor {
@Autowired
private IdemTokenService idemTokenService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
ApiIdempotent methodAnnotation = method.getAnnotation(ApiIdempotent.class);
if (methodAnnotation != null){
// 校验通过放行,校验不通过全局异常捕获后输出返回结果
idemTokenService.checkToken(request);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}