一、什么是幂等性?
一个操作,不论执行多少次,产生的效果和返回的结果都是一样的
二、应用场景
- 一个订单创建接口,第一次调用超时了,然后又调用了一次
- 订单创建后需要去执行扣减库存等等的后续操作,第一次调用超时后又调用一次
- 支付订单,已经发送过支付请求,第一次超时后又调用一次
- 订单完成支付后,发送消息处理一系列后续请求,中间件消息被多个机器(分布式)或者进程执行
- 对外提供接口的api如何保证幂等
这些问题都是在单体架构转成微服务架构之后,带来的问题。除了查询接口,增加、更新、删除都要保证幂等性,如何保证?
三、解决方方案
1、 全局唯一ID
根据每一次的操作和内容生成一个全局ID(guid等),在执行操作之前判断这个唯一ID是否存在,决定是否继续执行。
缺点:只是一种通用方案,不可能所有操作都做唯一性判读,一个是效率问题,另一个是要考虑分布式以及上下游的问题,实现起来较困难。
另外就是:如果是针对某张表的唯一索引,会出现重复更新的情况,所以更多的是要结合数据库字段以及状态机去实现,
业务情景:
- 支付订单:生成唯一的支付凭证
- 对外提供接口的api
2、 token机制
业务情景:
- 防止订单重复提交,创建多个订单
业务要求:
页面数据只能被点击提交一次
发生原因:
重复点击、网络重发、nginx重发等原因
解决方法:
在数据提交之前,向服务端申请token,提交后后端校验token,并删除token
3、 数据库字段约束
业务情景:
- 更新订单状态
- 库存扣减
数据库字段约束主要用版本号控制,状态机约束,字段约束
字段约束主要是通过一些主键或者唯一索引去判断执行
插入:无法插入相同数据
更新:
- 悲观锁:
select * from table_xxx where id=’xxx’ for update;
版本号控制
更新:
乐观锁:
updateGoodsName(int id,String newName,int version);update goods set name=#{newName},version=#{version+1} where id=#{id} and version=${version}
状态机
设置固定的状态类型,只有同时满足该状态类型下的多个状态,才能执行
比如:
A:订单
待付款
待发货 contain 待发货
已发货 全部是已发货 已收货 退款 退货
已收货 全部已收货
退款中
退款成功
退款失败
退货中
退货成功
退货失败
已取消
B:订单(不存入库状态)
待付款
待发货(未确认、已确认)
已发货
已收货
退款中
退款成功
退款失败
退货中
退货成功
退货失败
已取消
参考链接:
高并发的核心技术-幂等的实现方案
接口设计的幂等性考虑
分布式高并发系统如何保证对外接口的幂等性?
如何绘画状态机来描述业务的变化 - 人人都是产品经理