活动幂等性

如何保证操作的幂等性

是指可以使用相同参数重复执行,并能获得相同结果的函数,这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变

我们先从一个例子说起,假设有一个从账户取钱的远程API,我们暂时用类函数的方式记为


bool withdraw(account_id, amount) 

withdraw的语义是从account_id对应的账户中扣除amount数额的钱;如果扣除成功则返回true,账户余额减少amount;如果扣除失败则返回false,账户余额。一种典型的情况是withdraw请求已经被服务器端正确处理,但服务器端的返回结果由于网络等原因被掉丢了,导致客户端无法得知处理结果。如果是在网页上,一些不恰当的设计可能会使用户认为上一次操作失败了,然后刷新页面,这就导致了withdraw被调用两次,账户也被多扣了一次钱。如下图所示



我们可以通过一些技巧把withdraw变成幂等的,比如:
  1. int create_ticket()   
  2. bool idempotent_withdraw(ticket_id, account_id, amount)  
create_ticket的语义是获取一个服务器端生成的唯一的处理号ticket_id,它将用于标识后续的操作。idempotent_withdraw和withdraw的区别在于关联了一个ticket_id,一个ticket_id表示的操作至多只会被处理一次,每次调用都将返回第一次调用时的处理结果。这样,idempotent_withdraw就符合幂等性了,客户端就可以放心地多次调用。
基于幂等性的解决方案中一个完整的取钱流程被分解成了两个步骤:1.调用create_ticket()获取ticket_id;2.调用idempotent_withdraw(ticket_id, account_id, amount)。虽然create_ticket不是幂等的,但在这种设计下,它对系统状态的影响可以忽略,加上idempotent_withdraw是幂等的,所以任何一步由于网络等原因失败或超时,客户端都可以重试,直到获得结果。如下图所示



上面的例子是关于幂等性思想在取款流程的体现,其实支付是相似的。在取款流程中,服务器会生成一个唯一处理号ticket_id,而在支付流程中,会在提交订单时生成一个订单编号prepay_id,也叫预支付id。如果一个订单支付成功后,在支付页面(如下图)再次进行支付,应提示“该订单已支付”的提示。这就是幂等性在支付流程中的体现,同一个订单支付的操作只会进行一次,订单支付成功后,客户端尝试再次支付,此时会带着prepay_id进行请求,服务器会返回第一次支付请求的处理结果,所以同一个订单不会进行多次支付


常见的幂等性的处理方法
1 全局的唯一ID
这种方法适用于在业务中有唯一标的插入场景中,比如在以上的支付场景中,如果一个订单只会支付一次,所以订单ID可以作为唯一标识。这时,我们就可以建一张去重表,并且把唯一标识作为唯一索引,在我们实现时,把创建支付单据和写入去去重表,放在一个事务中,如果重复创建,数据库会抛出唯一约束异常,操作就会回滚

2 多版本控制去     
这种方法适合在更新的场景中,比如我们要更新商品的名字,这时我们就可以在更新的接口中增加一个版本号,来做幂等
update goods set name=#{newName},version=#{versionwhere id=#{idand version<${version}

3 状态机控制
这种方法适合在有状态机流转的情况下,比如就会订单的创建和付款,订单的付款肯定是在之前,这时我们可以通过在设计状态字段时,使用int类型,并且通过值类型的大小来做幂等,比如订单的创建为0,付款成功为100。付款失败为99
update `order` set status=#{statuswhere id=#{idand status<#{status}

4 token令牌

这种方式分成两个阶段:申请token阶段和支付阶段。
第一阶段,在进入到提交订单页面之前,需要订单系统根据用户信息向支付系统发起一次申请token的请求,支付系统将token保存到Redis缓存中,为第二阶段支付使用。
第二阶段,订单系统拿着申请到的token发起支付请求,支付系统会检查Redis中是否存在该token,如果存在,表示第一次发起支付请求,删除缓存中token后开始支付逻辑处理;如果缓存中不存在,表示非法请求。
实际上这里的token是一个信物,支付系统根据token确认,你是你妈的孩子。不足是需要系统间交互两次,流程较上述方法复杂

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页