什么是幂等性
就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了其他的结果
在 CRUD 这 4 个操作中,查询操作是天然幂等
的,删除操作也只会删除一次
,多次的删除操作结果还是一样,影响幂等的只有在添加和更新
着两个操作上面
在分布式场景 或者微服务架构场景下,以一个订单量流程来说明接口的幂等性问题
- 订单的创建接口,第一次调用超时,然后重试了一次
- 减库存操作超时,调用方重试了一次
- 扣钱操作重试了一次
- 订单状态更新接口,连续发送了两个消息,一个是已经创建,一个是已经付款,但是你先接受到已付款,然后收到已创建
- 订单支付完成之后,需要发送一条短信,一台机器收到短信后,处理慢,消息中间件又把消息投递给另外的一台机器处理
以上的问题,就是在单体架构转为 分布式 微服务等结构之后,幂等性的问题凸显。
说了这么多,那么如何解决幂等性的问题呢?
全局唯一ID
如果使用全局唯一ID(分布式锁),根据业务的操作和内容生成一个全局的ID,在执行操作之前先根据这个全局ID是否存在,来判断某个操作是否已经执行了。如果不存在就把这个全局ID
存储到 数据库 ,redis 等。如果存在表示方法已经执行了。
但是用 全局唯一ID
有个问题就是,在存储完某个 ID 之后,机器宕机了,客户端或者别的服务请求过来会响应超时,这种情况下 对于全局唯一ID
这个可能还需要加入集群做高可用等,反正全局性唯一ID
虽然可以一揽子都解决添加,修改
的幂等问题,但是实现起来也比较麻烦。
有几种特定场景下的 幂等性解决方案一起来看一下
去重表
这种方法适用于在业务中为唯一标的的插入场景
中,比如在以上的支付场景中,如果一个订单只会支付一次,所以订单的ID 可以作为唯一标识,这时,我们就可以创建一张去重表。并且把唯一的标识作为唯一索引,在实际实现的时候,可以把创建订单支付和写入去重表,放在一个事务中,如果重复创建,数据库就会抛出唯一约束异常,操作就会回滚。
插入或者更新
这个方法插入并且有唯一索引的情况,比如我们要关联商品品类,其中商品的ID 和品类的ID可以构成唯一索引,并且在数据库中也增加了唯一索引,这时就可以使用 insert 或者 update 操作了。
多版本控制
这种方式适合在更新的场景中,比如我们要更新商品的名字,这个时候我们就可以在更新的接口中增加一个版本号
boolean updateGoodsName(int id,String newName,int version);
实现时可以如下
update goods set name = #{newName},version = #{version} where id = #{id} and version<${version}
状态机控制
这种方法适合在右状态机流转的情况下,比如就会订单的创建和付款,订单的付款肯定是之前。这时可以通过设计状态字段,使用 int 类型。并且通过值的大小来做幂等。比如订单的创建为 0 。付款成功为 100,付款失败为 99
在做状态更新的时候,我们就可以这样控制
update order set status = #{status} where id = #{id} and status < #{status}
以上就是一个保证接口幂等性的一些方法
最新更新
insert
的时候我们的业务还没有唯一的单号,需要使用token
来保证幂等- 混合操作(CRUD) ,这个情况找唯一的业务单号,如果有就用分布式锁,如果没有就需要
token
保证幂等 delete
删掉操作的时候,如果是通过主键删除的,那么你不管操作多少次都是一样的,不会对业务数据有什么影响;但是如果删除的条件不是主键,比如说 根据业务状态删除status
status = 0 的时候表示业务数据无效,需要删除,当你删除一部分status =0 的数据后,又有 status = 0 的数据,如果在前端操作的时候点击了多次,那么这两次的操作是否需要幂等要结合实际的业务情况来解决,如果后面出现的 status =0 的数据是需要删除的,那么也就意味着这种情况下的 delete 也是需要幂等的,所以这种情况可以采用 token 的方式来保证幂等。- update 操作 如果有业务单号可以使用版本号 version,如果没有业务单号 可以使用
token
update order set xxx= #{aaa} ,version = version+1 where id = #{bbb} and version = #{version}
https://www.cnblogs.com/jack87224088/p/8688948.html 原文链接