分布式环境下的重复请求防护:非Redis锁替代方案全解析

目录

引言

方案一:前端防护策略

方案二:后端协同控制

方案三:流量控制与过滤

滑动窗口限流

布隆过滤器

方案四:基于框架的实践方案

多层防护策略与最佳实践

总结        


引言

        在Web应用开发中,防止用户重复点击提交是一个常见却棘手的问题。重复提交不仅会导致数据重复、资源浪费,在交易、下单等场景中甚至可能造成严重的业务异常。通常情况下,我们会使用Redis分布式锁来解决这个问题,但当Redis不可用或由于架构限制无法使用时,我们需要其他可靠的替代方案。
        本文将深入探讨几种不依赖Redis的防重复点击方案,从前端到后端,从简单到复杂,分析各自的实现原理、适用场景以及优缺点,帮助开发者根据自身业务需求选择最合适的解决方案。

方案一:前端防护策略

        最直接的防重复点击方案是在前端实现按钮防抖。当用户点击按钮后,立即将按钮禁用或置灰,防止用户进行二次点击。

// 伪代码:防抖按钮实现示例
function debounceButton(btn, time = 2000) {if (btn.disabled) return;// 禁用按钮btn.disabled = true;btn.classList.add('disabled');// 发送请求sendRequest().finally(() => {// 请求完成后恢复按钮状态(也可以根据业务需要不恢复)setTimeout(() => {btn.disabled = false;btn.classList.remove('disabled');}, time);});
}

优点:

  • 实现简单,无需后端配合
  • 用户体验友好,提供直观的视觉反馈
  • 适用于大多数普通业务场景

局限性:

  • 网络延迟可能导致禁用不及时
  • 技术熟练的用户可通过浏览器开发工具绕过前端限制
  • 无法防止通过接口工具(如Postman)直接调用API的重复请求

开发经验分享:在实际项目中,我发现单纯依赖前端防抖虽然能解决80%的问题,但在支付等关键业务中,仍需结合后端验证机制,构建多层防护。

 

方案二:后端协同控制

        Token机制是一种有效的服务端防重复提交方案。核心思想是为每次操作生成唯一标识,确保同一标识只被处理一次。
工作流程:

  1. 用户访问页面时,后端生成唯一token并返回前端
  2. 用户提交请求时携带该token
  3. 后端验证token是否已被使用,未使用则标记为已使用并处理请求
  4. 如token已使用,拒绝请求并返回错误提示

 后端实现示例:

// 伪代码
@RestController
public class OrderController {private final Map<String, Boolean> tokenMap = new ConcurrentHashMap<>();// 获取token@GetMapping("/getToken")public Result getToken() {String token = UUID.randomUUID().toString();tokenMap.put(token, false); // false表示未使用return Result.success(token);}// 提交订单@PostMapping("/submitOrder")public Result submitOrder(@RequestParam String token, @RequestBody OrderDTO order) {// 使用数据库事务保证原子性return transactionTemplate.execute(status -> {// 查询token使用状态Boolean used = tokenMap.get(token);if (used == null) {return Result.error("无效的token");}if (used) {return Result.error("请勿重复提交");}// 标记token为已使用tokenMap.put(token, true);// 处理订单逻辑orderService.createOrder(order);return Result.success();});}
}

数据库实现方案:
        在实际生产环境中,可使用数据库存储token状态,结合事务确保原子性:

-- 创建token表
CREATE TABLE submission_token (token VARCHAR(36) PRIMARY KEY,used BOOLEAN DEFAULT FALSE,create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,expire_time TIMESTAMP
);-- 验证并标记token (使用悲观锁)
BEGIN TRANSACTION;
SELECT used FROM submission_token WHERE token = ? FOR UPDATE;
-- 如果未使用,则标记为已使用
UPDATE submission_token SET used = TRUE WHERE token = ?;
COMMIT;

优点:

  • 服务端验证,安全性高
  • 可靠性强,能防止各种渠道的重复请求
  • 结合数据库事务,保证操作原子性

局限性:

  • 实现复杂度较高
  • 需要额外的存储空间管理token
  • 不适合所有场景下的性能要求

方案三:流量控制与过滤

滑动窗口限流

        滑动窗口限流是控制请求频率的有效方法,可以限制用户在指定时间窗口内的请求次数,从而防止重复提交。

直通车:高并发系统中的限流策略:滑动窗口限流与Redis实现-CSDN博客

 

//伪代码
public class SlidingWindowRateLimiter {// 用户请求记录: <用户ID, 请求时间列表>private Map<String, LinkedList<Long>> requestRecords = new ConcurrentHashMap<>();// 窗口大小(毫秒)private final long windowSize;// 窗口内允许的最大请求数private final int maxRequests;public SlidingWindowRateLimiter(long windowSize, int maxRequests) {this.windowSize = windowSize;this.maxRequests = maxRequests;}/*** 判断请求是否被允许* @param userId 用户ID* @return 是否允许请求*/public synchronized boolean allowRequest(String userId) {long currentTime = System.currentTimeMillis();// 获取用户的请求记录,如不存在则创建LinkedList<Long> records = requestRecords.computeIfAbsent(userId, k -> new LinkedList<>());// 移除窗口外的过期记录while (!records.isEmpty() && currentTime - records.getFirst() > windowSize) {records.removeFirst();}// 判断窗口内请求是否超过限制if (records.size() < maxRequests) {// 记录新请求records.addLast(currentTime);return true;}return false;}
}

使用示例:

// 创建限流器: 2秒内最多允许1次请求
SlidingWindowRateLimiter limiter = new SlidingWindowRateLimiter(2000, 1);@PostMapping("/api/submit")
public Result submit(@RequestHeader String userId) {// 检查限流if (!limiter.allowRequest(userId)) {return Result.error("请求过于频繁,请稍后再试");}// 处理正常业务逻辑return businessService.process();
}

适用场景:

  • 适用于高频操作的防重复控制
  • 对性能要求较高的场景
  • 允许在短时间内丢弃部分请求的业务

布隆过滤器

        布隆过滤器是一个空间效率很高的概率型数据结构,用于判断一个元素是否存在于集合中。它可以快速进行"可能存在"或"一定不存在"的判断,适合作为防重复提交的快速过滤层。

直通车:布隆过滤器原理介绍和典型应用案例_布隆过滤器案例-CSDN博客

// 伪代码
public class BloomFilterValidator {private BitSet bitSet;private int size;private int hashFunctions;public BloomFilterValidator(int size, int hashFunctions) {this.size = size;this.hashFunctions = hashFunctions;this.bitSet = new BitSet(size);}// 添加元素public void add(String element) {for (int i = 0; i < hashFunctions; i++) {int hash = getHash(element, i);bitSet.set(hash);}}// 判断元素是否可能存在public boolean mightContain(String element) {for (int i = 0; i < hashFunctions; i++) {int hash = getHash(element, i);if (!bitSet.get(hash)) {return false; // 一定不存在}}return true; // 可能存在}// 简单哈希函数private int getHash(String element, int seed) {int hash = element.hashCode();hash = hash * seed % size;return Math.abs(hash) % size;}
}

应用架构:

  1. 使用布隆过滤器进行快速判断
  2. 如果过滤器返回"可能存在",则进一步查询数据库确认
  3. 如果确实是重复提交,则拒绝请求
// 伪代码
@Service
public class OrderSubmitService {private BloomFilterValidator bloomFilter = new BloomFilterValidator(10000, 3);private OrderRepository orderRepository;public Result submitOrder(OrderDTO orderDTO) {// 生成请求标识String requestId = generateRequestId(orderDTO);// 布隆过滤器快速检查if (bloomFilter.mightContain(requestId)) {// 可能是重复请求,进一步查询数据库确认if (orderRepository.existsByRequestId(requestId)) {return Result.error("订单已提交,请勿重复操作");}}// 处理订单并保存请求标识Order order = orderService.createOrder(orderDTO);bloomFilter.add(requestId); // 添加到布隆过滤器return Result.success(order);}// 生成请求唯一标识private String generateRequestId(OrderDTO orderDTO) {// 根据关键业务字段生成唯一标识return DigestUtils.md5Hex(orderDTO.getUserId() + orderDTO.getProductId() + orderDTO.getAmount() + System.currentTimeMillis());}
}

优点:

  • 空间效率高,内存占用小
  • 查询速度快,适合大规模数据
  • 作为快速过滤层,降低数据库查询压力

局限性:

  • 有一定的误判率(误报)
  • 不能单独使用,需要与数据库等确切存储配合
  • 不支持删除元素,需要定期重建

方案四:基于框架的实践方案

        参考RuoYi框架的实现,RuoYi框架提供了一种基于表单信息的防重复提交方案,核心思想是将表单内容、提交时间等信息进行校验,限制相同内容在短时间内的重复提交。

/*** 自定义注解防止表单重复提交*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {/*** 间隔时间(ms),小于此时间视为重复提交*/int interval() default 5000;
}/*** 防重复提交拦截器*/
@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {private final FormTokenService tokenService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {if (handler instanceof HandlerMethod) {HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);if (annotation != null) {// 验证表单是否重复提交return validateFormRepeat(request, annotation);}}return true;}private boolean validateFormRepeat(HttpServletRequest request, RepeatSubmit annotation) {// 获取请求参数内容String formContent = getFormContent(request);// 获取请求路径String requestPath = request.getRequestURI();// 用户标识String userToken = getUserToken(request);// 生成表单唯一标识String formKey = DigestUtils.md5Hex(requestPath + userToken + formContent);// 检查数据库中是否存在且是否在规定时间内FormSubmitRecord record = formRecordRepository.findByFormKey(formKey);if (record != null) {long interval = System.currentTimeMillis() - record.getSubmitTime();if (interval < annotation.interval()) {return false; // 判定为重复提交}}// 记录本次提交saveFormRecord(formKey);return true;}// 获取表单内容private String getFormContent(HttpServletRequest request) {// 获取POST内容或GET参数,进行排序和标准化处理// ...具体实现略}// 保存表单记录private void saveFormRecord(String formKey) {FormSubmitRecord record = new FormSubmitRecord();record.setFormKey(formKey);record.setSubmitTime(System.currentTimeMillis());formRecordRepository.save(record);}
}

使用示例

@RestController
public class UserController {@PostMapping("/user/register")@RepeatSubmit(interval = 10000) // 10秒内不允许重复提交public Result register(@RequestBody UserRegisterForm form) {// 注册逻辑return userService.register(form);}
}

优点:

  • 配置简便,使用注解即可实现
  • 支持配置不同接口的防重复策略
  • 基于表单内容校验,更符合业务语义

局限性:

  • 依赖于请求内容,不适用于所有场景
  • 需要存储请求内容的哈希值
  • 配置不当可能影响用户体验

多层防护策略与最佳实践

        在实际项目中,往往需要综合使用多种防重复点击方案,构建多层防护机制。

前端第一道防线:

  • 实现按钮防抖和禁用
  • 合理设置UI反馈,提升用户体验

API网关层:

  • 实现基本的流量控制和限流
  • 对异常请求进行预警和拦截

应用服务层:

  • 实现token验证或表单校验机制
  • 使用布隆过滤器进行快速过滤

数据持久层:

  • 利用数据库约束和事务保证数据一致性
  • 实现业务层面的幂等性检查

总结        

        ​​​​​​​防止重复点击是一个需要从多角度综合考虑的问题。虽然Redis分布式锁提供了一种优雅的解决方案,但在Redis不可用的场景下,我们仍有多种替代方案可以选择。
        理想的防重复点击方案应当在安全性、可靠性和性能之间找到平衡点。在实际应用中,应根据业务特点、技术栈和性能要求等因素,选择合适的方案或组合方案。同时,也应当注意用户体验,避免过度限制影响正常操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/39279.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

4.1 C#获取目录的3个方法的区别

C#中常用有如下3个获取目录的方式如下 1.Directory.GetCurrentDirectory():获取当前工作目录&#xff0c;工作目录可能被用户或其他代码修改。尽量少用。&#xff08;似乎只要在运行中使用另存为或者打开某个文件夹&#xff0c;当前工作目录就修改&#xff09; 2.Application…

【漏洞复现】Next.js中间件权限绕过漏洞 CVE-2025-29927

什么是Next.js&#xff1f; Next.js 是由 Vercel 开发的基于 React 的现代 Web 应用框架&#xff0c;具备前后端一体的开发能力&#xff0c;广泛用于开发 Server-side Rendering (SSR) 和静态站点生成&#xff08;SSG&#xff09;项目。Next.js 支持传统的 Node.js 模式和基于边…

MCU-芯片时钟与总线和定时器关系,举例QSPI

时钟源&#xff1a; 时钟源为系统时钟提供原始频率信号&#xff0c;系统时钟则通过&#xff08;分频、倍频、选择器&#xff09;成为整个芯片的“主时钟”&#xff0c;驱动 CPU 内核、总线&#xff08;AHB、APB&#xff09;及外设的运行。 内部时钟源&#xff1a; HSI&#x…

使用 ByteDance 的 UI-TARS Desktop 探索 AI 驱动的 GUI 自动化新前沿

文章目录 UI-TARS Desktop 是什么&#xff1f;技术亮点应用场景如何快速上手&#xff1f;与其他技术的对比未来展望结语 随着人工智能技术的快速发展&#xff0c;AI 正在从单纯的文本生成和图像识别迈向更复杂的交互场景。ByteDance 近期推出的 UI-TARS Desktop&#xff08;基于…

DockerFile制作镜像(Dockerfile Creates an Image)

DockerFile制作镜像 hub.docker.com 搜索到的 Redis官方镜像&#xff0c;提示我们可以创建自己的 DockerFile 来添加 redis.conf 文件&#xff1a; 于是&#xff0c;我准备进行首次 DockerFile 的制作尝试。 一、准备工作 1.1 下载 redis.conf 我的方案是从 GitHub 上下载 …

C++List模拟实现|细节|难点|易错点|全面解析|类型转换|

目录 1.模拟代码全部 2.四大块代码理解 1.最底层&#xff1a;ListNode部分 2.第二层&#xff1a;ListIterator部分 3.第三层&#xff1a;ReserveListIterator部分 4最终层&#xff1a;List 1.模拟代码全部 using namespace std; template<class T> struct ListNode …

如何让自动驾驶汽车“看清”世界?坐标映射与数据融合概述

在自动驾驶领域,多传感器融合技术是实现车辆环境感知和决策控制的关键。其中,坐标系映射和对应是多传感器融合的重要环节,它涉及到不同传感器数据在统一坐标系下的转换和匹配,以实现对车辆周围环境的准确感知。本文将介绍多传感器融合中坐标系映射和对应的数学基础和实际应…

鸿蒙开发之背景图片的使用

在鸿蒙开发中&#xff0c;设置背景图片是提升应用界面视觉效果的重要一环。以下是关于鸿蒙开发中背景图片使用的详细方法&#xff1a; 一、通过XML布局文件设置背景图片 1.使用Image组件设置背景图片 在XML布局文件中&#xff0c;可以使用Image组件来设置背景图片。通过ohos…

如何在 HTML 中创建一个有序列表和无序列表,它们的语义有何不同?

大白话如何在 HTML 中创建一个有序列表和无序列表&#xff0c;它们的语义有何不同&#xff1f; 1. HTML 中有序列表和无序列表的基本概念 在 HTML 里&#xff0c;列表是一种用来组织信息的方式。有序列表就是带有编号的列表&#xff0c;它可以让内容按照一定的顺序呈现&#…

c++malloc出来的对象调用构造-------定位new

前言:之前在搓高并发内存池的时候就在想,类对象不能调用自身的构造函数,那直接申请内存出来的类对象岂不是很难受,然后我这两天仔细研究了一下,发现其实构造函数也可以显示去调用,而且含不限量,故做此文 在c中一个类对象不能直接调用自身的构造 class A { public:A() {cout &l…

ElementUI时间选择、日期选择

如大家所发现的&#xff0c;由于ElementUI 时间选择器&#xff0c;日期选择器&#xff0c;时间日期选择器点击清除按钮时&#xff0c;v-model 所绑定的属性值会变成 null&#xff0c;所以当使用 ElementUI 时间选择器&#xff0c;日期选择器&#xff0c;时间日期选择器 时&…

一篇文章入门Python Flask框架前后端数据库开发实践(pycharm在anaconda环境下)

Python Flask 是一个轻量级的 Web 应用框架&#xff0c;也被称为微框架。它以简洁、灵活和易于上手的特点而受到开发者的喜爱。 核心特点 轻量级&#xff1a;Flask 核心代码简洁&#xff0c;仅包含 Web 开发的基本功能&#xff0c;不强制使用特定的数据库、模板引擎等&#xf…

ctfshow WEB web2

1.查当前数据库名称 or 11 union select 1,database(),3 limit 1,2;#-- 得到数据库名称web2 2.查看数据库表的数量 or 11 union select 1,(select count(*) from information_schema.tables where table_schema web2),3 limit 1,2;#-- 得到数据库表数量为2 3.查表的名字 第…

【Git】--- 分支管理

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; Git 本篇博客我们来介绍Git的一个重要功能之一 ---- 分支。我们将讲解关于分支的各种操作&#xff0c;以及如何帮助我们进行开发。 &#x1f3e0; 理解分支…

系统思考与心智模式

“问题不是出在我们做了多少&#xff0c;而是出在我们做了什么。” — 赫尔曼凯恩 “一分耕耘一分收获”&#xff0c;这似乎是我们脑海中根深蒂固的心智模式。今天&#xff0c;我在一家餐厅用餐&#xff0c;店员告诉我&#xff0c;打卡收藏可以获得一份小食。没过多久&#xf…

纯文本驱动的数据可视化革命——AI生成图表「图表狐」全场景深度解析

一、技术架构重定义 图表狐核心能力边界 ✅ 纯文本输入&#xff1a;支持任意格式文字描述&#xff08;会议纪要/邮件/手写笔记&#xff09; ✅ 智能解析引擎&#xff1a; 实体识别&#xff08;数值/时间/分类维度&#xff09; 语义纠错&#xff08;自动修复错别字/单位混乱&…

多线程 --- 进程和线程的基本知识

进程 前面我们提到了一个概念是&#xff0c;多任务操作系统&#xff0c;即希望该系统能够同时运行多个程序。本质上说&#xff0c;进程&#xff0c;就算用来解决”并发编程“这样的问题的。 在一些特定的情况下&#xff0c;进程的表现&#xff0c;其实并不能很好的解决”并发…

SCI英文论文Accepted后的第一步——Rights and Access

SCI英文论文Accepted后的第一步——Rights and Access 目录 SCI英文论文Accepted后的第一步——Rights and AccessBased on information provided the embargo period/end date is 24 months. 因为选择闭源**Rights and Access(版权与访问权限)**环节是关键第一步,具体操作流…

流程控制语句

python中的流程控制语句有三种&#xff0c;顺序结构、条件结构和循环结构 1&#xff09;顺序结构&#xff1a; 从上往下&#xff0c;从左到右&#xff0c;依次逐行执行。 #顺序结构python print(start) print(hello world1 ) print(hello world2 ) print(hello world3 ) pri…

2.4 关键路径法

项目进度管理核心工具全解析 &#x1f680; 一、关键路径法&#xff08;CPM&#xff09;精要 1. 核心概念图解 #mermaid-svg-5MOABZm9lR8A53ss {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-5MOABZm9lR8A53ss .e…