[TOC] ## 一、方法级幂等性 * 主要作用于防止重复提交 * service实现类继承`SuperServiceImpl`,例子如下: ~~~ //例子1 private final static String LOCK_KEY_USERNAME = CommonConstant.LOCK_KEY_PREFIX+"username:"; String username = sysUser.getUsername(); boolean result = super.saveIdempotency(sysUser, lock , LOCK_KEY_USERNAME+username , new QueryWrapper<SysUser>().eq("username", username)); //例子2 private final static String LOCK_KEY_CLIENTID = CommonConstant.LOCK_KEY_PREFIX+"clientId:"; String clientId = client.getClientId(); boolean result = super.saveOrUpdateIdempotency(client, lock , LOCK_KEY_CLIENTID+clientId , new QueryWrapper<Client>().eq("client_id", clientId) , clientId + "已存在"); ~~~ ## 二、幂等性的定义 ### 2.1.什么是幂等性 HTTP/1.1中对幂等性的定义是:一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外)。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。 简单来说,是指无论调用多少次都不会有不同结果的 HTTP 方法。 ### 2.2.什么情况下需要幂等 就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品使用支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条。 ## 三、确定需要幂等性的范围 ### 3.1. 确定大范围 1. 请求层面:`读请求`、`写请求` * 其中读请求没有影响数据变化不需要做幂等性 2. 微服务层面:`负载均衡`、`api网关`、`业务逻辑层`、`数据访问层` * 其中负载均衡、api网关、业务逻辑层没有影响数据变化不需要做幂等性 > 总结:上面的范围里只有`数据访问层`和`写请求` ### 3.2. 数据访问层-写请求 1. Insert * **需要做幂等性** 2. Update * 直接更新某个值的:不需要做幂等性 * 累加操作等计算式的更新:**需要做幂等性** 3. Delete * 重复删除结果是一样:不需要做幂等性 ## 四、幂等性解决方案 * 没有最优的方案只有最适合的,因为这个和业务的逻辑强相关,所以就简单列举通用的方案 ### 4.1. Insert幂等方案 1. **数据字段增加唯一索引** * 优点:实现简单方便 * 缺点:影响数据库性能不适合该字段会被频繁更新的场景,唯一索引比普通索引在写操作上开销会大很多 2. **insert时使用临时表查询判断** ~~~ insert into sys_user(name,password) select 'admin', '123456' from dual where not exists(select 1 from sys_user where name='admin'); ~~~ * 优点:不需要创建唯一索引,语法相对通用(mysql和oracle) * 缺点:写操作会增加一次select子查询开销,增加sql语法的复杂度可读性较差 3. **细粒度分布式锁+select + insert** * 意思就是先加一个细粒度的分布式锁,然后select查一下是否存在,不存在再insert * 例子可参考文档最上面的`一、方法级幂等性`例子 * 优点:性能影响较少,使用的是细粒度锁,所以只有重复提交记录时才会阻塞 * 缺点:写操作会增加一次select开销,实现难度相对较大因为需要分布式细粒度锁 ### 4.2. Update计算操作幂等方案 * 这个需要结合具体业务来设计方案,常用的场景可通过版本号的方式来控制 * 在表里面添加`version`字段 ~~~ alter table sys_user add version int default 0; ~~~ * 然后更新的时候通过这个`version`来判断是否为过期无效操作,这是乐观锁的一种思路 ~~~ update sys_user set age=age+1, version=version+1 where version=xx ~~~