内容简介:之前写过一篇幂等的文章,但是仅仅只是思路,在生产环境肯定还是不能够用的。恰巧同事最近在讨论如何做幂等。那么如何设计幂等能够达到生产可用呢?请看下面代码:那么大家看到如上接口设计,会发现当执行process方法时执行幂等且还需要 confim一次,同时可以调用cancel取消幂等。为什么,因为如果在方法执行之前幂等了后续方法执行失败则出大事了。所以需要confirm、cancel两个方法保证最终成功和失败(画外音:此类设计有点TCC事务有木有?)第一次请求幂等失败手动取消幂等,第二次幂等成功
之前写过一篇幂等的文章,但是仅仅只是思路,在生产环境肯定还是不能够用的。恰巧同事最近在讨论如何做幂等。那么如何设计幂等能够达到生产可用呢?请看下面代码:
幂等处理接口:
public interface IdempotentProcessor { /** * 处理幂等执行代码块 * * @param optType the opt type * @param optId the opt id * @param initStatus the init status * @param expireMs the expire ms * @return the status enum */ StatusEnum process(String optType, String optId, StatusEnum initStatus, long expireMs); /** * 提交幂等 * @param optType the opt type * @param optId the opt id * @return the boolean */ boolean confirm(String optType, String optId); /** * * 取消幂等 * @param optType the opt type * @param optId the opt id * @return the boolean */ boolean cancel(String optType, String optId); } 复制代码
那么大家看到如上接口设计,会发现当执行process方法时执行幂等且还需要 confim一次,同时可以调用cancel取消幂等。为什么,因为如果在方法执行之前幂等了后续方法执行失败则出大事了。所以需要confirm、cancel两个方法保证最终成功和失败(画外音:此类设计有点TCC事务有木有?)
幂等实现类
public class ItemProcessor implements IdempotentProcessor { private static final String CACHE_KEY = "dew:idempotent:item:"; @Override public StatusEnum process(String optType, String optId, StatusEnum initStatus, long expireMs) { if (Dew.cluster.cache.setnx(CACHE_KEY + optType + ":" + optId, initStatus.toString(), expireMs / 1000)) { // 设置成功,表示之前不存在 return StatusEnum.NOT_EXIST; } else { // 设置不成功,表示之前存在,返回存在的值 String status = Dew.cluster.cache.get(CACHE_KEY + optType + ":" + optId); if (status == null || status.isEmpty()) { // 设置成功,表示之前不存在 return StatusEnum.NOT_EXIST; } else { return StatusEnum.valueOf(status); } } } @Override public boolean confirm(String optType, String optId) { long ttl = Dew.cluster.cache.ttl(CACHE_KEY + optType + ":" + optId); if (ttl > 0) { Dew.cluster.cache.setex(CACHE_KEY + optType + ":" + optId, StatusEnum.CONFIRMED.toString(), ttl); } return true; } @Override public boolean cancel(String optType, String optId) { Dew.cluster.cache.del(CACHE_KEY + optType + ":" + optId); return true; } } 复制代码
幂等拦截器
public class IdempotentHandlerInterceptor extends HandlerInterceptorAdapter { private DewIdempotentConfig dewIdempotentConfig; /** * Instantiates a new Idempotent handler interceptor. * * @param dewIdempotentConfig the dew idempotent config */ public IdempotentHandlerInterceptor(DewIdempotentConfig dewIdempotentConfig) { this.dewIdempotentConfig = dewIdempotentConfig; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Idempotent idempotent = ((HandlerMethod) handler).getMethod().getAnnotation(Idempotent.class); if (idempotent == null) { return super.preHandle(request, response, handler); } // 参数设置 String optType = "[" + request.getMethod() + "]" + Dew.Info.name + "/" + request.getRequestURI(); String optIdFlag = StringUtils.isEmpty(idempotent.optIdFlag()) ? dewIdempotentConfig.getDefaultOptIdFlag() : idempotent.optIdFlag(); String optId = request.getHeader(optIdFlag); if (StringUtils.isEmpty(optId)) { optId = request.getParameter(optIdFlag); } if (StringUtils.isEmpty(optId)) { // optId不存在,表示忽略幂等检查,强制执行 return super.preHandle(request, response, handler); } if (!DewIdempotent.existOptTypeInfo(optType)) { long expireMs = idempotent.expireMs() == -1 ? dewIdempotentConfig.getDefaultExpireMs() : idempotent.expireMs(); boolean needConfirm = idempotent.needConfirm(); StrategyEnum strategy = idempotent.strategy() == StrategyEnum.AUTO ? dewIdempotentConfig.getDefaultStrategy() : idempotent.strategy(); DewIdempotent.initOptTypeInfo(optType, needConfirm, expireMs, strategy); } switch (DewIdempotent.process(optType, optId)) { case NOT_EXIST: return super.preHandle(request, response, handler); case UN_CONFIRM: ErrorController.error(request, response, 409, "The last operation was still going on, please wait.", IdempotentException.class.getName()); return false; case CONFIRMED: ErrorController.error(request, response, 423, "Resources have been processed, can't repeat the request.", IdempotentException.class.getName()); return false; default: return false; } } } 复制代码
测试用例
测试场景一
第一次请求幂等失败手动取消幂等,第二次幂等成功
代码:
@GetMapping(value = "manual-confirm") @Idempotent(expireMs = 5000) public Resp<String> testManualConfirm(@RequestParam("str") String str) { try { if ("dew-test1".equals(str)){ throw new RuntimeException("处理幂等失败"); } DewIdempotent.confirm(); } catch (Exception e) { DewIdempotent.cancel(); return Resp.serverError(str + "处理幂等失败"); } return Resp.success(str); } 复制代码
@Test public void testConfirm() throws IOException, InterruptedException { //幂等唯一键值 // HashMap<String, String> header = new HashMap<String, String>() { { put(DewIdempotentConfig.DEFAULT_OPT_ID_FLAG, "0001"); } }; //字符串等于dew-test1幂等失败 Resp<String> error = Resp.generic($.http.get(urlPre + "manual-confirm?str=dew-test1", header), String.class); System.out.println("幂等失败 [errorCode=" + error.getCode() + "-errorMsg=" + error.getMessage() + "]"); //字符串等于dew-test2幂等成功 Resp<String> success = Resp.generic($.http.get(urlPre + "manual-confirm?str=dew-test2", header), String.class); System.out.println("幂等成功 [code=" + success.getCode() + "-body=" + success.getBody() + "]"); } 复制代码
结果:
幂等失败 [errorCode=500-errorMsg=dew-test1处理幂等失败] 幂等成功 [code=200-body=dew-test2] 复制代码
测试场景二
第一次请求幂等成功手动提交confim,第二次幂等失败
代码:
@GetMapping(value = "manual-confirm") @Idempotent(expireMs = 5000) public Resp<String> testManualConfirm(@RequestParam("str") String str) { try { DewIdempotent.confirm(); } catch (Exception e) { DewIdempotent.cancel(); return Resp.serverError(str + "处理幂等失败"); } return Resp.success(str); } 复制代码
@Test public void testConfirm() throws IOException, InterruptedException { //幂等唯一键值 // HashMap<String, String> header = new HashMap<String, String>() { { put(DewIdempotentConfig.DEFAULT_OPT_ID_FLAG, "0001"); } }; //字符串等于dew-test1幂等成功 Resp<String> success = Resp.generic($.http.get(urlPre + "manual-confirm?str=dew-test1", header), String.class); System.out.println("幂等成功 [code=" + success.getCode() + "-body=" + success.getBody() + "]"); Thread.sleep(500); //字符串等于dew-test2幂等失败 Resp<String> error = Resp.generic($.http.get(urlPre + "manual-confirm?str=dew-test2", header), String.class); System.out.println("幂等失败 [errorCode=" + error.getCode() + "-errorMsg=" + error.getMessage() + "]"); } 复制代码
结果:
幂等成功 [code=200-body=dew-test1] 2019-04-23 00:14:17.123 ERROR 8600 --- [nio-8080-exec-2] ms.dew.core.web.error.ErrorController : Request [GET-/idempotent/manual-confirm] 169.254.156.20 , error 423 : Resources have been processed, can't repeat the request. 幂等失败 [errorCode=423-errorMsg=[]Resources have been processed, can't repeat the request.] 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Hadoop小文件解决方案-基于文件整合的解决方案
- Hadoop小文件解决方案-基于NameNode内存和MapReduce性能解决方案
- 跨域解决方案
- 异步解决方案---promise
- 前端一键打印解决方案
- MySQL 压缩解决方案(一)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。