对于任何有状态(涉及状态修改)的服务,在设计服务、编码时,都必须考虑幂等性。
注:无状态的(如查询服务)服务,也要考虑幂等性。
设计原则
- 对外:返回完全相同的结果
- 对内:自身状态不再发生任何改变
另,在实现层面:
- 对于服务方
- 对于调用方
02
一些例子
正例 ✅
- 某个支付接口,对于多次重复的支付调用,能够返回完全相同的结果(结果字段完全一样),且内部状态不再改变,我们就说该接口满足幂等性。
反例 ❌
- 某个转账接口,对于多次重复的转账,真的将用的余额转出了多次,预期应该只转出一次。
- 某个更新接口,对于多次重复更新,首次更新和之后的更新返回的结果有差异。
最佳实践
case 1:以业务单据表做幂等
- 对于幂等性的控制,一般在事务中进行,且一定要加锁处理
- 只有确认当前操作此前未执行,才可以执行操作(第一次来)
- 一旦发现操作在此前已经完成(被幂等了),则不能继续做操作,避免重复处理。此时应该检查请求和DB数据的一致性(幂等一致性校验),原则上所有的字段都应该一致,至少要检验关键字段的一致性(如单号,金额,用户等)。
- 无论是“第一次来”还是“被幂等了”,最后返回的结果,应该是完全相同的。
- 对于调用方需要判断幂等场景,并作相应的处理。
case 2:以业务幂等表做幂等
- 首先,所有的操作都应该在事务中完成,并且严格按照:一锁二判三更新实施。
其他的步骤类似case1,这里不再赘述。
什么场景用case 1什么场景用case 2?为什么会有不一致的场景。我们遇到的情况是同一张流水表上承载了不同的产品,而不同产品的幂等规则不一致,如果UK全部加到通用流水表上,会对其产生侵入甚至产生互斥,而且UK多了会降低表的插入性能。其实不同场景就是把幂等服务和幂等表独立了出来。如果你的系统有多个服务都需要用幂等逻辑,那么这种方式的扩展性会好一点,便于后续的升级和修改。另外,在分库分表的情况下,case2中的幂等表其实还保存了业务单号,此时幂等表还承担了路由表的角色。
举一反三
由同步调用的幂等性,可以引申出:
DB幂等键的设计(唯一索引的设计)
消息发送和接收的幂等性处理
定时任务的幂等性处理
超时任务的幂等性处理
全局幂等表的接入和使用
分布式事务的幂等处理
Notes:幂等处理如何做
可以从幂等处理对象操作类型进行分类:
对已有数据进行更新操作的实现逻辑的幂等。这个比较适合用一锁二判三更新。
对于有新数据进行写入的操作的实现的幂等,这个是需要用唯一性约束来判断的。这种表的唯一设计可以直接用业务表的唯一性,也可以根据扩展和变化引入独立的幂等表。关于幂等表,核心就是在于扩展,尤其是对于那些业务规则在前期设计设计时候没有识别出来,后期又要增加控制逻辑或者幂等逻辑的时候,为了减少对业务表的冲击,引入额外的幂等表就有价值了。
幂等表也存在一些弊端,就是引入额外的DB操作,以及后续的数据订正和迁移都要比直接用业务表做幂等复杂。
总结
总之,对于幂等设计,只要记住本文的第一句话就好:
无论调用方还是服务方,对于任何有状态(涉及状态修改)的服务,在设计服务、编码时,都必须考虑幂等性。
更多代码设计内容:
如何通过代码重构让你的代码赏心悦目?从0到1教你写好系分设计模式实践&技巧大盘点:帮你打开思路,灵活变通各类应用场景