接口幂等性(防重令牌)(重复提交)

动态每日更新算法,想要一起学习的关注一下

文章目录

  • 一、接口幂等性
    • 1.接口幂等性的概念
    • 2.什么时候会发生接口幂等性
  • 二、防止接口幂等性
    • 1.token机制(防重令牌)(推荐)
    • 2. 数据库锁机制
    • 3. 分布式锁
    • 4. 唯一约束(md5加密)
    • 5.防重表
    • 6.全局id


一、接口幂等性

1.接口幂等性的概念

幂等性原本是数学上的概念,用在接口上就可以理解为:同一个接口,多次发出同一个请求,必须保证操作只执行一次。 调用接口发生异常并且重复尝试时,总是会造成系统所无法承受的损失,所以必须阻止这种现象的发生。

比如下面这些情况,如果没有实现接口幂等性会有很严重的后果: 支付接口,重复支付会导致多次扣钱 ;订单接口,同一个订单可能会多次创建。
在这里插入图片描述

2.什么时候会发生接口幂等性

  • 网络波动, 可能会引起重复请求
  • 用户重复操作,用户在操作时候可能会无意触发多次下单交易,甚至没有响应而有意触发多次交易应用
  • 使用了失效或超时重试机制(Nginx重试、RPC重试或业务层重试等)
  • 页面重复刷新
  • 使用浏览器后退按钮重复之前的操作,导致重复提交表单
  • 使用浏览器历史记录重复提交表单
  • 浏览器重复的HTTP请求
  • 定时任务重复执行
  • 用户双击提交按钮

二、防止接口幂等性

1.token机制(防重令牌)(推荐)

接口幂等性(防重令牌)(重复提交)_第1张图片
如上图所说:

  • 服务器发放一个token令牌,这个令牌可以是uuid或者别的什么验证码生成的,且服务器将这个token令牌存入Redis。
  • 当客户端再次访问服务端时会在请求头中带上token令牌
  • 服务端收到令牌与Redis进行比较,若不存在则认为是重复提交,若存在则进行lua脚本进行原子性删除,然后进行业务代码。

这里特别说明一下,为什么使用lua脚本
先看下面代码

 		String script= "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Long execute = redisTemplate.execute(new DefaultRedisScript<>(script,Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberResponseVo.getId()), submitVo.getOrderToken());
        if (execute == 0L) {
            //1.1 防重令牌验证失败
           return ;
        }else {
            //2. 进行业务代码

这是使用lua脚本的,如果不使用lua脚本,在服务器收到令牌时,服务器在和Redis进行交互时可能会出现网络延迟或者中断,来不及删除令牌,而客户端在此时已经重复提交,这样服务端会进行两次查询请求,且都能查询到令牌。

2. 数据库锁机制

接口幂等性(防重令牌)(重复提交)_第2张图片
数据库锁机制,但是都存在一些弊端,悲观锁存在锁定时间过长的问题。
而乐观锁和ElasticSearch一样,在每次更新操作时,都会带上version版本号,但是这种操作更适合更新操作,在查询操作上补起作用。

3. 分布式锁

接口幂等性(防重令牌)(重复提交)_第3张图片

分布式锁的介绍在Redis缓存穿透、雪崩、击穿以及分布式锁和本地锁
此文中介绍,可以借鉴一下,但是究其本质还是操作Redis

4. 唯一约束(md5加密)

接口幂等性(防重令牌)(重复提交)_第4张图片

数据库约束没啥好说的。这里详细介绍一下MD5盐值加密。
MD5加密就是对字符串进行加密,每一个字符串得到加密后的MD5字符串都是相同的,其实也就是和哈希表一样,这个字符串作为key,而生成的字符串作为value,他俩就是对应的。
但是这种做法容易被破解(可以使用穷举法),所以采用随机盐值加密,在MD5加密时使用 Spring Security。

		BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode = bCryptPasswordEncoder.encode(vo.getPassword());
        memberEntity.setPassword(encode);
        memberEntity.setMobile(vo.getPhone());
        memberEntity.setGender(0);
        memberEntity.setCreateTime(new Date());

5.防重表

接口幂等性(防重令牌)(重复提交)_第5张图片

以支付为例: 使用唯一主键去做防重表的唯一索引,比如使用订单号作为防重表的唯一索引,每一次请求都根据订单号向防重表中插入一条数据,插入成功说明可以处理后面的业务,当处理完业务逻辑之后删除防重表中的订单号数据,后续如果有重复请求,则会因为防重表唯一索引原因导致插入失败,直接返回操作失败,直到第一次请求返回结果,可以看出防重表作用就是加锁的功能。
注: 最好结合状态机幂等先判断一下

6.全局id

接口幂等性(防重令牌)(重复提交)_第6张图片

比如通过source来源 + 唯一序列号传入给后端,后端来判断请求是否重复,在并发时只能处理一个请求,其他相同并发请求要么返回请求重复,要么等待 前面请求执行完成后再执行。

你可能感兴趣的:(java,java,redis)