网站首页 > 技术文章 正文
嘿,各位后端开发的伙伴们!在日常开发工作里,大家是不是经常碰到这样的问题:用户在前端一顿疯狂操作,猛点按钮或者飞速提交表单,结果后端接口就收到了一连串重复请求。这不但浪费服务器资源,还可能引发数据不一致等诸多麻烦事。今天,咱们就来深入探讨在 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 实现的限流防抖,都能很好地应对高并发场景下的重复请求问题,提升我们系统的性能和稳定性。大家在实际项目中不妨试试这些方法,根据业务场景的特点选择最适合的方案。如果你在实现过程中有任何疑问或者遇到了有趣的问题,欢迎在评论区留言分享,咱们一起交流进步。同时,也别忘了点赞、收藏这篇文章,说不定以后在项目中就能派上用场哦!
猜你喜欢
- 2025-05-08 佳能RF系统中有哪些宝藏镜头?(佳能rf镜头2021最新消息)
- 2025-05-08 前端分享-VueUse着实是有东西的(vue.use做了什么)
- 2025-05-08 前端必看!10 个 Vue3 实战技巧,解决 90% 开发难题?
- 2025-05-08 高并发下实现幂等的几种方式(并发和幂等)
- 2025-05-08 解释一下什么是防抖(Debounce)和节流(Throttle)? (面试题)
- 2025-05-08 前端人必收!10 个 Vue3 隐藏技巧,解决 99% 开发卡顿?
- 2025-05-08 CP+2016:五挡防抖 富士100-400现场试用
- 2025-05-08 「SpringCloud」(三十九)使用分布式锁实现微服务重复请求控制
- 2025-05-08 前端人必看!10 个 Vue3 救命技巧,专治性能差、代码乱
- 2025-05-08 频繁触发事件崩溃?JS 防抖节流 3 板斧,第 3 种 90% 人没用过!
你 发表评论:
欢迎- 05-10如何优化数据库和前端之间的交互?
- 05-10前端代码优化小秘籍(前端优化24条建议)
- 05-10VS Code当中的15个神仙插件,值得收藏
- 05-10如何自己开发一个Google浏览器插件?
- 05-10前端流行框架Vue3教程:14. 组件传递Props效验
- 05-10吃了一年的SU,最好用的插件都在这了
- 05-10前端必看!这款神器让网站界面告别千篇一律
- 05-10程序员请收好:10个非常有用的 Visual Studio Code 插件
- 最近发表
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端md5加密 (49)
- 前端路由 (55)
- 前端数组 (65)
- 前端定时器 (47)
- 前端懒加载 (45)
- 前端接口 (46)
- Oracle RAC (73)
- oracle恢复 (76)
- oracle 删除表 (48)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle查询数据库 (45)
- oracle约束 (46)
- oracle 中文 (51)
- oracle链接 (47)
- oracle的函数 (57)
- mac oracle (47)
- 前端调试 (52)
- 前端登录页面 (48)
本文暂时没有评论,来添加一个吧(●'◡'●)