百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Spring Boot3 中接口防抖操作全解析

mhr18 2025-04-07 16:22 25 浏览 0 评论

嘿,各位后端开发的伙伴们!在日常开发工作里,大家是不是经常碰到这样的问题:用户在前端一顿疯狂操作,猛点按钮或者飞速提交表单,结果后端接口就收到了一连串重复请求。这不但浪费服务器资源,还可能引发数据不一致等诸多麻烦事。今天,咱们就来深入探讨在 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.isannotationpresentrequestkeyparam.class if arg 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 saveUser(@RequestBody User user) {
    // 保存用户信息的业务逻辑
    return ResponseEntity.ok("用户信息保存成功");
}

这个接口设置了 2 秒的防抖时间,在这 2 秒内,如果收到相同参数的重复请求,就会提示 “请求过于频繁,请稍后再试”。

使用 Guava 的 RateLimiter 实现限流防抖

引入依赖

首先pom.xml文件中引入 Guava 的依赖:


    com.google.guava
    guava
    32.1.2-jre

配置 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 addUser(@RequestBody User user) {
        if (!rateLimiter.tryAcquire()) {
            return ResponseEntity.status(429).body("请求过于频繁,请稍后再试");
        }
        // 添加用户的业务逻辑
        return ResponseEntity.ok("用户添加成功");
    }
}

这种方式通过限制请求的速率来实现防抖,适用于对请求频率有严格控制的场景。

总结

今天我们深入探讨了在 Spring Boot3 中实现接口防抖的两种常见且有效的方案。无论是基于注解和 AOP 利用 Redis 实现的防抖,还是借助 Guava 的 RateLimiter 实现的限流防抖,都能很好地应对高并发场景下的重复请求问题,提升我们系统的性能和稳定性。大家在实际项目中不妨试试这些方法,根据业务场景的特点选择最适合的方案。如果你在实现过程中有任何疑问或者遇到了有趣的问题,欢迎在评论区留言分享,咱们一起交流进步。同时,也别忘了点赞、收藏这篇文章,说不定以后在项目中就能派上用场哦!

相关推荐

【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...

Pure Storage推出统一数据管理云平台及新闪存阵列

PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...

对Java学习的10条建议(对java课程的建议)

不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...

SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!

官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...

JDK21有没有什么稳定、简单又强势的特性?

佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...

「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了

在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...

Java面试题及答案最全总结(2025版)

大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...

数据库日常运维工作内容(数据库日常运维 工作内容)

#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...

分布式之系统底层原理(上)(底层分布式技术)

作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...

oracle 死锁了怎么办?kill 进程 直接上干货

1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...

SpringBoot 各种分页查询方式详解(全网最全)

一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...

《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略

《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...

LoadRunner(loadrunner录制不到脚本)

一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...

Redis数据类型介绍(redis 数据类型)

介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...

RMAN备份监控及优化总结(rman备份原理)

今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...

取消回复欢迎 发表评论: