最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

一、核心區(qū)別

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

二、實(shí)現(xiàn)方式

接口防抖:

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

1.前端畫(huà)面每次請(qǐng)求添加loading遮罩層(接口響應(yīng)時(shí)間過(guò)長(zhǎng)就會(huì)導(dǎo)致用戶體驗(yàn)不好)

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

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

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

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

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

接下來(lái)聊聊第二種方式,自定義注解:

 1.AOP (攔截請(qǐng)求,并獲取請(qǐng)求具體信息,將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);
        // 查詢r(jià)edis中是否存在對(duì)應(yīng)關(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) {
        //獲取連接點(diǎn)的方法簽名對(duì)象
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        //Method對(duì)象
        Method method = methodSignature.getMethod();
        String className = method.getDeclaringClass().getName();

        //獲取Method對(duì)象上的注解對(duì)象
        //獲取方法參數(shù)
        final Object[] args = joinPoint.getArgs();
        //獲取Method對(duì)象上所有的注解
        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);
                    //如果沒(méi)有,跳過(guò)
                    if (annotation == null) {
                        continue;
                    }
                    //如果有,設(shè)置Accessible為true(為true時(shí)可以使用反射訪問(wèn)私有變量,否則不能訪問(wèn)私有變量)
                    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.自定義注解模板(配置緩存時(shí)間,和指定提示消息)

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 {

    /**
     * 默認(rèn)的防抖時(shí)間ms
     *
     * @return
     */
    long value() default 1000;

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

}

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

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

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

    }

接口冪等性:

核心思想:通過(guò)唯一標(biāo)識(shí)符(如請(qǐng)求ID、業(yè)務(wù)參數(shù))確保同一請(qǐng)求只處理一次。

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

    數(shù)據(jù)庫(kù)設(shè)置唯一索引重復(fù)提交時(shí),插表就會(huì)直接報(bào)錯(cuò)重復(fù)

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

2.數(shù)據(jù)庫(kù)樂(lè)觀鎖:(數(shù)據(jù)修改時(shí)間 / 版本號(hào)) => 比對(duì)

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

最后更新記錄時(shí),帶入版本號(hào)或者修改時(shí)間進(jìn)去,
update xxx  set name = xxx   where id = xxx  and updateTime = xxx 

   (并發(fā)量小的時(shí)候可以,并發(fā)大的時(shí)候存在重復(fù)修改問(wèn)題)

3.唯一值+緩存:

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

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

(推薦,但是當(dāng)服務(wù)異常掛掉時(shí),或者某些原因接口沒(méi)有正常執(zhí)行完成時(shí),redis緩存一直都會(huì)在,不好維護(hù),浪費(fèi)資源)

4.分布式鎖(redisson)

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

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

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

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

5.手動(dòng)實(shí)現(xiàn)鎖

 其實(shí)原理和第4點(diǎn)一樣,就是需要考慮手動(dòng)實(shí)現(xiàn)鎖的復(fù)雜性

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

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

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

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

總結(jié)

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

    前端防抖:減少無(wú)效請(qǐng)求。

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

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

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

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

相關(guān)文章

  • MyBatis整合Redis實(shí)現(xiàn)二級(jí)緩存的示例代碼

    MyBatis整合Redis實(shí)現(xiàn)二級(jí)緩存的示例代碼

    這篇文章主要介紹了MyBatis整合Redis實(shí)現(xiàn)二級(jí)緩存的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • 在idea中設(shè)置項(xiàng)目編碼格式為UTF-8的操作方法

    在idea中設(shè)置項(xiàng)目編碼格式為UTF-8的操作方法

    idea中的默認(rèn)編碼為GBK,在開(kāi)發(fā)過(guò)程中一般將編碼格式改為UTF-8,所以本文給大家介紹了在idea中設(shè)置項(xiàng)目編碼為UTF-8的操作方法,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-12-12
  • Java實(shí)現(xiàn)InputStream的任意拷貝方式

    Java實(shí)現(xiàn)InputStream的任意拷貝方式

    這篇文章主要介紹了Java實(shí)現(xiàn)InputStream的任意拷貝方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • JAVA熔斷和降級(jí)真實(shí)關(guān)系的圖文詳解

    JAVA熔斷和降級(jí)真實(shí)關(guān)系的圖文詳解

    這篇文章主要介紹了Java熔斷和降級(jí)的關(guān)系,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-09-09
  • Java NIO中的零拷貝原理

    Java NIO中的零拷貝原理

    這篇文章主要介紹了Java NIO中的零拷貝原理,零拷貝即Zero-Copy,顧名思義,零拷貝是指的一種非拷貝的方式來(lái)減少IO次數(shù)的工作方式,零拷貝的作用就是減少IO,提高IO效率,需要的朋友可以參考下
    2023-11-11
  • JAVA HashMap詳細(xì)介紹和示例

    JAVA HashMap詳細(xì)介紹和示例

    我們先對(duì)HashMap有個(gè)整體認(rèn)識(shí),然后再學(xué)習(xí)它的源碼,最后再通過(guò)實(shí)例來(lái)學(xué)會(huì)使用HashMap。
    2013-11-11
  • Java中Scanner使用方式:單行/多行輸入

    Java中Scanner使用方式:單行/多行輸入

    這篇文章主要介紹了Java中Scanner使用方式:單行/多行輸入,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • 使用C3P0改造JDBC對(duì)數(shù)據(jù)庫(kù)的連接

    使用C3P0改造JDBC對(duì)數(shù)據(jù)庫(kù)的連接

    這篇文章主要為大家詳細(xì)介紹了使用C3P0改造JDBC對(duì)數(shù)據(jù)庫(kù)的連接,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-08-08
  • Java 讀取文件方法大全

    Java 讀取文件方法大全

    這篇文章主要介紹了Java 讀取文件方法大全,需要的朋友可以參考下
    2014-11-11
  • Java對(duì)象的內(nèi)存布局全流程

    Java對(duì)象的內(nèi)存布局全流程

    這篇文章主要介紹了Java對(duì)象的內(nèi)存布局全流程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05

最新評(píng)論

洛扎县| 永顺县| 通州市| 眉山市| 商都县| 井冈山市| 宁国市| 临夏县| 曲阳县| 嵊州市| 濮阳市| 营山县| 法库县| 金华市| 外汇| 南汇区| 德兴市| 浮梁县| 平远县| 南昌县| 云阳县| 上林县| 广水市| 如东县| 成都市| 卢龙县| 谢通门县| 巧家县| 沾化县| 公主岭市| 房产| 睢宁县| 新密市| 新竹县| 舞钢市| 丹寨县| 盱眙县| 赤壁市| 高尔夫| 淮阳县| 兰考县|