编程中的幂等性 — HTTP幂等性

幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。编程

在编程中.一个幂等操做的特色是其任意屡次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可使用相同参数重复执行,并能得到相同结果的函数。这些函数不会影响系统状态,也不用担忧重复执行会对系统形成改变。例如,“getUsername()和setTrue()”函数就是一个幂等函数.更复杂的操做幂等保证是利用惟一交易号(流水号)实现.后端

——百度百科浏览器

什么是幂等性(Idempotence)?

Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.
——HTTP/1.1规范中幂等性的定义

从定义上看,HTTP方法的幂等性是指一次和屡次请求某一个资源应该具备一样的反作用。说白了就是,同一个请求,发送一次和发送N次效果是同样的!幂等性是分布式系统设计中十分重要的概念,而HTTP的分布式本质也决定了它在HTTP中具备重要地位。下面将以HTTP中的幂等性作例子加以介绍。安全

简单示例

假设有一个从帐户取钱的远程API(能够是HTTP的,也能够不是),咱们暂时用类函数的方式记为:服务器

bool withdraw(account_id, amount)

withdraw的语义是从account_id对应的帐户中扣除amount数额的钱;若是扣除成功则返回true,帐户余额减小amount;若是扣除失败则返回false,帐户余额不变。网络

值得注意的是:和本地环境相比,咱们不能轻易假设分布式环境的可靠性架构

因此问题来了,一种典型的状况是withdraw请求已经被服务器端正确处理,但服务器端的返回结果因为网络等缘由被掉丢了,致使客户端没法得知处理结果。若是是在网页上,一些不恰当的设计可能会使用户认为上一次操做失败了,而后刷新页面,这就致使了withdraw被调用两次,帐户也被多扣了一次钱。如图所示:异步

non-idempotent

解决方案一:采用分布式事务,经过引入支持分布式事务的中间件来保证withdraw功能的事务性。分布式事务的优势是对于调用者很简单,复杂性都交给了中间件来管理。缺点则是一方面架构过重量级,容易被绑在特定的中间件上,不利于异构系统的集成;另外一方面分布式事务虽然能保证事务的ACID性质,而但却没法提供性能和可用性的保证。分布式

解决方案二:幂等设计。咱们能够经过一些技巧把withdraw变成幂等的,好比:ide

int create_ticket() 
bool idempotent_withdraw(ticket_id, account_id, amount)

create_ticket的语义是获取一个服务器端生成的惟一的处理号ticket_id,它将用于标识后续的操做。idempotent_withdraw和withdraw的区别在于关联了一个ticket_id,一个ticket_id表示的操做至多只会被处理一次,每次调用都将返回第一次调用时的处理结果。这样,idempotent_withdraw就符合幂等性了,客户端就能够放心地屡次调用。

基于幂等性的解决方案中一个完整的取钱流程被分解成了两个步骤:1.调用create_ticket()获取ticket_id;2.调用idempotent_withdraw(ticket_id, account_id, amount)。虽然create_ticket不是幂等的,但在这种设计下,它对系统状态的影响能够忽略,加上idempotent_withdraw是幂等的,因此任何一步因为网络等缘由失败或超时,客户端均可以重试,直到得到结果。如图所示:

idempotent

和分布式事务相比,幂等设计的优点在于它的轻量级,容易适应异构环境,以及性能和可用性方面。在某些性能要求比较高的应用,幂等设计每每是惟一的选择。

HTTP的幂等性

本文主要以HTTP GET、DELETE、PUT、POST四种方法为主进行语义和幂等性的介绍。

HTTP GET方法用于获取资源,不该有反作用,因此是幂等的。好比:GET http://www.bank.com/account/123456,不会改变资源的状态,不论调用一次仍是N次都没有反作用。请注意,这里强调的是一次和N次具备相同的反作用,而不是每次GET的结果相同。GET http://www.news.com/latest-news这个HTTP请求可能会每次获得不一样的结果,但它自己并无产生任何反作用,于是是知足幂等性的。

HTTP DELETE方法用于删除资源,有反作用,但它应该知足幂等性。好比:DELETE http://www.forum.com/article/4231,调用一次和N次对系统产生的反作用是相同的,即删掉id为4231的帖子;所以,调用者能够屡次调用或刷新页面而没必要担忧引发错误。

HTTP POST方法用于建立资源,所对应的URI并不是建立的资源自己,而是去执行建立动做的操做者,有反作用,不知足幂等性。好比:POST http://www.forum.com/articles的语义是在http://www.forum.com/articles下建立一篇帖子,HTTP响应中应包含帖子的建立状态以及帖子的URI。两次相同的POST请求会在服务器端建立两份资源,它们具备不一样的URI;因此,POST方法不具有幂等性。

HTTP PUT方法用于建立或更新操做,所对应的URI是要建立或更新的资源自己,有反作用,它应该知足幂等性。好比:PUT http://www.forum/articles/4231的语义是建立或更新ID为4231的帖子。对同一URI进行屡次PUT的反作用和一次PUT是相同的;所以,PUT方法具备幂等性。

对前文示例进行改进

利用Web API的形式实现前面所提到的取款功能。

一、用POST /tickets来实现create_ticket;

二、用PUT /accounts/account_id/ticket_id&amount=xxx来实现idempotent_withdraw。

值得注意的是严格来说amount参数不该该做为URI的一部分,真正的URI应该是/accounts/account_id/ticket_id,而amount应该放在请求的body中。这种模式能够应用于不少场合,好比:论坛网站中防止意外的重复发帖。

电商中遇到的问题

如何防范 POST 重复提交

HTTP POST 操做既不是安全的,也不是幂等的(至少在HTTP规范里没有保证)。当咱们由于反复刷新浏览器致使屡次提交表单,屡次发出一样的POST请求,致使远端服务器重复建立出了资源。

因此,对于电商应用来讲,第一对应的后端 WebService 必定要作到幂等性,第二服务器端收到 POST 请求,在操做成功后必须302跳转到另一个页面,这样即便用户刷新页面,也不会重复提交表单。

把分布式事务分解为具备幂等性的异步消息处理

电商的不少业务,考虑更多的是 BASE(即Basically Available、Soft state、和Eventually consistent),而不是 ACID(Atomicity、Consistency、Isolation和 Durability)。即为了知足高负载的用户访问,咱们能够容忍短暂的数据不一致。那怎么作呢?

第一,不作分布式事务,代价太大。
第二,不必定须要实时一致性,只须要保证最终的一致性便可。
第三,“经过状态机和严格的有序操做,来最大限度地下降不一致性”。
第四,最终一致性(Eventually Consistent)经过异步事件作到。

若是消息具备操做幂等性,也就是一个消息被应用屡次与应用一次产生的效果是同样的话,那么把不须要同步执行的事务交给异步消息推送和订阅者集群来处理便可。假如消息处理失败,那么就消息重播,因为幂等性,应用屡次也能产生正确的结果。

实际状况下,消息很难具备幂等性,解决方法是使用另外一个表记录已经被成功应用的消息,即消息队列和消息应用状态表一块儿来解决问题。

总结

上面简单介绍了幂等性的概念,用幂等设计取代分布式事务的方法,以及HTTP主要方法的语义和幂等性特征。其实,若是要追根溯源,幂等性是数学中的一个概念,表达的是N次变换与1次变换的结果相同,有兴趣的读者能够从Wikipedia上进一步了解。

转自:http://www.i3geek.com/archives/841

相关文章
相关标签/搜索