一、概念
一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外)。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。
幂等:是一个数学概念,表示N次变换和1次变换的结果相同。
幂等操作:其特点是任意多次执行所产生的影响均与一次执行的影响相同(不会改变资源状态,对数据没有副作用)。
幂等性:一系列操作都是幂等操作。
幂等接口:幂等接口认为,外部调用者会存在多次调用的场景,为了防止重试对数据状态的改变,需要将接口的设计为幂等的
二、幂等性和重复提交的区别
-
重复提交是在第一次请求已经成功的情况下,导致不满足幂等要求的服务多次改变状态。
-
幂等更多使用的情况是第一次请求不知道结果(比如超时)或者失败的异常情况下,发起多次请求,目的是多次确认第一次请求成功,却不会因多次请求而出现多次的状态变化。
三、幂等场景
-
重试机制:微服务场景,在我们传统应用架构中调用接口由于网络原因可能会超时。如果超时了,微服务框架会进行重试。
-
用户重复操作:客服端多次点击。如:快速点击按钮多次。
-
MQ消息中间件:消息重复消费
-
调用第三方平台的接口,因为异常也会导致多次异步回调
四、不足
-
增加了额外控制幂等的业务逻辑,复杂化了业务功能;
-
把并行执行的功能改为串行执行,降低了执行效率。
五、设计幂等性分析
5.1、数据库层面
增加(C)
数据库自增主键,不具备幂等性
查询(R)
重复查询不会产生或变更新的数据,因此查询是天然具备幂等性
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n35" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">select * from goods where xxx</pre>
更新(U)
-
基于主键的计算赋值,不具备幂等性,
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="sql" cid="n40" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit;">UPDATE goods SET number=number-1 WHERE id=1`</pre>
-
基于主键的非计算式,具备幂等性,
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="sql" cid="n43" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit;">UPDATE goods SET number=newNumber WHERE id=1</pre>
-
基于条件查询,不一定具有幂等性(需要根据实际情况进行分析判断)
删除(D)
-
基于主建的delete具备幂等性
-
一般业务层面都是逻辑删除(即update操作),而基于主键的逻辑删除操作也是具有幂等性的
5.2、接口层面(HTTP)
在HTTP规范中定义GET,PUT和DELETE方法应该具有幂等性。
POST方法
HTTP POST 方法是一个非幂等方法,因为调用多次,都将产生新的资源。因为它会对资源本身产生影响,每次调用都会有新的资源产生,因此不具备幂等性
GET方法
GET方法是向服务器查询,不会对系统产生副作用,具有幂等性(不代表每次请求都是相同的结果)
PUT方法
也就是说PUT方法首先判断系统中是否有相关的记录,如果有记录则更新该记录,如果没有则新增记录。
DELETE 方法
DELETE方法是删除服务器上的相关记录。
六、解决幂等性方案
唯一索引
这是最容易想到的方式。页面的数据通常只能被提交一次,多次提交可能会产生脏数据。比如,同一名称的商品只能被创建一次,为了防止创建多次,可以给商品名称添加唯一索引。当在添加一个已有名称的商品时,数据库插入操作就会因为唯一索引而引发异常,避免了脏数据的产生。类似的案例还有博客点赞,订单创建等场景。
唯一索引不仅可以解决并发下的脏数据问题,也可以解决永久性幂等的问题。
缺点: 无法适用分布式存储系统,需要维护数据库的唯一索引,多的情况下不容易管理
分布式锁
如果是分布式系统,全局的唯一索引就很难构建,此时可以使用分布式锁的方式解决此类问题。我们可以在执行第操作时先获取分布式锁,做完操作后,再将分布式锁释放。这样可以解决高并发性下的幂等性问题。
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n70" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Controller
String addOrder(order){
redisLock.lock();
addOrder(order);
redisLock.unLock();
}</pre>
需要配合 select+insert 使用,如果不配合,无法解决表单重复提交的问题。
MVCC 机制
MVCC(Multi-Version Concurrency Control) 多版本并发控制。在数据更新时需要去比较持有数据的版本号,版本号不一致的操作无法进行更新,更新成功后版本号将会发生变化。
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n75" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> updateStock(int num, int version) {
if (version != currentVersion) {
throw Exception;
} else {
stockDao.minStock();
}
}</pre>
注意,只适用于更新接口
状态机幂等
所谓状态机,就是任务或者业务在执行的过程中,拥有的状态以及状态的变更图。在执行某个操作前,需要先对当前状态进行验证,如果状态不是该有的状态,则拒绝操作。
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n80" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> String pay() {
if (order.status == "待支付") {
doPay();
}
}</pre>
后台管理员在新增数据时,由于特殊原因,比如按钮抖动,而导致目标数据插入两条;用户下单时,没有做逻辑校验,导致用户下了多笔相同的订单。这些情况都是不被允许的,我们要保证这些操作无论执行多少次,最终产生的结果都是相同的,这类业务通常需要拥有较强的一致性。幂等性就是描述这一接口特性的名词。
参考
网友评论