概念
多次操作对数据的影响和操作一次是一样的。【查询】、【删】操作就不存在幂等问题,不管怎么查询结果都一样,删除一次这个数据就没了。【增】、【改】才存在幂等问题。
解决
这里仅讨论后台的如何处理幂等问题,对于页面的限制属于前端范畴不做扩展。
- 主键和乐观锁/悲观锁 大家第一想到的肯定是直接由数据库来保证幂等性,在【改】的操作上通过『ById』主键的方式即可。
但是这里仅仅能保证我无论通过什么方式进行修改都是同一条数据,而无法确保【改】的顺序性。为了能进一步让我们的程序设计的符合我们的预期,一般表设计会存在version字段来保证数据的版本,同时也可以避免多个客户端同时查询后同时修改导致的不同修改互相覆盖问题,这里就引出了乐观锁的方式。
主键的方式来确定修改的指定数据,乐观锁保证了修改的顺序执行,即可完美的解决了改操作上的幂等性和顺序性的问题,同时在分布式环境中也同样适用,前提是操作的是同个数据库里的同个表。
对于一些定时任务或对顺序操作不那么敏感的场景可以通过更新失败后在补偿措施里再次查询获取最新version进行更新。但是在高并发场景中不能这么做,如果并发中改的很频繁会不断进行补偿操作,最终修改的内容可能不是预期内容了。
当然说完乐观锁肯定少不了乐观锁,但这使用场景可能比较少,相比乐观锁来说颗粒度大了很多,在操作的性能上相比效率会低点。但也不妨作为一种方案。
- Token、分布式锁 说了【改】的幂等操作,都是绑定了已有的数据进行操作。但是对于【增】来说则没有这样的锚点,那只能通过系统中做标记来实现。做标记无非是Token了,这很好理解,每次用户打开新增页面时,向后台申请一个Token。在用户点击保存后,前台往后台传Token与后台的Token进行匹配,匹配成功后将系统中的该Token删除。这样就避免了由于网络波动或各种情况的重试导致的新增了多条相同的数据。
但对于非页面的接口调用,在准备调用新增接口前先调用获取Token的接口才能进行后续的数据新增操作。原理其实和上面的页面调用是一样的。但是对于分布式场景中该Token保存的位置就要进行集中管理,比如redis或zk(zk不建议将自己作为存数据的地方)这样的地方。
先拿许可再新增/修改的方式 其实可以有很多花样,对于银行这样对幂等性要求很高的的业务场景时,每一笔操作都会加入序列号和客户标识,原理其实一样,都是先拿许可再修改,无非是通过序列号来判断操作是否按序执行。
总结
幂等性在有些场景中其实很重要,对于金额或高并发新增和修改的场景尤为要注意幂等性的设计,普通的幂等性比如乐观锁+主键就可以了,而且代码上也很简单。
MyBaits这些框架里就带着版本号这些功能。但是新增场景中的幂等设计会相对来说多那么一两个方法和字段,如果真的确实有幂等的需求也不要偷懒,统一用注解做个切面就可以完成所有所有token的验证。
实现方法多,为了确保程序少出bug还是建议设计程序时候多多考虑可能的方方面面。