什么是HTTP的幂等性呢

幂等性是数学中的一个概念,表达的是N次变换与1次变换的结果相同 [ ff((x)) = f(x) ]

系统开发过程中经常遇到这个路由到底改用POST,PUT还是PACHT,这就是HTTP的幂等性。

在HTTP/1.1规范中幂等性的定义是:

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方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用。可以理解为无论调用多少次都不会有不同结果的HTTP方法。只作用于结果而非资源本身。HTTP协议本身是面向资源的应用层协议,但HTTP协议的使用实际上存在2种:RESTfulSOAPRESTful遵守了HTTP协议的各种规定,把HTTP协议作为应用层协议。SOAP则把HTTP协议作为传输层协议,然后在HTTP之上建立了自己的应用层协议。

安全方法是指不修改资源的 HTTP 方法。譬如,当使用 GET 或者 HEAD 作为资源 URL,都必须不去改变资源。然而,这并不全准确。意思是:它不改变资源的 表示形式。对于安全方法,它仍然可能改变服务器上的内容或资源,但这必须不导致不同的表现形式。

说完HTTP幂等性定义,到底哪些叫幂等且安全的HTTP方法呢?

(部分) HTTP 方法概览

HTTP MethodIdempotentSafe
OPTIONSYesYes
GETYesYes
HEADYesYes
PUTYesNo
POSTYesNo
DELETEYesNo
PATCHNono

HTTP 的 POSTPUT 并不等价于 CRUD 中的创建(C)和更新(U)。他们服务于不同的目标,只不过在某种情况下可能也可以, 甚至推荐使 POST 来创建资源或者使用 PUT 来更新资源。值得一提的是 PUT 方法是幂等的。对同一资源的多次 PUT 操作,不应该返回不同的资源,而对同一资源的多次 POST 可以生产多个资源。而 PATCH 方法可以用来更新资源的一个组成部分。举个例子,当你仅需变更某个用户名,PUT 一个完整的资源就显得很累赘同时会消耗更多带宽。

高并发系统中接口幂等性的实现方法

背景

  • 下单过程中,点击确认之后没有反应,然后再次点击
  • 订单完成后,只能给用户发一次消息

假设有一个从账户扣款的接口,我们暂时用类函数的方式记为:

public function tickMoney($userId, $amount){...}

如果扣除成功则返回 true,账户余额减少 amount;如果扣除失败则返回 false,账户余额不变。看似没有瑕疵的方法,如果 tickMoney() 方法已经被服务器处理,但由于网络原因返回结果被丢掉了。于是客户端没有收到通知,所有又发起了一次请求,这样就导致了 tickMoney() 方法被调用了两次,账户多扣了一次钱。

解决思路1:让客户端生成唯一流水号ID,用来标识是不是同一次请求或者交易。这种ID通常都需要具备全局唯一性。现将这个ID保存到流水表里面,并且流水表将这个ID字段设置为 UNIQUE KEY, 如果插入出现冲突,说明这个创建的请求订单已经处理过了。直接返回操作结果。用携带流水号ID请求接口 tickMoney('ID', $userId, $amount), 如果出现超市了,调用方不知道扣款成功还是失败,用同一个流水号重试请求,扣款虽然受到2次请求,但是由于流水号是同一个,可以根据流水号做幂等操作,并告知客户端请求成功与否。

解决思路2:与解决思路1类似,就是把唯一值的放入Redis来提高效率。求情发起后,系统去Redis系统检测是否存在此账户的ID的key,如果不存在则增加key,然后扣减账户余额。如果存在key,则不进行实际扣减操作。无论扣减成功与失败,在请求返回后,都删除该账户的key。

幂等性不能脱离业务来讨论

在不同的需求场景下,实现幂等的思路和方案也会不同。

RESTful手册