架构设计 | 接口幂等性原则,防重复提交Token管理
一、幂等性概念
1、幂等简介
编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。就是说,一次和多次请求某一个资源会产生同样的作用影响。
2、HTTP请求
遵循Http协议的请求,越来越强调Rest请求风格,可以更好的规范和理解接口的设计。
GET:用于获取资源,不应有副作用,所以是幂等的;
POST:用于创建资源,重复提交POST请求可能产生两个不同的资源,有副作用不满足幂等性;
PUT:用于更新操作,重复提交PUT请求只会对其URL中指定的资源有副作用,满足幂等性;
DELETE:用于删除资源,有副作用,但它应该满足幂等性;
HEAD:和GET本质是一样的,但HEAD不含有呈现数据,仅是HTTP头信息,没有副作用,满足幂等性;
OPTIONS:用于获取当前URL所支持的请求方法,满足幂等性;
二、场景业务分析
1、订单支付
实际开发中,经常会面对订单支付问题,基本流程如下:
- 客户端发起订单支付请求 ;
- 支付前系统本地相关业务处理 ;
- 请求第三方支付服务执行扣款;
- 第三方支付返回处理结果;
- 本地服务基于支付结果响应客户端;
该业务流程中要处理相当复杂的问题,比如事务,分布式事务,接口延迟超时,客户端重复提交等等,这里只基于幂等接口角度来看该流程,其他问题后续再聊。
2、幂等接口
当上述流程的支付请求有明确结果的时候:失败或成功,这样业务流程都好处理,但是例如支付场景如果请求超时,如何判断服务的结果状态:客户端请求超时,本地服务超时,请求支付超时,支付回调超时,客户端响应超时等等。
这就需要设计流程化的状态管理。
3、基础操作案例
模拟管理上述流程,设计幂等接口:
表结构设计
CREATE TABLE `dp_order_state` ( `order_id` BIGINT (20) NOT NULL AUTO_INCREMENT COMMENT '订单id', `token_id` VARCHAR (50) DEFAULT NULL COMMENT '防重复提交', `state` INT (1) DEFAULT '1' COMMENT '1创建订单,2本地业务,3支付业务', PRIMARY KEY (`order_id`) ) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '订单状态表'; CREATE TABLE `dp_state_record` ( `id` INT (11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `order_id` BIGINT (20) NOT NULL COMMENT '订单id', `state_dec` VARCHAR (50) DEFAULT NULL COMMENT '状态描述', PRIMARY KEY (`id`) ) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '状态记录表';
模拟业务流程
将订单创建,本地业务,支付业务,分开分段管理提交。分阶段测试异常熔断的业务。
@Service public class OrderServiceImpl implements OrderService { @Resource private OrderStateMapper orderStateMapper ; @Resource private StateRecordMapper stateRecordMapper ; @Override public OrderState queryOrder(OrderState orderState) { Map<String,Object> paramMap = new HashMap<>() ; paramMap.put("order_id",orderState.getOrderId()); List<OrderState> orderStateList = orderStateMapper.selectByMap(paramMap); if (orderStateList != null && orderStateList.size()>0){ return orderStateList.get(0) ; } return null ; } @Override public boolean createOrder(OrderState orderState) { int saveRes = orderStateMapper.insert(orderState); if (saveRes > 0){ saveStateRecord(orderState.getOrderId(),"订单创建成功"); } return saveRes > 0 ; } @Override public boolean localBiz(OrderState orderState) { orderState.setState(2); int updateRes = orderStateMapper.updateState(orderState) ; if (updateRes > 0){ saveStateRecord(orderState.getOrderId(),"本地业务成功"); } return updateRes > 0; } @Override public boolean paymentBiz(OrderState orderState) { orderState.setState(3); int updateRes = orderStateMapper.updateState(orderState) ; if (updateRes > 0){ saveStateRecord(orderState.getOrderId(),"支付业务成功"); } return updateRes > 0; } private void saveStateRecord (Long orderId,String stateDec){ StateRecord stateRecord = new StateRecord() ; stateRecord.setOrderId(orderId); stateRecord.setStateDec(stateDec); stateRecordMapper.insert(stateRecord) ; } }
测试接口
根据订单状态,分段补偿执行未完成的业务,如果该订单已经完成,多次提交不影响最终结果。
@Api(value = "OrderController") @RestController public class OrderController { @Resource private OrderService orderService ; @PostMapping("/submitOrder") public String submitOrder (OrderState orderState){ OrderState orderState01 = orderService.queryOrder(orderState) ; if (orderState01 == null){ // 正常业务流程 orderService.createOrder(orderState) ; orderService.localBiz(orderState) ; orderService.paymentBiz(orderState) ; } else { switch (orderState01.getState()){ case 1: // 订单创建成功:后推执行本地和支付业务 orderService.localBiz(orderState01) ; orderService.paymentBiz(orderState01) ; break ; case 2: // 订单本地业务成功:后推执行支付业务 orderService.paymentBiz(orderState01) ; break ; default: break ; } } return "success" ; } }
絮叨一句
:实际开发中,该流程是不会由页面多次提交完成,订单是不能重复提交的,下面会演示如何控制,这里业务是执行后推到完成,也可能业务向前清理,把整个流程置为失败,这里涉及关键状态判断,要选取一个状态作为成功或失败的标识,判断后续操作流程。在分布式系统中这种复杂流程最难处理的是分布式事务,最终一致性问题,后续再聊。
三、接口重复提交
1、表单重复提交
在实际情况中,接口如果处理时间过长,用户可能会点击多次提交按钮,导致数据重复。
常见的一个解决方案:在表单提交中隐藏一个token_id参数,一起提交到接口服务中,数据库存储订单和关联的tokenId,如果多次提交,直接返回页面提示信息即可。
2、演示案例
订单关联Token查询
@Service public class OrderServiceImpl implements OrderService { @Override public Boolean queryToken(OrderState orderState) { Map<String,Object> paramMap = new HashMap<>() ; paramMap.put("order_id",orderState.getOrderId()); paramMap.put("token_id",orderState.getTokenId()); List<OrderState> orderStateList = orderStateMapper.selectByMap(paramMap); return orderStateList.size() > 0 ; } }
测试接口
@RestController public class OrderController { @Resource private OrderService orderService ; @PostMapping("/repeatSub") public String repeatSub (OrderState orderState){ boolean flag = orderService.queryToken(orderState) ; if (flag){ return "请勿重复提交订单" ; } return "success" ; } }
四、源代码地址
GitHub·地址 https://github.com/cicadasmile/data-manage-parent GitEE·地址 https://gitee.com/cicadasmile/data-manage-parent

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
阿里、百度、美团都在用的‘高并发系统设计’;秒杀、抢红包、12306抢票等高并发场景难点
"秒杀活动"、"抢红包"、"微博热搜"、"12306抢票"、"共享单车拉新"等都是高并发的典型业务场景,那么如何解决这些业务场景背后的难点问题呢? 秒杀系统中,QPS达到10万/s时,如何定位并解决业务瓶颈? 明星婚恋话题不断弓|爆微博热搜,如何确保系统不宕机? 共享单车充值活动,如何保证不超卖? ...... 同一时间、海量用户的高频访问对任何平台都是难题,但可喜的是,虽然业务场景不同,设计和优化的思想却是万变不离宗。如果你掌握了高并发系统设计的核心技术点(缓存、池化、异步化、负载均衡、队列、降级熔断等),深化成自 己的知识体系,解决这些业务问题将不在话下,应对自如。 在小编看来,不少技术能力极强的工程师依旧会被"高并发"所困,这与知识储备不足,无法系统化地掌握核心技术有很大关系。技术人要不断汲取新的营养,更要能将技术知识应用到实际业务中,这样才能提升竞争力,突破职场瓶颈。 高并发系统设计知识框架图 说明:文章限于篇幅,故只做部分展示,完整的《高并发系统设计》文档小编已经整理好了,正在学习高并发或者想把这份文档当做练习题复习一下的朋友,可以关注微信公众号:慕容千语 基础 一起了解高并...
- 下一篇
阿里面试官必问的12个MySQL数据库基础知识,哪些你还不知道?
数据库基础知识 为什么要使用数据库 什么是SQL? 什么是MySQL? 数据库三大范式是什么 mysql有关权限的表都有哪几个 MySQL的binlog有有几种录入格式?分别有什么区别? 数据类型 mysql有哪些数据类型 引擎 MySQL存储引擎MyISAM与InnoDB区别 MyISAM索引与InnoDB索引的区别? InnoDB引擎的4大特性 存储引擎选择 数据库基础知识 为什么要使用数据库 (1)数据保存在内存 优点: 存取速度快 缺点: 数据不能永久保存 (2)数据保存在文件 优点: 数据永久保存 缺点: 1)速度比内存操作慢,频繁的IO操作。 2)查询数据不方便 (3)数据保存在数据库 1)数据永久保存 2)使用SQL语句,查询方便效率高。 3)管理数据方便 什么是SQL? 结构化查询语言(Structured Query Language)简称SQL,是一种数据库查询语言。 作用:用于存取数据、查询、更新和管理关系数据库系统。 什么是MySQL? MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关...
相关文章
文章评论
共有0条评论来说两句吧...