浅谈幂等设计
最近笔者在做一个幂等方面的需求,把开发过程中的一些心得,分享给大家,主要是围绕着幂等的一些理念的辩证与思考,如有错误请指正!
什么是幂等
幂等性原本是一个数学领域的概念:对于函数f(x),若满足f(f(x))=f(x),则称该函数具有幂等性。在计算机领域,则指多次执行同一操作对系统状态的改变与仅执行一次是相同的。
核心是对于系统资源状态的修改,不管执行一次还是执行多次,最终资源状态的终态是一致的。
常见的场景有,
- 支付系统:钱包扣款,一笔订单被请求了十次扣款,与仅请求一次扣款,钱包的剩余金额应该是一致的;
- 库存系统:商品的超卖防护机制,对同一个商品的多次下单,最终库存数量应该仅减一;
接口返回值是否需要一致呢?
比如,同样以订单扣款为例,一次扣款很好理解,需要返回扣款成功,那么后面的九次重复的扣款呢?应该也返回扣款成功吗?还是提示重复扣款异常呢?
回顾下幂等的定义,其实对接口返回策略并没有什么约束。但是笔者在前期查阅资料的时候,很多相关的资源中,往往会要求“接口返回一致”,这引起了笔者的很大困惑,笔者认为这种方式是有一些不妥的,至少在一些场景下,是不对的。
依旧是以扣款操作为例,用户下单完后进行支付时,第一次扣款成功了,但是因为一些别的原因,导致顾客发起了第二次支付,同样也显示成功了,那么对于顾客来说,这笔订单,到底支付了几次呢?(这个场景其实有些不太恰当,笔者想表达的意思是,有些操作:比如直接对客展示,或者就是由客户发起的操作,如果返回相同的结果,对顾客来说反而会产生误导,引起一些不必要的问题。)
而且,除此以外,在一些情况下,还需要组装接口响应数据,该过程中也有可能会产生较大的资源消耗。
所以,直接提示异常,然后通过其他方式,比如跳转到订单页面,重新查看下该订单的状态,看是否已经支付成功了,可能会更好些。
接口幂等?业务幂等?
在网上搜一下幂等的实现,大部分都还是以唯一序列号、请求令牌之类的业务无关性的设计,通过它们来实现接口幂等,但是仅仅依靠这些,能满足业务需求吗?或者说我们实际需要的是接口维度的幂等?还是业务维度的幂等?
依旧以扣款操作为例,该类业务操作往往会涉及到订单系统、收银系统等,顾客发起的第一次支付,从订单系统请求收银系统时,虽然收银系统是支付成功了的,但是由于订单系统与收银系统之间的网络等其他问题,导致订单系统处,该订单的状态还是支付失败的。所以顾客又发起了一次支付,此时如果我们仅使用接口幂等,如如唯一序列号,第二次顾客发起的支付操作,唯一序列号与第一次是不一致的,所以该订单会再次支付。这也是常见的导致了重复支付问题的一种原因。
所以实际上在支付的操作中,是需要校验整个订单是否被成功支付过,而非仅仅是校验请求token。或者可以说,能否真正的保证业务的幂等性,依赖的是业务规则的校验,而非接口幂等性的校验。
这其实也对应了,我们在接口设计中,常常会遇到的两个id信息:请求id与业务id。
接口幂等有必要吗?
在探讨这个问题前,我们先看下,业务幂等需要做哪些校验?
比较简单的业务下,可能仅仅只需要查询一张表,查询一条数据,看下对应数据的状态就足够了;稍稍复杂些的业务,则可能需要查询多张表,关联着看多条数据的状态;更复杂些的业务,甚至会需要查询周边系统资源的状态。
显而易见,业务幂等,根据业务的不同,所需要的资源消耗是不同的,在整个校验业务规则的过程中,会对数据库、周边系统造成较大压力。
如果本身就是不同的请求,付出这些资源无可厚非,至少在当前的业务下,这些资源是为了实现业务,所必须要付出的,它们保证了业务的正常运行,是有价值的。但是对于重复请求来说,就得不偿失了,所以我们需要能更简单、更高效的识别出重复请求。
在此诉求下,接口幂等就非常合理了,比如接口幂等有一种方式是Token防重令牌,该方式签发与校验令牌都可以依赖redis等组件实现,在整个过程中都不会对数据库有压力。
虽然紧靠接口幂等无法保证业务正确性,但是实际上它处理了至少99%的重复请求。
最终设计
接口幂等层解决的是是否是同一条消息的问题,进行流量过滤,极大的减少了资源消耗;
而业务幂等层才能解决是否是同一个业务操作的问题,才能保证资源状态的一致性、正确性。