幂等性介绍
现如今很多系统都会基于分布式或微服务思想完成对系统的架构设计。那么在这一个系统中,就会存在若干个微服务,而且服务间也会产生相互通信调用。那么既然产生了服务调用,就必然会存在服务调用延迟或失败的问题。当出现这种问题,服务端会进行重试等操作或客户端有可能会进行多次点击提交。如果这样请求多次的话,那最终处理的数据结果就一定要保证统一,如支付场景。此时就需要通过保证业务幂等性方案来完成。
什么是幂等性
幂等是一个数学与计算机学概念,即
f(n) = 1^n
,无论n为多少,f(n)的值永远为1,在数学中某一元运算为幂等时,其作用在任一元素两次后会和其作用一次的结果相同。
在编程开发中,对于幂等的定义为:无论对某一个资源操作了多少次,其影响都应是相同的。 换句话说就是:在接口重复调用的情况下,对系统产生的影响是一样的,但是返回值允许不同,如查询。
幂等函数或幂等方法是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。
幂等性不仅仅只是一次或多次操作对资源没有产生影响,还包括第一次操作产生影响后,以后多次操作不会再产生影响。并且幂等关注的是是否对资源产生影响,而不关注结果。
幂等性维度
幂等性设计主要从两个维度进行考虑:空间、时间。
- 空间:定义了幂等的范围,如生成订单的话,不允许出现重复下单。
- 时间:定义幂等的有效期。有些业务需要永久性保证幂等,如下单、支付等。而部分业务只要保证一段时间幂等即可。
同时对于幂等的使用一般都会伴随着出现锁的概念,用于解决并发安全问题。
以SQL为例
select * from table where id=1
。此SQL无论执行多少次,虽然结果有可能出现不同,都不会对数据产生改变,具备幂等性。insert into table(id,name) values(1,'heima')
。此SQL如果id或name有唯一性约束,多次操作只允许插入一条记录,则具备幂等性。如果不是,则不具备幂等性,多次操作会产生多条数据。update table set score=100 where id = 1
。此SQL无论执行多少次,对数据产生的影响都是相同的。具备幂等性。update table set score=50+score where id = 1
。此SQL涉及到了计算,每次操作对数据都会产生影响。不具备幂等性。delete from table where id = 1
。此SQL多次操作,产生的结果相同,具备幂等性。
什么是接口幂等性
在
HTTP/1.1
中,对幂等性进行了定义。
它描述了一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外),即第一次请求的时候对资源产生了副作用,但是以后的多次请求都不会再对资源产生副作用。
这里的副作用是不会对结果产生破坏或者产生不可预料的结果。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。
为什么需要实现幂等性
使用幂等性最大的优势在于使接口保证任何幂等性操作,免去因重试等造成系统产生的未知的问题。
在接口调用时一般情况下都能正常返回信息不会重复提交,不过在遇见以下情况时可以就会出现问题:
前端重复提交表单
在填写一些表格时候,用户填写完成提交,很多时候会因网络波动没有及时对用户做出提交成功响应,致使用户认为没有成功提交,然后一直点提交按钮,这时就会发生重复提交表单请求。
用户恶意进行刷单
例如在实现用户投票这种功能时,如果用户针对一个用户进行重复提交投票,这样会导致接口接收到用户重复提交的投票信息,这样会使投票结果与事实严重不符。
接口超时重复提交
很多时候 HTTP 客户端工具都默认开启超时重试的机制,尤其是第三方调用接口时候,为了防止网络波动超时等造成的请求失败,都会添加重试机制,导致一个请求提交多次。
消息进行重复消费
当使用 MQ 消息中间件时候,如果发生消息中间件出现错误未及时提交消费信息,导致发生重复消费。
引入幂等性后对系统有什么影响
幂等性是为了简化客户端逻辑处理,能放置重复提交等操作,但却增加了服务端的逻辑复杂性和成本,其主要是:
- 把并行执行的功能改为串行执行,降低了执行效率。
- 增加了额外控制幂等的业务逻辑,复杂化了业务功能;
所以在使用时候需要考虑是否引入幂等性的必要性,根据实际业务场景具体分析,除了业务上的特殊要求外,一般情况下不需要引入的接口幂等性。
Restful API 接口幂等
现在流行的 Restful 推荐的几种 HTTP 接口方法中,分别存在幂等行与不能保证幂等的方法,如下:
HTTP协议语义幂等性
HTTP协议有两种方式:RESTFUL、SOA。现在对于WEB API,更多的会使用RESTFUL风格定义。为了更好的完成接口语义定义,HTTP对于常用的四种请求方式也定义了幂等性的语义。
- GET:用于获取资源,多次操作不会对数据产生影响,具有幂等性。注意不是结果。
- POST:用于新增资源,对同一个URI进行两次POST操作会在服务端创建两个资源,不具有幂等性。
- PUT:用于修改资源,对同一个URI进行多次PUT操作,产生的影响和第一次相同,具备幂等性。
- DELETE:用于删除资源,对同一个URI进行多次DELETE操作,产生的影响和第一次相同,具备幂等性
综上所述,这些仅仅只是HTTP协议建议在基于RESTFUL风格定义WEB API时的语义,并非强制性。同时对于幂等性的实现,肯定是通过前端或服务端完成。
业务问题抛出
在业务开发与分布式系统设计中,幂等性是一个非常重要的概念,有非常多的场景需要考虑幂等性的问题,尤其对于现在的分布式系统,经常性的考虑重试、重发等操作,一旦产生这些操作,则必须要考虑幂等性问题。以交易系统、支付系统等尤其明显,如:
- 当用户购物进行下单操作,用户操作多次,但订单系统对于本次操作只能产生一个订单。
- 当用户对订单进行付款,支付系统不管出现什么问题,应该只对用户扣一次款。
- 当支付成功对库存扣减时,库存系统对订单中商品的库存数量也只能扣减一次。
- 当对商品进行发货时,也需保证物流系统有且只能发一次货。
在电商系统中还有非常多的场景需要保证幂等性。但是一旦考虑幂等后,服务逻辑务必会变的更加复杂。因此是否要考虑幂等,需要根据具体业务场景具体分析。而且在实现幂等时,还会把并行执行的功能改为串行化,降低了执行效率。
此处以下单减库存为例,当用户生成订单成功后,会对订单中商品进行扣减库存。 订单服务会调用库存服务进行库存扣减。库存服务会完成具体扣减实现。
现在对于功能调用的设计,有可能出现调用超时,因为出现如网络抖动,虽然库存服务执行成功了,但结果并没有在超时时间内返回,则订单服务也会进行重试。那就会出现问题,stock对于之前的执行已经成功了,只是结果没有按时返回。而订单服务又重新发起请求对商品进行库存扣减。 此时出现库存扣减两次的问题。 对于这种问题,就需要通过幂等性进行结果。
解决方案
对于幂等的考虑,主要解决两点前后端交互与服务间交互。这两点有时都要考虑幂等性的实现。从前端的思路解决的话,主要有三种:前端防重、PRG模式、Token机制。
前端防重
通过前端防重保证幂等是最简单的实现方式,前端相关属性和JS代码即可完成设置。可靠性并不好,