浅谈java接口的幂等性及解决方案

目录
  • 一、什么情况下需要幂等
  • 二、幂等性解决方案
    • 2.1 token机制(令牌)
    • 2.2 各种锁机制
    • 2.3 各种唯一约束
    • 2.4 防重表
    • 2.5 全局请求唯一id
  • 总结

一、什么情况下需要幂等

用户多次点击按钮
用户页面回退再次提交
微服务相互调用,由于网络问题,导致请求失败,feign触发重试机制

二、幂等性解决方案

2.1 token机制(令牌)

在加载页面详情时候,服务器会顺便生成一个token一起返回给前端,服务端同时也在Redis中保存这个token数据,前端并不展示这个token,但当页面点击提交按钮时候,会在携带上这个token参数,此时后端便会先校验前端提交请求的token与redis中的token是否一致,一致的话即是第一次请求,执行业务代码并在Redis中删除该token,当用户还是携带上次的验证码多次提交,此时服务器判断redis中验证码不存在,便可能是多次提交的情况,不再执行业务代码,保证业务的幂等性,不被重复执行。

问题1:先删除token还是后删除token

先删除可能导致, 业务确实没有执行,重试还带上之前token,由于防重设计导致,请求还是不能执行。后删除可能导致,业务处理成功,但是服务闪断,出现超时,没有删除token,别人继续重试,导致业务被执行两遍
解决:我们最好设计为先删除token,如果业务调用失败,就重新获取token再次请求。

问题2:如何保证token 获取、比较和删除必须是原子性

redis.get(token) 、token.equals、 redis del(token)如果这两个操作不是原子,可能导致,高并发下,都get到同样的数据,判断都成功,继续业务并发执行

解决:可以在redis使用lua脚本完成这个操作

//  redis+lua脚本 原子验证令牌防止重复提交攻击
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        String orderToken = "现在的令牌";
        //  return 0 失败  1 成功
        Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),Arrays.asList("要验证的KEY"), orderToken);

2.2 各种锁机制

1)数据库悲观锁

//0.开始事务
begin;
//1.查询出商品信息
select status from t_goods where id=1 for update;
//2.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update t_goods set status=2;
//4.提交事务
commit;

2)数据库乐观锁
适用读多写少的情况

update t_goods set status=2,version=version+1 where id=#{id} and version=#{version};

3)业务层分布式锁
redison可以解决,代码实现

    @Override
    @RedisLock(lockedPrefix = "model:replace:materialId:", timeOut = 5000)
    @Transactional(rollbackFor = Exception.class)
    public void changeSpuSync(@LockedObject Integer materialId, String userName) {
        log.info("模型表监听物料档案id={}是否修改了spu", materialId);
        ModelChangeSpuReq modelChangeSpuReq = new ModelChangeSpuReq(materialId, userName);
        this.dealChangeSpuSyncModel(modelChangeSpuReq);
    }

2.3 各种唯一约束

1)数据库的唯一约束:
利用主键的唯一性
2)redis set防重:
很多数据需要处理,只能被处理一次,比如我们可以计算数据的MD5将其放入redis的set,每次处理数据,先看这个MD5是否已经存在,存在就不处理。(百度上传文件秒传机制,如果该文件已经存在,就无需重复上传)

2.4 防重表

使用订单号orderNo做为去重表的唯一索引, 把唯一索引插入去重表, 再进行业务操作,且他们在同一个事中。这个保证了重复请求时,因为去重表有唯一约束,导致请求失败,避免了幂等问题。这里要注意的是,去重表和业务表应该在同一库中,这样就保证了在同一个事务,即使业务操作失败了,也会把去重表的数据回滚。这个很好的保证了数据一致性。

2.5 全局请求唯一id

前端每次调用接口请求时,生成一个唯一id,redis将数据保存到集合中(去重),存在即处理过。
全链路tranceId:可以使用Nginx设置每一个请求的唯一id;也可方便系统的链路追踪,该id并不能解决去重问题,当A系统调用B系统,B系统是否产生重试机制时候,可以根据这个id去判断

proxy_set header X-Request-ld $request_id;

总结

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

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

到此这篇关于浅谈java接口的幂等性及解决方案的文章就介绍到这了,更多相关java接口的幂等性内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java接口幂等性设计原理解析

    在微服务架构下,我们在完成一个订单流程时经常遇到下面的场景: 一个订单创建接口,第一次调用超时了,然后调用方重试了一次 在订单创建时,我们需要去扣减库存,这时接口发生了超时,调用方重试了一次 当这笔订单开始支付,在支付请求发出之后,在服务端发生了扣钱操作,接口响应超时了,调用方重试了一次 一个订单状态更新接口,调用方连续发送了两个消息,一个是已创建,一个是已付款.但是你先接收到已付款,然后又接收到了已创建 在支付完成订单之后,需要发送一条短信,当一台机器接收到短信发送的消息之后,处理较慢.消息中

  • 浅谈java接口的幂等性及解决方案

    目录 一.什么情况下需要幂等 二.幂等性解决方案 2.1 token机制(令牌) 2.2 各种锁机制 2.3 各种唯一约束 2.4 防重表 2.5 全局请求唯一id 总结 一.什么情况下需要幂等 用户多次点击按钮 用户页面回退再次提交 微服务相互调用,由于网络问题,导致请求失败,feign触发重试机制 二.幂等性解决方案 2.1 token机制(令牌) 在加载页面详情时候,服务器会顺便生成一个token一起返回给前端,服务端同时也在Redis中保存这个token数据,前端并不展示这个token,

  • 浅谈Java代码的 微信长链转短链接口使用 post 请求封装Json(实例)

    废话不多说,直接上代码 String longUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + MpUtil.APPID + "&redirect_uri=" + MpUtil.HOMEPAGE + "/nweixinLoginPc.fo%3Frandomcode=" + randomcode + "&response_type=co

  • 浅谈java的接口和C++虚类的相同和不同之处

    C++虚类相当于java中的抽象类,与接口的不同之处是: 1.一个子类只能继承一个抽象类(虚类),但能实现多个接口 2.一个抽象类可以有构造方法,接口没有构造方法 3.一个抽象类中的方法不一定是抽象方法,即其中的方法可以有实现(有方法体),接口中的方法都是抽象方法,不能有方法体,只有声明 4.一个抽象类可以是public.private.protected.default,接口只有public 5.一个抽象类中的方法可以是public.private.protected.default,接口中的

  • 浅谈java web中常用对象对应的实例化接口

    1. request对象 是javax.servlet.HttpServletRequest接口的实例化 2. response对象 是javax.servlet.HttpServletResponse接口的实例化 3. session 对象 是javax.servlet.HttpSession接口的实例化 4. application对象 是javax.servlet.ServletContext接口的实例化 以上是常用的对象 5. pageContext对象 是javax.servlet.j

  • 浅谈java 面对对象(抽象 继承 接口 多态)

    什么是继承? 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可. 多个类可以称为子类,单独这个类称为父类.超类或者基类. 子类可以直接访问父类中的非私有的属性和行为. 通过 extends 关键字让类与类之间产生继承关系. class SubDemo extends Demo{} //SubDemo是子类,Demo是父类 继承有什么好处? •提高代码的复用性. •让类与类之间产生了关系,是多态的前提. 继承的特点 1.Java只支

  • 浅谈java调用Restful API接口的方式

    摘要:最近有一个需求,为客户提供一些RestfulAPI接口,QA使用postman进行测试,但是postman的测试接口与java调用的相似但并不相同,于是想自己写一个程序去测试RestfulAPI接口,由于使用的是HTTPS,所以还要考虑到对于HTTPS的处理.由于我也是首次使用Java调用restful接口,所以还要研究一番,自然也是查阅了一些资料. 分析:这个问题与模块之间的调用不同,比如我有两个模块frontend和backend,frontend提供前台展示,backend提供数据支

  • 浅谈Java抽象类和接口的个人理解

    今天来说一波自己对Java中抽象类和接口的理解,含参考内容: 一.抽象类 1.定义: public abstract class 类名{} Java语言中所有的对象都是用类来进行描述,但是并不是所有的类都是用来描述对象的.我所理解的抽象类其实就是对同一类事物公共部分的高度提取,这个公共部分包括属性和行为.比如牛.羊.猪它们的公共属性是都有毛,公共行为是都哺乳,所以我们可以把公共部分抽象成一个哺乳类,含有属性毛和行为哺乳,当牛.羊.猪继承了哺乳类后也就有了哺乳的功能,至于怎么完成这个功能就需要自己

  • 浅谈Java中向上造型向下造型和接口回调中的问题

    最近回顾了一下java继承中的问题,下面贴代码: public class Base { protected String temp = "base"; public void fun(){ System.out.print("BASE fun()"); } public static void main(String[] args) { Base b =new Base();//实例化Base对象 b.fun(); //调用父类中fun()的方法 System.o

  • 浅谈Java 继承接口同名函数问题

    在Java中如果一个类同时继承接口A与B,并且这两个接口中具有同名方法,会怎么样? 动手做实验: interface A{ void fun(); } interface B{ void fun(); } interface C extends A,B{ } public class Test implements C{ @Override public void fun() { System.out.println("hehe"); } public static void main

  • 浅谈Java实现分布式事务的三种方案

    一.问题描述 用户支付完成会将支付状态及订单状态保存在订单数据库中,由订单服务去维护订单数据库.由库存服务去维护库存数据库的信息.下图是系统结构图: 如何实现两个分布式服务(订单服务.库存服务)共同完成一件事即订单支付成功自动减库存,这里的关键是如何保证两个分布式服务的事务的一致性. 尝试解决上边的需求,在订单服务中远程调用减库存接口,伪代码如下: 订单支付结果通知方法{ ​ 更新支付表中支付状态为"成功". ​ 远程调用减库存接口减库存. } 上边的逻辑说明: 1.更新支付表状态为本

随机推荐