一个HTTP方法是幂等的,指的是同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的。换句话说就是,幂等方法不应该具有副作用(统计用途除外)。在正确实现的条件下, GET , HEAD , PUT 和 DELETE 等方法都是幂等的,而 POST 方法不是。所有的 safe 方法也都是幂等的。
幂等性只与后端服务器的实际状态有关,而每一次请求接收到的状态码不一定相同。例如,第一次调用 DELETE 方法有可能返回 200 ,但是后续的请求可能会返回 404 。 DELETE 的言外之意是,开发者不应该使用 DELETE 法实现具有删除最后条目功能的 RESTful API。
幂等,一般在以下场景中会遇到:
幂等与防重初看起来是相同的,但幂等在功能方面优于防重,与业务更相关。 幂等可能会要求存在重复的请求时返回与首次请求相同的响应,同时严格的防重使客户端处理超时变得复杂。 防重在性能优于幂等,并且通常在调用链的“偏前端”完成。在两者结合时,可以实现短时防重、只防成功响应等,来减小只实现幂等代价。
本文以较为通用的幂等表方案进行说明。
对于查询以及天然幂等这样没有副作用的操作(也就是第一节中描述的safe方法),一般不用做额外的工作。
对于页面的重复提交,前端页面可以以跳转、按钮置灰的方式在重复发生前阻止重复。
当然,前端的实现,一般可以被绕过直接请求后端接口,所以后端在需要的地方必须要实现幂等。
为了识别不同时间发送的相同请求,首先需要定义的是什么是重复,唯一性从何而来。
为了让我们的唯一性能更通用一些,我们可以:
这里还有个小插曲,在实现严格无副作用幂等的时候,因为服务端需要在业务逻辑失败返回客户端前将失败的结果落库,而失败的业务逻辑通常需要rollback,因而又有两种不同的事务实现方式。
使用Propagation.REQUIRES_NEW或Propagation.NESTED。
使用Propagation.NESTED实现相对简单一些,因为nested的事务与外层事务是同一事务,异常处理起来相对简单。
而使用Propagation.REQUIRES_NEW的话,因为是两个事务,所以基本上需要当作分布式系统的情况来考虑各种异常。因此单机还是尽量用Propagation.NESTED。
业务处理过程中存在未知异常时(例如空指针)或者某个切面异常,在Propagation.REQUIRES_NEW实现中,由于外层无法判断new transaction的提交与否(因为数据库提交后也可能发生异常),可以捕获后返回【处理中】。
如果是用MySQL实现,需要注意MySQL的隔离级别、并发控制( MySQL并发实验 )。
按照中间件的可靠性等方面,可以有:
由于Redis与MySQL相比可靠性较低,且Redis一般无法与MySQL保持同一事务,在一些不太重要的地方或者失败可接受的情况下可以使用,这里没再深入了。
查询是被滥用最严重的接口了。在查询仅用于展示数据,而不是控制流程或者其它目的的情况下,查询一般工作得很好。
一旦查询的结果影响了系统后面动作的时候,就会发生问题。此时查询的含义已经变了,变得不再单纯,它已经不能被称作是查询接口了。
查询的数据一旦脱离了其专门领域的掌控,就任由其它实体摆布。这也是贫血模型导致的失忆症的情况。这时可以运用TDA原则。
在客户端多次请求无果的情况下,客户端可能希望查询后进行回退补偿或者其它操作(其实不查询直接回退补偿也是可以的)。
这时的查询,也已经不是单纯的查询,而是查询for补偿,需在在查无结果的情况下,插入失败的请求记录,以此来确保返回数据的准确。