接口幂等性适用场景及设计方法
说明:
本文主要内容并非所原创,而是对网上已有文章的收集整理和自我
提炼总结,仅作学习笔记之用,如有冒犯,请联系本人删除。
1.幂等地定义 1.1数学定义 在数学里,幂等有两种主要的定义:
- 在某二元运算下,幂等元素是指被自己重复运算(或对于函数是为复合)的结果等于它自己的元素。如,乘法运算下,0和1符合的自乘运算符和幂等,即s*s=s
- 某一元运算为幂等的时,其作用在任一元素两次后会和其作用一次的结果相同。例如,高斯符号便是幂等的,即f(f(x))=f(x)
幂等:
update test_user set user_age = 25 where user_id = 2 ,这中情况无论执行多少次,结果都不受影响,所以是幂等的。
非幂等:
update test_user set user_times = user_times + 1 where user_id = 2, 这样的更新语句每执行一次,结果都会不一样,所以是非幂等的。
1.2.1Http规范定义 在HTTP/1.1规范中幂等性的定义是:
A request method is considered "idempotent" if the intended
effect on the server of multiple identical requests with that
method is the same as the effect for a single such request. Of
the request methods defined by this specification, PUT,
DELETE, and safe request methods are idempotent.
即:
一个请求方法,如果被请求多次和被请求一次效果相同,被认为是幂等的,比如PUT、DELETE和其他安全的请求方法都是幂等的。
1.2.2 微服务场景中幂等 由于微服务的普及,原有的单体应用,被设计成不同的功能模块作为服务,独立部署在不同的物理环境中。要完成一个完整的业务流程,就需要在多个微服务中间进行调用,而调用的过程当然是经由网络来完成的。
因此,网络通信的不确定性因素,比如网络的抖动,对端微服务的异常,可能会导致微服务间调用,产生超时的现象。为保证业务流程的顺利完成,调用过程必须建立重试机制。
然而,一旦建立了重试机制,那就可能会将同一个请求发送多次,导致接受方重复消费,多次执行相同操作,进而可能产生错误的数据。为避免这个问题,必须保证可能产生错误数据的接口(方法),请求一次和请求多次的效果相同,即幂等。
2.需要幂等的场景 可能会发生重复请求或消费的场景,在微服务架构中是随处可见的。以下是笔者梳理的几个常见场景:
- 网络波动:
因网络波动,可能会引起重复请求
- 分布式消息消费:
任务发布后,使用分布式消息服务来进行消费,参考【消息总线真的能保证幂等?】
- 用户重复操作:
用户在使用产品时,可能会误操作而触发多笔交易,或者因为长时间没有响应,而有意触发多笔交易。
- 未关闭的重试机制:
技术人员人为的错误,因开发人员、测试人员或运维人员没有检查出来,而开启的重试机制(如Nginx重试、RPC通信重试或业务层重试等)
所以CRUD角度分析幂等性,是从操作目的层面来看问题的:
操作 | 幂等性 |
---|---|
新增类请求(C) | 数据库自增主键,不具备幂等性 |
查询类动作(R) | 重复查询不会产生或变更新的数据,因此查询是天然具备幂等性 |
基于主键的计算式更新(U) | 不具备幂等性,即:UPDATE goods SET number=number-1 WHERE id=1 |
基于主键的非计算式更新(U) | 具备幂等性,即:UPDATE goods SET number=newNumber WHERE id=1 |
基于条件查询的更新(U) | 不一定具有幂等性(需要根据实际情况进行分析判断) |
基于主建的删除(D) | 具备幂等性 |
业务层面都是逻辑删除(即Update操作)(U) | 不具备幂等性 |
方法 | 幂等性 | 对应CRUD操作 |
---|---|---|
POST | 不安全且不幂等 | C |
GET | 安全且幂等 | R |
PUT | 不安全但幂等 | U |
DELETE | 不安全但幂等 | D |
首先GET请求很好理解,对资源做查询多次,此实现的结果都是一样的。
PUT请求的幂等性可以这样理解,将A修改为B,它第一次请求值变为了B,再进行多次此操作,最终的结果还是B,与一次执行的结果是一样的,即属于CURD中所说的基于主键的非计算式更新,所以PUT是幂等操作。
同理可以理解DELETE操作,第一次将资源删除后,后面多次进行此删除请求,最终结果是一样的,将资源删除掉了。
3.4需要“人工”的幂等 POST不是幂等操作,因为一次请求添加一份新资源,二次请求则添加了两份新资源,多次请求会产生不同的结果,因此POST不是幂等操作。
如果需要在POST方法的接口实现幂等,需要人为加上幂等的机制。
下面我们来说说,幂等地实现方法。
4.幂等实现方法 4.1 全局唯一ID 如果使用全局唯一ID,就是根据业务的操作和内容生成一个全局ID,在执行操作前先根据这个全局唯一ID是否存在,来判断这个操作是否已经执行。如果不存在则把全局ID,存储到存储系统中,比如数据库、Redis等。如果存在则表示该方法已经执行。
使用全局唯一ID是一个通用方案,可以支持插入、更新、删除业务操作。但是这个方案看起来很美但是实现起来比较麻烦,下面的方案适用于特定的场景,但是实现起来比较简单。
4.2 去重表 这种方法适用于在业务中有唯一标的插入场景中,比如在以上的支付场景中,如果一个订单只会支付一次,所以订单ID可以作为唯一标识。这时,我们就可以建一张去重表,并且把唯一标识作为唯一索引,用以记录订单支付信息,在我们实现时,把创建支付单据和写入去去重表,放在一个事务中,如果重复创建,数据库会抛出唯一约束异常,操作就会回滚。这个方法其实也是用到唯一ID,与上面全局唯一ID不同的是,他是针对具体单个业务流程的,实现起来相对简单。
4.3 插入或更新 这种方法插入并且有唯一索引的情况,比如我们要关联商品品类,其中商品的ID和品类的ID可以构成唯一索引,并且在数据表中也增加了唯一索引。这时就可以使用InsertOrUpdate操作。在mysql数据库中如下:
insert into goods_category
(goods_id,category_id,create_time,update_time)
values(#{goodsId},#{categoryId},now(),now())
on DUPLICATE KEY UPDATE update_time=now()
4.4 多版本控制 这种方法适合在更新的场景中,比如我们要更新商品的名字,这时我们就可以在更新的接口中增加一个版本号,来做幂等:
boolean updateGoodsName(int id,String newName,int version);
在实现时可以如下:
update goods set name=#{newName},version=#{version} where
id=#{id} and version<${version}
4.5 状态机控制 这种方法适合在有状态机流转的情况下,比如就会订单的创建和付款,订单的付款肯定是在之前,这时我们可以通过在设计状态字段时,使用int类型,并且通过值类型的大小来做幂等,比如订单的创建为0,付款成功为100,付款失败为99。在做状态机更新时,我们就这可以这样控制:
update goods_order set status=#{status} where id=#{id} and
status<#{status}
以上就是保证接口幂等性的一些方法。
5.总结 【接口幂等性适用场景及设计方法】幂等性设计不能脱离业务来讨论,一般情况下,去重表同时也是业务数据表,而针对分布式的去重ID,可以参考以下几种方式:
- UUID
- Snowflake
- 数据库自增ID
- 业务本身的唯一约束
- 业务字段+时间戳拼接
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 基于微信小程序带后端ssm接口小区物业管理平台设计
- 《魔法科高中的劣等生》第26卷(Invasion篇)发售
- 2020-04-07vue中Axios的封装和API接口的管理
- 我执意要等,是因为我相信你一定会来
- 调取接口时报404错误(ID:16)
- 4月23日海军节,我在青岛等你,一起看强大的中国海军。(如图如视频)
- CICC(脑机接口,科幻几近成真())
- 自律第1天
- 《遥遥无期的等待》