如何实现一个幂等接口
首先我们要搞清楚,何为幂等。幂等本来是一个数学中的概念,即使f(x)=f(f(x))。引入到计算机领域后,指对同一个接口或方法,使用同样的条件,一次请求和任意多次请求对系统的影响是一致的。
那通常我们有哪些手段来实现一个幂等接口或幂等函数呢?
我们还是先来将接口分下类,将接口分为查询,删除和交易类型:
1. 查询类型:
查询类型的接口,天然具有幂等性,因为查询交易一般不会对系统资源进行变更。比如根据用户的userId,查询用户的账户余额。如果账户的余额没有变化,那么查询一次得到的结果和查询多次得到的结果肯定是一样的。
2. 删除类型:
删除类型的接口,也一般都是幂等的,比如根据流水号,删除用户的操作记录。同样的流水号,删除一次,删除多次,返回的结果可能不一样,但影响是一样的,都只是把这条数据删除了。
3. 交易类型:
我们讨论的主角应该是交易类型。比如说转账,如果用户只操作了一次转账,但因为客户端调用服务端超时重发,或者使用了MQ的系统,MQ消息重发,又或者单纯的系统BUG,导致服务端转账接口被调用了2次或更多次,那如何控制调用多次和调用1次,对用户的影响是一样的呢?即用户最终只是转了一笔钱。这也就是我们今天的话题,如何保证交易的幂等性。
接口幂等通常的设计方法通常有:
a):运用数据库的唯一索引进行控制:
对于上面转账的例子。在设计服务端系统的转账接口时,可以在接口上设计一个流水号,或者一个转账ID。客户端在调用服务端时,需要将转账ID传送到服务端。服务端接收到消息后,先保存一条转账交易记录transfer_log。transfer_log中将转账ID设计成唯一索引,这样如果客户端一次转账交易调用了服务端多次,但多次用的是同一个转账ID,那么服务端只有在第一次接收到该转账ID时,可以成功入库,后续再接收到相同转账ID,因为数据库唯一索引的控制,会入库失败,入库失败后,直接中断后续操作直接返回客户端一个错误码,告诉该次转账重复。这样就可以保证同样一个转账ID,无论客户端调用几次,对服务端产生的影响都是跟调用1次是一样的。
b): 运用数据库CAS机制(乐观锁)。
还是转账的例子,用户A账户有余额500元,需要转走100元。
b1: 客户端发起转账时,同时把用户当前余额上送给服务端。服务端进行账户余额扣减时,判断当前的余额和客户上送的余额是否一致。
update transfer set amout=amout-100 where userid = '用户A' and amount = 500。
上面这条语句意思就是,用户账户余额是500,转出100,如果余额不是500,就不转。这样如果用户A的转账命令,客户端只会成功执行1次,执行第2次时,因为余额肯定已经不是500了,不会执行成功,这样也是实现了该交易的幂等性。
b2: b1方案确实可以实现转账交易的幂等性,但有一个漏洞。如果用户的操作步骤是下面这样的,就会出现问题:
第一步:用户A有余额500元,在手机上进行转账100元,因为某种原因,比如MQ消息重发。服务端分别在第1分钟,和第5分钟收到了重复的两条消息。第一条消息服务端处理完成,成功从用户A账户转走100元,用户余额现在变成了400元。
第二步:用户A当前余额为400元,用户B在第3分钟给用户A转入了100元,第3分钟时,用户A的余额又变成了500元。
第三步:第5分钟,第一步的重复消息到达服务端,服务端又进行成功的从用户A账户上转走了100元,因为这个时候用户余额已经恢复到了500元,update语句的条件成立,但其实是做了重复转账,这是不能接受的。
那这种情况我们可以怎样进行处理呢?
我们可以在transfer表里增加一个version字段。转账交易,客户端必须将version传到服务器,每次发生转入转出,余额有变化时,就将version加1,语句如下:
update transfer set amount-100, version=version+1 where userid = '账户A' and version=1
像这样,通过version来控制金额的变化,就可以解决通过余额来控制金额变化带来的问题。
c): 使用状态机进行控制:
还是上面转账的例子。假如客户端转账中很多步骤,每一步都会更新一个状态,我们就可以通过状态机来实现幂等。
比如转账交易中,服务端有四步操作,每步都记录状态。
1-校验用户信息通过。
2-校验账户状态通过。
3-校验余额通过
4-发起转账。
转账必须必须是在校验余额通过后,我们就可以通过状态机来控制及数据库的CAS机制来实现幂等。
update transfer set status = 4 where transfer_id = '001' and status = 3;
因为同一笔转账ID,status状态由3转为4,只能是1次成功,第二调用,由于条件中的status=3必定不成立,更新也不会成功。也可以控制同一个转账ID,必然只会进行一次转账。
网友评论