在分布式系统中,由于网络波动、机器故障或者操作重试等原因,很容易出现重复提交的情况。这就要求接口必须具有幂等性设计,即无论调用发多少次,接口的结果都是相同的。
几种常见的实现幂等性的设计方案:
请求唯一标识:
在每个请求中添加一个唯一标识,可以使用(UUID、redis自增id、mysql主键id、雪花算法)中的某一个方式生成,并存储到某个全局可见的地方(如Redis)(之所以要求全局可见是为了满足分布式系统每个结构单元对给唯一标识的一致同步性),每次请求到达时先检查该标识是否存在。如果已经存在,则说明当前请求被认为是重复的,直接返回之前保存的结果即可,否则将该标识存储起来并执行业务逻辑。
乐观锁:
对于带有状态转换的接口,可以引入版本号的概念,在返回数据中加上版本号信息。客户端在后续请求中将该版本号一并提交,服务端在处理时比较该版本号和当前版本号是否一致,若不一致说明请求是重复的,直接返回之前的结果即可。这个主要适用于写少读多的场景
对于乐观锁,主要有两种实现方式:
1. 版本号法——详细看mybatisplus笔记2. CAS法——原理和版本号法一致,不过无需创建多一个版本号,只要进行操作前再次查询一遍该要被操作的数据值,只要和先前的一样就行
Token机制:
通过使用Token进行幂等性保证,当某个接口第一次访问时,后台生成一个唯一Token传给前端,在后续的接口请求中,前端将Token带上,保证相同Token只能访问一次操作,重复请求直接被拒绝。
Redis锁机制:
使用Redis作为分布式缓存,在接口开始处理前获取一个分布式锁(这里主要采用的是redis的setnx指令),如果获取失败则说明业务逻辑正在处理,直接返回之前保存的结果;否则继续执行业务处理和幂等性校验,并在最后释放锁。
总之,在分布式系统中实现接口的幂等性设计需要考虑到并发和延迟方面的问题,下面是几个注意点:
-
避免并发时出现竞态条件(race condition)或者资源泄露导致内存溢出等问题。
-
要正确处理每个接口的响应顺序,尤其是异步 / 多线程场景下,必须确保先处理完成的请求先返回,并把最终结果保存到缓存中。
-
应用程序必须正确处理各种异常情况、超时情况和网络故障等,避免因奔溃导致数据丢失或者脏数据等问题。
-
最好能够全局统一管理幂等性,减少多人开发带来的混乱,例如对于同样功能的方法,在开发时可以统一考虑如何做幂等性处理,在运行时可以共享相同的策略。