Java接口幂等性的解决方案:

java 语音中,同一个接口相同的参数多次和一次请求产生的效果是一样,这样的过程即被称为满足幂等性

//这中情况无论执行多少次,结果都不受影响,是幂等的。
update user set age = 25 where user_id = 2

//这样的更新语句每执行一次,结果都会不一样,是非幂等的。
update user set user_num = user_num + 1 where user_id = 2

而对非幂等性的接口防止重复提交(如果幂等性的接口重复提交,理论上不会有问题),就是我们今天要解决的问题:

1、jvm加锁方式

调用java中的内置锁synchronized或显示锁Lock加锁。

单体架构下,新增数据,新增之前需要先查询数据库是否已经存在此数据,查询之前加锁,执行完插入数据后释放锁。

注意:此方案不适合微服务集群环境下,此类锁值对当下的单体服务起作用;多个通知经过负载均衡转发到不同的机器,上面的锁就不起效了。

2、数据库加悲观锁方式:

新增之前查询,数据库悲观锁类似于方式二中的Lock,只不过是依靠数据库来实现的。数据中悲观锁使用for update来实现 

select * from user where id = 1 for update;

重点在于 for update,解释如下:
1).当线程A执行for update,数据会对当前记录加锁,其他线程执行到此行代码的时候,会等待线程A释放锁之后,才可以获取锁,继续后续操作。
2).事物提交时,for update获取的锁会自动释放。

此方案能保证接口的幂等性,不过存在缺点:
如果业务处理比较耗时,并发情况下,后面线程会长期处于等待状态,占用了很多线程,让这些线程处于无效等待状态,我们的服务中的线程数量一般都是有限的,如果大量线程由于获取  for update锁处于等待状态,不利于系统并发操作,大大降低了性能。

3、乐观锁CAS思想方案:

/**
*每次添加都加一个版本号,因此第二次重复添加时更新版本号失败 回滚事务
 代码思想如下:自己灵活设计
*/
update t_order set status = 1 where order_id = trade_no and status = 0;
//上面的update操作会返回影响的行数num,业务代码模型
if(num==1){
 //表示更新成功
 提交事务;
}else{
 //表示更新失败
 回滚事务;
}

 

4、数据库中唯一约束方法:

try{
    insert into user (type_type,order_id) values (1,order_no);
    //提交本地事务:
}catch(Exception e){
    //回滚本地事务;
}

注意:参数 order_no 在数据库一定加了唯一性 索引,以保住此参数的值在数据库唯一存在;第一次插入成功,第二次失败并回滚事务。

5、前端web页面触发接口后 置灰(多长时间内不能重复触发),这是幂等问题解决的第一道防火墙。

6、分布式锁解决方案:

通过redis 或其他nosql工具,用分布式锁的方法解决,进入主业务时加锁(可以同时设置超时时间),事务结束时释放锁。单体架构和集群环境均适合此方案。尤其在微服务集群环境下属于最佳解决方案。

7、总之,一般情况下,几种方式的优选级使用顺序可以这样:分布式锁 > 乐观锁 > JVM锁 > 唯一约束 > 数据库悲观锁

当然,幂等性设计不能脱离业务实际来讨论,一定要根据实际业务场景选择合适的幂等性解决方案。