likes
comments
collection
share

避免浪费资源,解锁4种解决「重复请求」的方式🔥

作者站长头像
站长
· 阅读数 33

🏆本文收录于《Spring Boot从入门到精通》,专门攻坚指数提升。

本专栏致力打造最硬核 Spring Boot 从零基础到进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中…欢迎大家订阅持续学习。

环境说明:Windows10 + Idea2021.3.2 + Jdk1.8 + SpringBoot 2.3.1.RELEASE

 1. 前言

  在日常业务开发中,处理重复请求应该是我们需要经常注意的,在某些情况下是可能重复发送的,如果是查询类操作并无大碍,但其中有些请求是涉及写入操作的,一旦重复了,很可能会导致很严重的后果,例如交易的接口如果重复请求就可能会重复下单。还比如如下场景:

  1. 黑客拦截了请求,重放
  2. 前端/客户端因为某些原因重复请求了,或者用户在很短的时间内多次点击请求。
  3. 网关重发
  4. ….

  那么在Spring Boot 中,防止重复请求的方法有那些?像如何禁止用户重复点击等客户端操作将不在本文的讨论范畴(有点low),我要玩点高级的,同学们请看:

  • Token 验证

解析: 在页面中生成一个唯一的Token,然后在请求中携带此Token,服务端接收到请求后验证解析该Token是否是正确的。如果Token不正确,则认为是重复请求并过滤/拒绝该次请求。

  • Token 桶算法

解析: 在服务端中使用Token桶算法对请求进行限制,每个用户都有一个Token桶,每次请求需要从Token桶中获取一个Token,如果Token桶中没有用户此次请求的Token,则认为是重复请求并过滤/拒绝该次请求。

  • 限流控制

解析: 通过在请求接口中设置一个时间间隔,例如5秒钟,同一个用户在5秒钟内只能请求一次,如果再次请求则认为是重复请求并过滤/拒绝该次请求。

  • 接口密等性设计

解析: 通过设计接口的幂等性来防止重复请求。在设计接口时,确保同样的请求不管发送多少次都会得到相同的结果,这样即使用户发送了重复请求,服务端都返回同一种请求结果,也就不会对系统及数据造成影响。

  那么,具体如何一一实现呢?这将又会是干货满满的一期,全程无尿点不废话只抓重点教,具有非常好的学习效果,拿好小板凳准备就坐!希望学习的过程中大家认真听好好学,学习的途中有任何不清楚或疑问的地方皆可评论区留言或私信,bug菌将第一时间给予解惑,那么废话不多说,直接开整!Fighting!! 

避免浪费资源,解锁4种解决「重复请求」的方式🔥

2. 环境说明

本地的开发环境:

  • 开发工具:IDEA 2021.3
  • JDK版本: JDK 1.8
  • Spring Boot版本:2.3.1 RELEASE
  • Maven版本:3.8.2

3. 正文

  那么接下来,我就给同学们逐一具体介绍下如上四种防止重复请求的解决方案吧。

3.1 Token验证

3.1.1 算法思路

  在页面中生成一个唯一的Token,然后在请求中携带此Token,服务端接收到请求后验证解析该Token是否是正确的。如果Token不正确,则认为是重复请求并过滤/拒绝该次请求。

3.1.2 代码实现

①首先在页面中生成一个唯一的Token

 <form id="demoForm" action="/submit" method="post">
     <input type="hidden" name="token" value="${token}" />
     <!-- 其他表单元素 -->
     <button type="submit">提交</button>
 </form>

②在服务端中接收请求并验证Token

 @RestController
 public class DemoController{
 
     private Map<String, Boolean> tokenMap = new ConcurrentHashMap<>();
 
     @PostMapping("/submit")
     public String submit(HttpServletRequest request) {
         //获取请求携带的参数token值
         String token = request.getParameter("token");
         //校验是否存有
         if (!tokenMap.containsKey(token)) {
             //不存在则放过,存在则判定同一次请求
             tokenMap.put(token, true);
             // 处理请求
             return "success";
         } else {
             // 重复请求
             return "error";
         }
     }
 }

3.1.3 代码解析

  在上述代码中,DemoController是一个基于Spring的RESTful API控制器类,其中包含了一个用于防止重复提交的功能。具体来说,它针对来自客户端的POST请求,从请求参数中获取一个名为"token"的值,用于判断这个请求是否已经被处理过。然后,它使用一个ConcurrentHashMap来保存已经处理过的请求,以便在后续的请求中进行比对和校验。如果请求中的token在map中不存在,则将其添加到map中,并处理请求。如果已经存在,则说明请求是重复的,会返回错误信息。

3.2 Token桶算法

3.2.1 算法思路

        在服务端中使用Token桶算法对请求进行限制,每个用户都有一个Token桶,每次请求需要从Token桶中获取一个Token,如果Token桶中没有用户此次请求的Token,则认为是重复请求并过滤/拒绝该次请求。

3.2.2 代码实现

 @RestController
 public class DemoController {
 
     private Map<String, LinkedList<Long>> tokenBucketMap = new ConcurrentHashMap<>();
 
     @PostMapping("/submit")
     public String submit(HttpServletRequest request) {
         //获取用户id
         String userId = request.getParameter("userId");
         //通过用户id生成一个token桶
         LinkedList<Long> tokenBucket = tokenBucketMap.get(userId);
         if (tokenBucket == null) {
             tokenBucket = new LinkedList<>();
             tokenBucketMap.put(userId, tokenBucket);
         }
         long currentTime = System.currentTimeMillis();
         synchronized (tokenBucket) {
             if (tokenBucket.size() < 10 || currentTime - tokenBucket.getFirst() > 60000) {
                 tokenBucket.addLast(currentTime);
                 if (tokenBucket.size() > 10) {
                     tokenBucket.removeFirst();
                 }
                 // 处理请求
                 return "success";
             } else {
                 // 重复请求
                 return "error";
             }
         }
     }
 }

3.2.3 代码解析

  这是一个基于Spring框架的RESTful API的示例代码,使用了令牌桶算法对请求进行限流。具体来说,代码中实现了一个名为submit的POST请求处理函数,能够获取HTTP请求中的参数(userId),并将该用户对应的令牌桶(tokenBucket)放入一个MaptokenBucketMap)中进行管理。如果该用户对应的令牌桶不存在,代码会创建一个新的令牌桶并将其放入Map中进行管理。

  接下来,代码对令牌桶进行操作。如果令牌桶中当前令牌数量小于10个,或者距离令牌桶中第一个令牌的时间超过了60秒,那么当前请求可以被处理。此时代码会在令牌桶的尾部添加一个新的令牌,并将该请求标记为成功处理。如果令牌桶中当前令牌数量已经达到了10个,那么代码会移除令牌桶中的第一个令牌,再将新的令牌添加到令牌桶的尾部,从而保证令牌桶中始终只有10个令牌。

  如果当前请求不符合上述条件,代码会直接返回错误信息,标记该请求为重复请求。为了保证多线程并发处理时令牌桶的安全性,代码使用了synchronized关键字对令牌桶进行了加锁。乐观情况下,该令牌桶算法能够有效地限流用户的请求,防止接口被恶意攻击或者异常请求所耗尽。

3.3 限流控制

3.3.1 算法思路

  通过在请求接口中设置一个时间间隔,例如5秒钟,同一个用户在5秒钟内只能请求一次,如果再次请求则认为是重复请求并过滤/拒绝该次请求。

3.3.2 代码实现

@RestController
 public class DemoController {
 
     private Map<String, Long> lastRequestTimeMap = new ConcurrentHashMap<>();
 
     @PostMapping("/submit")
     public String submit(HttpServletRequest request) {
         //获取请求中的用户id
         String userId = request.getParameter("userId");
         //从map中查找上次请求的时间戳
         Long lastRequestTime = lastRequestTimeMap.get(userId);
         //如果为空或者上次请求的时间戳与当前时间做差大于5s,则视为新请求,否则重复请求。
         if (lastRequestTime == null || System.currentTimeMillis() - lastRequestTime > 5000) {
             lastRequestTimeMap.put(userId, System.currentTimeMillis());
             // 处理请求
             return "success";
         } else {
             // 重复请求
             return "error";
         }
     }
 }

3.3.3 代码解析

  在Controller中定义了一个Map对象lastRequestTimeMap,用于存储每个用户的最近一次请求时间戳。

  在submit方法中,通过HttpServletRequest获取请求中的用户id,然后从lastRequestTimeMap中查找该用户的最近一次请求时间戳。

  如果lastRequestTime为空,即用户第一次请求,或者当前时间与上次请求时间戳间隔大于5秒,则认为是新请求,将当前时间戳放入lastRequestTimeMap并返回"success"。

  如果当前时间与上次请求时间戳间隔小于等于5秒,则认为是重复请求,直接返回"error"。

  总而言之,目的是为了防止频繁重复的请求对后端系统造成过大的负载,通过控制重复请求的处理,可以有效节省服务器资源。

3.4 接口密等性设计

3.4.1 思路分析

  通过设计接口的幂等性来防止重复请求。在设计接口时,确保同样的请求不管发送多少次都会得到相同的结果,这样即使用户发送了重复请求,服务端都返回同一种请求结果,也就不会对系统及数据造成影响。

3.4.2 代码实现

 @RestController
 public class DemoController {
 
     private Map<String, String> resultCache = new ConcurrentHashMap<>();
 
     @PostMapping("/submit")
     public String submit(HttpServletRequest request) {
         //指定一个请求参数key。
         String key = request.getParameter("key");
         //将请求结果存放到key值里
         String result = resultCache.get(key);
         //如果result不为空则说明是重复请求。
         if (result != null) {
             // 返回之前的结果
             return result;
         } else {
             // 处理请求并缓存结果
             // 模拟一个结果赋值
             result = doBusinessLogic();
             // 将结果缓存
             resultCache.put(key, result);
             // 返回
             return result;
         }
     }
 
     private String doBusinessLogic() {
         // 业务逻辑处理
         return "success";
     }
 }

3.4.3 代码解析

  这是一个基于Spring框架的RESTful Web Service,其中包含一个使用了缓存的HTTP POST请求处理方法。代码实现步骤具体如下:

  1. @RestController 注解指明这是一个RESTful Web ServiceController类。

  2. resultCache是一个线程安全的并发HashMap,用于缓存请求的处理结果,key为请求参数中的某个值。

  3. submit方法是HTTP POST请求的处理方法,它接受一个HttpServletRequest对象作为参数。在方法中,首先解析出请求参数中的key值。

  4. 然后从resultCache中根据key值取出对应的处理结果,如果能取到结果,则直接返回之前的结果。

  5. 如果没有取到结果,则执行业务处理逻辑(doBusinessLogic方法),然后将处理结果放入resultCache中进行缓存,并返回处理结果。

  6. doBusinessLogic方法是一个示例性质的业务处理逻辑,这里直接返回了字符串"success"。

  7. 总体上,这个HTTP POST请求处理方法是使用了缓存机制的,通过缓存处理结果可以减少重复计算和响应时间,提高响应效率。

  ... ...

  好啦,以上就是4种解决重复请求的方式啦,同学们觉得哪种更好可以在文末进行投票讨论交流哦。

  以上就是我这期的全部内容啦,如果还想学习更多,你可以看看如下的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬。

「赠人玫瑰,手留余香」,咱们下期拜拜~~

4. 热文推荐

  若想学习更多,可以参考这篇专栏总结《2023最新首发,全网最全 Spring Boot 学习宝典(附思维导图)》本专栏致力打造最硬核 Spring Boot 进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中。欢迎大家订阅持续学习。

  在入门及进阶之途,我必助你一臂之力,系统性学习,从入门到精通,带你不走弯路,直奔终点;投资自己,永远性价比最高,都这么说了,你还不赶紧来学??

5. 文末

避免浪费资源,解锁4种解决「重复请求」的方式🔥

转载自:https://juejin.cn/post/7277115202234187817
评论
请登录