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

2023年 9月 12日 56.5k 0

👨‍🎓作者:bug菌
✏️博客:CSDN、掘金、infoQ、51CTO等
🎉简介:CSDN|阿里云|华为云|51CTO等社区博客专家,历届博客之星Top30,掘金年度人气作者Top40,51CTO年度博主Top12,掘金 | InfoQ | 51CTO等社区优质创作者,全网粉丝合计15w+ ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板等海量资料。

...

✍️温馨提醒:本文字数:1999字, 阅读完需:约 5 分钟

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

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

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

 1. 前言

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

  • 黑客拦截了请求,重放
  • 前端/客户端因为某些原因重复请求了,或者用户在很短的时间内多次点击请求。
  • 网关重发
  • ….
  •   那么在Spring Boot 中,防止重复请求的方法有那些?像如何禁止用户重复点击等客户端操作将不在本文的讨论范畴(有点low),我要玩点高级的,同学们请看:

    • Token 验证

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

    • Token 桶算法

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

    • 限流控制

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

    • 接口密等性设计

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

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


    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

     
         
         
         提交
     
    

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

     @RestController
     public class DemoController{
     
         private Map 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 tokenBucketMap = new ConcurrentHashMap();
     
         @PostMapping("/submit")
         public String submit(HttpServletRequest request) {
             //获取用户id
             String userId = request.getParameter("userId");
             //通过用户id生成一个token桶
             LinkedList tokenBucket = tokenBucketMap.get(userId);
             if (tokenBucket == null) {
                 tokenBucket = new LinkedList();
                 tokenBucketMap.put(userId, tokenBucket);
             }
             long currentTime = System.currentTimeMillis();
             synchronized (tokenBucket) {
                 if (tokenBucket.size()  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 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 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请求处理方法。代码实现步骤具体如下:

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

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

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

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

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

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

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

  •   ... ...

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

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

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

    4. 热文推荐

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

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

    5. 文末

           我是bug菌,CSDN | 阿里云 | 华为云 | 51CTO 等社区博客专家,历届博客之星Top30,掘金年度人气作者Top40,51CTO年度博主Top12,掘金 | InfoQ | 51CTO等社区优质创作者,全网粉丝合计15w+ ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板等海量资料。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论