网站首页 > 精选教程 正文
嘿,各位后端开发的伙伴们!在日常开发工作里,大家是不是经常碰到这样的问题:用户在前端一顿疯狂操作,猛点按钮或者飞速提交表单,结果后端接口就收到了一连串重复请求。这不但浪费服务器资源,还可能引发数据不一致等诸多麻烦事。今天,咱们就来深入探讨在 Spring Boot3 中,如何巧妙实现接口防抖操作,一举解决这个令人头疼的问题。
问题引入
设想你正在开发一个电商系统的订单提交接口。用户在付款页面,由于网络卡顿,没看到提交按钮的响应,一着急就多点了好几次。此时,要是接口没有防抖机制,订单系统很可能会收到多条完全一样的订单提交请求,进而导致重复下单、库存混乱,给用户和商家都带来极大困扰。再比如在社交平台的点赞接口中,用户不小心手抖多点了几下,要是没有接口防抖,用户的点赞数就会瞬间不正常地飙升,这显然不是我们期望的结果。那么,怎样才能避免这种情况呢?这就是我们今天要全力攻克的接口防抖难题。
在当今高并发的互联网应用场景下,接口的稳定性和性能极为关键。接口防抖作为一种常见的优化方式,主要功能是防止在短时间内多次触发同一操作。用户端可能由于网络延迟、误操作等原因,在短时间内多次发送相同请求。如果后端接口不加以管控,这些请求一股脑涌入服务器,服务器就得重复处理相同业务逻辑,这无疑会加重服务器负担,严重时甚至可能致使系统崩溃。而且,多次处理重复请求还可能引发数据一致性问题,比如重复向数据库插入相同数据。所以,实现接口防抖对于提升系统性能、保障数据准确性和稳定性意义重大。
解决方案
基于注解和 AOP 实现防抖
定义防抖注解
首先,我们自定义一个防抖注解,比如@RequestLock。在这个注解里,我们可以设置一些参数,像timeUnit用来指定锁时间的单位(默认可以设为秒),delimiter用于参数分隔,方便生成唯一的锁key。示例代码如下:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLock {
long value() default 1;
TimeUnit timeUnit() default TimeUnit.SECONDS;
String delimiter() default "&";
}
生成唯一key
为了准确判断两次请求是否重复,我们需要生成一个唯一的key。这个key可以由请求参数和注解中的配置共同组成。例如,对于一个添加用户的接口,请求参数中有用户名userName和用户手机号userPhone,我们可以选择这两个参数来生成key。假设userName是 “张三”,userPhone是 “123456”,按照注解中设置的分隔符 “&”,生成的key就是 “张三 & 123456”,再加上注解中设置的锁前缀,就构成了一个唯一标识此次请求的key。具体生成key的代码逻辑如下(这里以反射获取方法参数为例):
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
public class RequestKeyGenerator {
public static String getLockKey(ProceedingJoinPoint joinPoint, RequestLock requestLock) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Parameter[] parameters = method.getParameters();
Object[] args = joinPoint.getArgs();
StringBuilder keyBuilder = new StringBuilder(requestLock.prefix());
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
Object arg = args[i];
if (parameter.isAnnotationPresent(RequestKeyParam.class)) {
if (arg != null) {
if (keyBuilder.length() > requestLock.prefix().length()) {
keyBuilder.append(requestLock.delimiter());
}
keyBuilder.append(arg.toString());
}
}
}
return keyBuilder.toString();
}
}
这里RequestKeyParam是一个自定义注解,用于标记哪些参数参与key的生成。
实现防抖逻辑(基于 Redis)
我们利用 Redis 的setnx命令来实现防抖逻辑。setnx(即SET if Not eXists)命令可以判断指定的key是否存在,如果不存在就设置这个key并返回成功,否则直接返回失败。我们在获取锁成功后,给锁设置一个过期时间,防止死锁。当方法执行完成后,手动删除锁。示例代码如下(基于 Spring Data Redis):
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
public class RequestLockAspect {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Around("@annotation(requestLock)")
public Object around(ProceedingJoinPoint joinPoint, RequestLock requestLock) throws Throwable {
String lockKey = RequestKeyGenerator.getLockKey(joinPoint, requestLock);
boolean success = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "locked", requestLock.value(), requestLock.timeUnit());
if (!success) {
throw new RuntimeException("请求过于频繁,请稍后再试");
}
try {
return joinPoint.proceed();
} finally {
stringRedisTemplate.delete(lockKey);
}
}
}
应用防抖注解
最后,在需要防抖的接口方法上加上@RequestLock注解就大功告成啦。比如一个保存用户信息的接口:
@PostMapping("/saveUser")
@RequestLock(value = 2, timeUnit = TimeUnit.SECONDS)
public ResponseEntity<String> saveUser(@RequestBody User user) {
// 保存用户信息的业务逻辑
return ResponseEntity.ok("用户信息保存成功");
}
这个接口设置了 2 秒的防抖时间,在这 2 秒内,如果收到相同参数的重复请求,就会提示 “请求过于频繁,请稍后再试”。
使用 Guava 的 RateLimiter 实现限流防抖
引入依赖
首先在pom.xml文件中引入 Guava 的依赖:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.2-jre</version>
</dependency>
配置 RateLimiter
在 Spring Boot 的配置类中,我们可以创建一个RateLimiter的实例,并设置每秒允许通过的请求数。例如,我们设置每秒只允许通过 5 个请求:
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RateLimiterConfig {
@Bean
public RateLimiter rateLimiter() {
return RateLimiter.create(5);
}
}
在接口中使用 RateLimiter
在需要防抖的接口方法中,获取RateLimiter实例并调用tryAcquire方法尝试获取令牌。如果获取成功,说明请求在限流范围内,可以继续处理;如果获取失败,说明请求过于频繁,需要返回错误提示。示例代码如下:
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Autowired
private RateLimiter rateLimiter;
@PostMapping("/addUser")
public ResponseEntity<String> addUser(@RequestBody User user) {
if (!rateLimiter.tryAcquire()) {
return ResponseEntity.status(429).body("请求过于频繁,请稍后再试");
}
// 添加用户的业务逻辑
return ResponseEntity.ok("用户添加成功");
}
}
这种方式通过限制请求的速率来实现防抖,适用于对请求频率有严格控制的场景。
总结
今天我们深入探讨了在 Spring Boot3 中实现接口防抖的两种常见且有效的方案。无论是基于注解和 AOP 利用 Redis 实现的防抖,还是借助 Guava 的 RateLimiter 实现的限流防抖,都能很好地应对高并发场景下的重复请求问题,提升我们系统的性能和稳定性。大家在实际项目中不妨试试这些方法,根据业务场景的特点选择最适合的方案。如果你在实现过程中有任何疑问或者遇到了有趣的问题,欢迎在评论区留言分享,咱们一起交流进步。同时,也别忘了点赞、收藏这篇文章,说不定以后在项目中就能派上用场哦!
- 上一篇: jmeter接口测试教程
- 下一篇: Spring应用上下文 ApplicationContext
猜你喜欢
- 2025-05-21 Spring应用上下文 ApplicationContext
- 2025-05-21 jmeter接口测试教程
- 2025-05-21 使用httpclient post请求中文乱码解决办法
- 2025-05-21 前端请求一到,后端就乱套?揭秘 Spring MVC 底层调用全流程!
- 2025-05-21 在 Spring Boot3 中轻松解决接口跨域访问问题
- 2025-05-21 SpringBoot 数据验证与表单处理全面指南(史上最全)
- 2025-05-21 原来大牛们口中的接口Mock测试是长这样的,今天我终于搞懂了
- 2025-05-21 3 分钟Java 开发 MCP Server,一键打通飞书机器人消息发送通道!
- 2025-05-21 Java 调用 DeepSeek 模型的完整示例及特点
- 2025-05-21 SpringBoot之数据访问——访问SQL数据库!
你 发表评论:
欢迎- 最近发表
-
- 我的世界光影MOD下载(我的世界光影mod下载安装)
- 我的世界1.7/1.8VoxelMap小地图MOD下载
- 我的世界1.7.10多世界 整合包(我的世界1.7.10forge整合包)
- 我的世界1.8最好用的修改器下载(我的世界1.8最好用的修改器下载安装)
- 我的世界更多弯曲动作MOD下载(我的世界更多弯曲动作mod下载手机版)
- 我的世界龙珠MOD下载(我的世界龙珠模组整合包下载)
- 我的世界1.7.10以太2 下载(我的世界以太2mod1.12.2)
- 我的世界虚拟人生MOD下载分享(我的世界虚拟人生下载安装)
- 我的世界无正版账号的简单联机方法(非网易版,仅适用于局域网)
- “我的语言极限,即是我的世界的极限。” ——《On Java》书籍推荐
- 标签列表
-
- nginx反向代理 (57)
- nginx日志 (56)
- nginx限制ip访问 (62)
- mac安装nginx (55)
- java和mysql (59)
- java中final (62)
- win10安装java (72)
- java启动参数 (64)
- java链表反转 (64)
- 字符串反转java (72)
- java逻辑运算符 (59)
- java 请求url (65)
- java信号量 (57)
- java定义枚举 (59)
- java字符串压缩 (56)
- java中的反射 (59)
- java 三维数组 (55)
- java插入排序 (68)
- java线程的状态 (62)
- java异步调用 (55)
- java中的异常处理 (62)
- java锁机制 (54)
- java静态内部类 (55)
- java怎么添加图片 (60)
- java 权限框架 (55)
本文暂时没有评论,来添加一个吧(●'◡'●)