亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java接口防抖/冪等性解決方案(redis)

 更新時間:2025年07月01日 10:27:50   作者:JavaRookies13  
在Java項目開發(fā)過程中并發(fā)處理與冪等性問題緊密相關(guān),這也導致了一些人認為解決冪等性就是解決高并發(fā)的問題,這篇文章主要介紹了Java接口防抖/冪等性(redis)的相關(guān)資料,需要的朋友可以參考下

一、核心區(qū)別

特性接口防抖(Debouncing)接口冪等性(Idempotency)
目的減少資源浪費:防止短時間內(nèi)多次觸發(fā)同一操作(如用戶頻繁點擊、網(wǎng)絡抖動導致重復請求)。保證結(jié)果一致性:確保同一請求無論調(diào)用一次還是多次,最終結(jié)果相同,避免重復操作導致的數(shù)據(jù)異常。
作用層面前端/后端均可實現(xiàn):前端優(yōu)化用戶體驗,后端過濾重復請求。后端核心邏輯:依賴業(yè)務邏輯和數(shù)據(jù)層設(shè)計,確保操作的唯一性。
關(guān)注點時間窗口內(nèi)的重復請求:只處理最后一次或首次請求。請求的唯一性標識:通過唯一標識符(如請求ID、業(yè)務參數(shù))判斷是否重復。
典型場景用戶搜索輸入、按鈕多次點擊、無限滾動加載。支付接口、訂單創(chuàng)建、數(shù)據(jù)修改等需避免重復操作的場景。

二、實現(xiàn)方式

接口防抖:

核心思想:在指定時間窗口內(nèi),僅允許最后一次(或首次)請求生效。

1.前端畫面每次請求添加loading遮罩層(接口響應時間過長就會導致用戶體驗不好)

2.使用redis每次將請求主要參數(shù)和請求人綁定起來,放入指定的緩存時間,第二次再請求看到是同一個接口和同一個人操作則提示:操作頻繁,稍后重試!

(推薦,做成自定義注解的方式,實現(xiàn)簡單)

3.前端發(fā)送請求時,在指定時間窗口內(nèi),延遲發(fā)送請求

(不推薦,畢竟會延遲發(fā)送請求,影響接口速度)

let timeout;
function handleSearchInput(event) {
  clearTimeout(timeout);
  timeout = setTimeout(() => {
    // 發(fā)送請求
    fetch('/search', { query: event.target.value });
  }, 300); // 300ms防抖間隔
}

接下來聊聊第二種方式,自定義注解:

 1.AOP (攔截請求,并獲取請求具體信息,將url,接口主要參數(shù),用戶id存入Redis中)

package com.qeoten.sms.edu.config;

import com.qeoten.sms.util.api.R;
import com.qeoten.sms.util.auth.AuthUtil;
import com.qeoten.sms.util.util.DigestUtil;
import com.qeoten.sms.util.util.RedisUtil;
import io.lettuce.core.dynamic.support.ReflectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.concurrent.TimeUnit;

/**
 * 接口防抖aop
 */
@Aspect
@Component
@Slf4j
public class AntiShakeAOP {

    @Autowired
    private RedisUtil redisUtil;

    private static final String prefix = "RepeatSubmit";

    @Around(value = "@annotation(com.qeoten.sms.edu.config.RepeatClick)")
    public Object antiShake(ProceedingJoinPoint pjp) throws Throwable {

        // 獲取調(diào)用方法的信息和簽名信息
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        // 獲取方法
        Method method = signature.getMethod();
        // 獲取注解中的參數(shù)
        RepeatClick annotation = method.getAnnotation(RepeatClick.class);

        String key = getLockKey(pjp);
        // 查詢redis中是否存在對應關(guān)系
        if (!redisUtil.hasKey(key)) {

            redisUtil.setKeyAndExpire(key, null, annotation.value(), TimeUnit.MILLISECONDS);
            return pjp.proceed();
        } else {
            log.error(annotation.message());
            return R.fail(annotation.message());
        }
    }

    public static String getLockKey(ProceedingJoinPoint joinPoint) {
        //獲取連接點的方法簽名對象
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        //Method對象
        Method method = methodSignature.getMethod();
        String className = method.getDeclaringClass().getName();

        //獲取Method對象上的注解對象
        //獲取方法參數(shù)
        final Object[] args = joinPoint.getArgs();
        //獲取Method對象上所有的注解
        final Parameter[] parameters = method.getParameters();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < parameters.length; i++) {
            final RepeatClick keyParam = parameters[i].getAnnotation(RepeatClick.class);
            if (keyParam == null) {
                //如果屬性不是RepeatSubmit注解,則獲取方法的參數(shù)名
                sb.append(args[i]).append("&");
            } else {
                final Object object = args[i];
                //獲取注解類中所有的屬性字段
                final Field[] fields = object.getClass().getDeclaredFields();
                for (Field field : fields) {
                    //判斷字段上是否有RepeatSubmit注解
                    final RepeatClick annotation = field.getAnnotation(RepeatClick.class);
                    //如果沒有,跳過
                    if (annotation == null) {
                        continue;
                    }
                    //如果有,設(shè)置Accessible為true(為true時可以使用反射訪問私有變量,否則不能訪問私有變量)
                    field.setAccessible(true);
                    //如果屬性是RepeatSubmit注解,則拼接 連接符" & + RepeatSubmit"
                    sb.append(ReflectionUtils.getField(field, object)).append("&");
                }
            }
        }
        //返回指定前綴的key
        return prefix + ":" + className + ":" + method.getName() + ":" + AuthUtil.getUserId() + ":" + DigestUtil.md5Hex((sb.toString()));
    }
}

2.自定義注解模板(配置緩存時間,和指定提示消息)

package com.qeoten.sms.edu.config;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author QT-PC-0021
 */
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatClick {

    /**
     * 默認的防抖時間ms
     *
     * @return
     */
    long value() default 1000;

    String message() default "操作太頻繁,請稍后再試!";

}

3.在需要進行操作表的接口上,添加自定義注解,實現(xiàn)功能

    @GetMapping("/advancePaper")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "交卷", notes = "傳入考試id")
    @RepeatClick
    public R<MyExamVo> advancePaper(@RequestParam Long examId){

      // 接口邏輯,可能頻繁操作表

    }

接口冪等性:

核心思想:通過唯一標識符(如請求ID、業(yè)務參數(shù))確保同一請求只處理一次。

1.數(shù)據(jù)庫唯一索引:

    數(shù)據(jù)庫設(shè)置唯一索引重復提交時,插表就會直接報錯重復

     (不推薦,畢竟壓力直接進入數(shù)據(jù)庫了)

2.數(shù)據(jù)庫樂觀鎖:(數(shù)據(jù)修改時間 / 版本號) => 比對

    查詢列表畫面時,將數(shù)據(jù)的修改時間(毫秒級)記錄一下,下次請求增刪改接口時,將數(shù)據(jù)原本的修改時間傳入接口,接口第一步判斷當前數(shù)據(jù)的修改時間是否和畫面上傳入的修改時間一致,一致就代表沒有人修改做此數(shù)據(jù),否則就提示此數(shù)據(jù)已被他人修改,請稍后再試!

最后更新記錄時,帶入版本號或者修改時間進去,
update xxx  set name = xxx   where id = xxx  and updateTime = xxx 

   (并發(fā)量小的時候可以,并發(fā)大的時候存在重復修改問題)

3.唯一值+緩存:

  其實也就是接口防抖中的第二個實現(xiàn)方案的變化版本

  上面提到將接口的主要參數(shù)+用戶id作為唯一標識存入redis并記錄指定的緩存時間,那么這次存入redis不記錄時間,并且在接口結(jié)束時清除掉此緩存。

(推薦,但是當服務異常掛掉時,或者某些原因接口沒有正常執(zhí)行完成時,redis緩存一直都會在,不好維護,浪費資源)

4.分布式鎖(redisson)

 業(yè)務開始時候去tryLock,嘗試獲取鎖(鎖的參數(shù)可以是本次操作的對象id,假如說本次要給某個商品增加扣減庫存,那么參數(shù)可以是商品id),保障在接口的最后一步,釋放鎖即可。

    RLock lock = redissonClient.getLock("my-distributed-lock");

    // 嘗試獲取鎖:等待最多 10 秒,鎖自動續(xù)期 30 秒
    boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);

 這樣每次拿到鎖的線程才會繼續(xù)進行接口邏輯操作。

5.手動實現(xiàn)鎖

 其實原理和第4點一樣,就是需要考慮手動實現(xiàn)鎖的復雜性

  • 加鎖時如何保證加鎖和給鎖設(shè)置有效期的一致性
  • 鎖的過期時間,鎖需要釋放
  • 鎖不能提前釋放,防止其他線程獲取到此鎖
  • 怎樣給將要過期的鎖加過期時間
  • 釋放鎖的時候,如何保證釋放的是同一個鎖,防止錯釋放
  • 保證釋放鎖時的原子性

1. 加鎖時setnx命令,設(shè)置其lock資源名稱 + value(一般為threadId / 時間戳) + 過期時間

2. 進行后續(xù)業(yè)務操作

3. 最后需要用lua腳本來釋放鎖(先獲取鎖的value確保是當前的lock,使用腳本釋放鎖)

總結(jié)

  • 防抖:重點是減少請求次數(shù),通過時間戳、緩存實現(xiàn)。
  • 冪等性:重點是保證結(jié)果唯一,通過唯一標識符、數(shù)據(jù)庫約束或鎖或業(yè)務校驗實現(xiàn)。
  • 實際應用:通常需要結(jié)合兩者,例如:

    前端防抖:減少無效請求。

    后端冪等性:即使防抖失效,也能保證最終結(jié)果一致。

根據(jù)業(yè)務需求選擇合適的方案,例如:

  • 高頻非敏感操作(如普通的修改或者刪除接口):使用本地緩存或 Redis 防抖。
  • 敏感操作(如支付):結(jié)合 Redis 唯一標識符和數(shù)據(jù)庫唯一索引確保冪等。

到此這篇關(guān)于Java接口防抖/冪等性解決(redis)的文章就介紹到這了,更多相關(guān)Java接口防抖冪等性內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解spring boot實現(xiàn)多數(shù)據(jù)源代碼實戰(zhàn)

    詳解spring boot實現(xiàn)多數(shù)據(jù)源代碼實戰(zhàn)

    本篇文章主要介紹了詳解spring boot實現(xiàn)多數(shù)據(jù)源代碼實戰(zhàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07
  • 利用棧使用簡易計算器(Java實現(xiàn))

    利用棧使用簡易計算器(Java實現(xiàn))

    這篇文章主要為大家詳細介紹了Java利用棧實現(xiàn)簡易計算器,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • spring-session自定義序列化方式

    spring-session自定義序列化方式

    這篇文章主要介紹了spring-session自定義序列化方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • 深入淺析MyBatis foreach標簽

    深入淺析MyBatis foreach標簽

    Mybatis foreach 標簽用于循環(huán)語句,它很好的支持了數(shù)據(jù)和 List、set 接口的集合,并對此提供遍歷的功能,本文給大家介紹MyBatis foreach標簽的相關(guān)知識,感興趣的朋友一起看看吧
    2021-09-09
  • Java多個版本切換的幾種方法

    Java多個版本切換的幾種方法

    本文主要介紹了Java多個版本切換的幾種方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-03-03
  • Java線程中的ThreadLocal詳細分析

    Java線程中的ThreadLocal詳細分析

    這篇文章主要介紹了Java線程中的ThreadLocal詳細分析,ThreadLocal?提供線程的局部變量,每個線程都可以通過?get()和?set()對局部變量進行操作而不會對其他線程的局部變量產(chǎn)生影響,實現(xiàn)了線程之間的數(shù)據(jù)隔離,需要的朋友可以參考下
    2023-09-09
  • 使用jekins自動構(gòu)建部署java maven項目的方法步驟

    使用jekins自動構(gòu)建部署java maven項目的方法步驟

    這篇文章主要介紹了使用jekins自動構(gòu)建部署java maven項目的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-01-01
  • Java中樹的存儲結(jié)構(gòu)實現(xiàn)示例代碼

    Java中樹的存儲結(jié)構(gòu)實現(xiàn)示例代碼

    本篇文章主要介紹了Java中樹的存儲結(jié)構(gòu)實現(xiàn)示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • Java中Stream實現(xiàn)List排序的六個核心技巧總結(jié)

    Java中Stream實現(xiàn)List排序的六個核心技巧總結(jié)

    這篇文章主要介紹了Java中Stream實現(xiàn)List排序的六個核心技巧,分別是自然序排序、反向排序、空值安全處理、多字段組合排序、并行流加速、原地排序等,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2025-04-04
  • Java的二叉樹排序以及遍歷文件展示文本格式的文件樹

    Java的二叉樹排序以及遍歷文件展示文本格式的文件樹

    這篇文章主要介紹了Java的二叉樹排序以及遍歷文件展示文本格式的文件樹,是對二叉樹結(jié)構(gòu)學習的兩個很好的實踐,需要的朋友可以參考下
    2015-11-11

最新評論