博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
生产级幂等解决方案
阅读量:6158 次
发布时间:2019-06-21

本文共 7642 字,大约阅读时间需要 25 分钟。

之前写过一篇幂等的文章,但是仅仅只是思路,在生产环境肯定还是不能够用的。恰巧同事最近在讨论如何做幂等。那么如何设计幂等能够达到生产可用呢?请看下面代码:

幂等处理接口:

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
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); }复制代码
@Testpublic void testConfirm() throws IOException, InterruptedException {    //幂等唯一键值    //    HashMap
header = new HashMap
() { { put(DewIdempotentConfig.DEFAULT_OPT_ID_FLAG, "0001"); } }; //字符串等于dew-test1幂等失败 Resp
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
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
testManualConfirm(@RequestParam("str") String str) { try { DewIdempotent.confirm(); } catch (Exception e) { DewIdempotent.cancel(); return Resp.serverError(str + "处理幂等失败"); } return Resp.success(str);}复制代码
@Testpublic void testConfirm() throws IOException, InterruptedException {    //幂等唯一键值    //    HashMap
header = new HashMap
() { { put(DewIdempotentConfig.DEFAULT_OPT_ID_FLAG, "0001"); } }; //字符串等于dew-test1幂等成功 Resp
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
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.]复制代码

最后

此代码及思路的项目名为dew且是微服务一站式解决方案,提供:架构指南、容器优先/兼容Spring Cloud与Istio的框架、最佳实践及Devops标准化流程。

源代码github地址:

各位朋友可以给这位大佬的项目star一波

转载地址:http://dflfa.baihongyu.com/

你可能感兴趣的文章
微信小程序输入框input
查看>>
MySql字符串函数使用技巧
查看>>
Doc2Vec,Word2Vec文本相似度 初体验。
查看>>
系统ghost后变成一个盘了别的分区的文件怎么找回
查看>>
Win7+Ubuntu11
查看>>
请问华为三层交换机里面的那个从IP是个什么意思? -
查看>>
kFeedback开源啦
查看>>
大数据传输,文件传输的专业解决方案!
查看>>
阿里云专家穆轩的《杭州九年程序员之“修炼”手册》
查看>>
JQuery:deferred对象的方法
查看>>
eyoucms问答 百度权重是什么
查看>>
win10中遇到qq视频时摄像头打不开没反应的解决方法
查看>>
介绍自己的一个Android插桩热修复框架项目QuickPatch
查看>>
关于textarea的ie9的maxlength不起作用的问题,请参考如下URL解决。
查看>>
Solr Facet 查询
查看>>
C++类的继承一
查看>>
数据库分库分表(sharding)系列(五) 一种支持自由规划无须数据迁移和修改路由代码的Sharding扩容方案...
查看>>
巧用VMware Workstation的clone来制作虚拟机模板
查看>>
Spring-Mybatis MapperScannerConfigurer 取不到PropertyPlaceholderConfigurer里的值
查看>>
HP DL380G4服务器前面板指示灯的含义
查看>>