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

基于AOP+Redis的簡(jiǎn)易滑動(dòng)窗口限流

 更新時(shí)間:2025年07月30日 10:08:16   作者:TCChzp  
本文主要介紹了基于AOP+Redis的簡(jiǎn)易滑動(dòng)窗口限流,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

在分布式系統(tǒng)設(shè)計(jì)中,限流是保障服務(wù)穩(wěn)定性的核心技術(shù)之一?;瑒?dòng)窗口限流算法以其精確性平滑性優(yōu)勢(shì),成為解決傳統(tǒng)固定窗口限流臨界突變問(wèn)題的理想方案。本文將深入解析滑動(dòng)窗口算法原理,并通過(guò)AOP+Redis滑動(dòng)窗口限流。

固定窗口與滑動(dòng)窗口對(duì)比

固定窗口限流及其缺陷

固定窗口限流將時(shí)間劃分為固定區(qū)間(如每分鐘),統(tǒng)計(jì)每個(gè)區(qū)間內(nèi)的請(qǐng)求數(shù)量。這種方法雖然簡(jiǎn)單,但存在嚴(yán)重缺陷:

當(dāng)大量請(qǐng)求集中在兩個(gè)窗口的交界處時(shí)(如00:59:59和01:00:00),系統(tǒng)會(huì)在極短時(shí)間內(nèi)接收雙倍于閾值的請(qǐng)求,導(dǎo)致服務(wù)過(guò)載?;瑒?dòng)窗口限流通過(guò)動(dòng)態(tài)時(shí)間區(qū)間解決了這個(gè)問(wèn)題。

核心原理:為每個(gè)請(qǐng)求動(dòng)態(tài)定義一個(gè)以當(dāng)前時(shí)間為終點(diǎn)、向前回溯固定時(shí)長(zhǎng)T的時(shí)間區(qū)間(滑動(dòng)窗口),統(tǒng)計(jì)該區(qū)間內(nèi)的請(qǐng)求數(shù):

  1. 動(dòng)態(tài)窗口:每個(gè)請(qǐng)求到達(dá)時(shí)計(jì)算[當(dāng)前時(shí)間 - T, 當(dāng)前時(shí)間]區(qū)間
  2. 實(shí)時(shí)統(tǒng)計(jì):計(jì)算該區(qū)間內(nèi)的請(qǐng)求數(shù)量
  3. 決策執(zhí)行:請(qǐng)求數(shù) < 閾值 → 允許;否則拒絕
  4. 窗口滑動(dòng):過(guò)期請(qǐng)求自動(dòng)移出統(tǒng)計(jì)范圍

Redis實(shí)現(xiàn)方案

Redis的有序集合(ZSET)是實(shí)現(xiàn)滑動(dòng)窗口限流的理想數(shù)據(jù)結(jié)構(gòu):

ZSET結(jié)合了集合(Set)和哈希(Hash)的特性:

  • 唯一成員:每個(gè)成員(member)在集合中唯一
  • 分?jǐn)?shù)排序:每個(gè)成員關(guān)聯(lián)一個(gè)分?jǐn)?shù)(score),用于排序
  • 自動(dòng)排序:成員按分?jǐn)?shù)值從小到大排序

之所以是滑動(dòng)窗口限流的理想選擇,關(guān)鍵在于它完美解決了滑動(dòng)窗口算法的三個(gè)核心需求:

  • 時(shí)間序列的天然支持:ZSET的分?jǐn)?shù)(score)機(jī)制為時(shí)間戳提供了原生支持。當(dāng)我們將請(qǐng)求時(shí)間戳作為score存儲(chǔ)時(shí),所有請(qǐng)求按時(shí)間順序自動(dòng)排序,形成精確的時(shí)間序列。這使得界定時(shí)間窗口邊界變得簡(jiǎn)單——只需計(jì)算當(dāng)前時(shí)間 - 窗口大小就能得到窗口起始點(diǎn),無(wú)需額外維護(hù)時(shí)間索引。這種設(shè)計(jì)讓滑動(dòng)窗口的"滑動(dòng)"機(jī)制得以自然實(shí)現(xiàn)。
  • 高效的范圍操作能力:滑動(dòng)窗口的核心操作是清理過(guò)期請(qǐng)求,ZSET的ZREMRANGEBYSCORE命令正是為此而生。它能以O(log(N)+M)的復(fù)雜度高效刪除指定時(shí)間范圍外的歷史請(qǐng)求。這種高效的范圍刪除能力確保了窗口滑動(dòng)時(shí)的實(shí)時(shí)性能。
  • 精確的實(shí)時(shí)統(tǒng)計(jì)特性:通過(guò)ZCARDZCOUNT命令,ZSET提供原子級(jí)的精確計(jì)數(shù)能力。在滑動(dòng)窗口算法中,我們需要實(shí)時(shí)統(tǒng)計(jì)當(dāng)前窗口內(nèi)的請(qǐng)求數(shù)并與閾值比較,這些命令能以O(shè)(1)和O(log(N))復(fù)雜度瞬間完成統(tǒng)計(jì)。這種即時(shí)反饋機(jī)制對(duì)高并發(fā)場(chǎng)景至關(guān)重要,確保限流決策的及時(shí)性和準(zhǔn)確性。
命令統(tǒng)計(jì)范圍時(shí)間復(fù)雜度典型使用場(chǎng)景
ZCARD key整個(gè) ZSET 的總成員數(shù)O(1)清理過(guò)期數(shù)據(jù)后快速獲取當(dāng)前窗口請(qǐng)求總數(shù)
ZCOUNT key min max指定 score 范圍內(nèi)的成員數(shù)O(log(N))動(dòng)態(tài)統(tǒng)計(jì)子窗口/特定時(shí)間段的請(qǐng)求量

代碼

自定義注解

首先自定義注解,定義限流維度、窗口大小、時(shí)間單位、窗口內(nèi)最大請(qǐng)求數(shù)量

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SlidingWindowLimit {
    /**
     * 限流維度的 SpEL 表達(dá)式
     * 示例:
     * - 按郵箱: "#email"
     * - 按 IP: "#request.remoteAddr"
     * - 按用戶(hù) ID + 郵箱: "#user.id + ':' + #user.email"
     */
    String keySpEL() default "#email";
    /**
     * 窗口大小
     */
    int windowSize() default 60;
    /**
     * 時(shí)間單位
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;
    /**
     * 窗口內(nèi)最大請(qǐng)求數(shù)
     */
    int maxRequests() default 10;

}

切面類(lèi)

1. 切面配置與基礎(chǔ)結(jié)構(gòu)

首先創(chuàng)建切面類(lèi)

@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
public class SlidingWindowLimitAspect {
    @Resource
    private RedissonClient redissonClient;
    private static final Logger log = LoggerFactory.getLogger(SlidingWindowLimitAspect.class);
    private static final String RATE_LIMIT_PREFIX = "rate_limit";
}
  • @Aspect:聲明該類(lèi)為AOP切面
  • @Order:設(shè)置切面執(zhí)行優(yōu)先級(jí)(數(shù)字越小優(yōu)先級(jí)越高)
  • redissonClient:Redis客戶(hù)端操作接口
  • RATE_LIMIT_PREFIX:Redis鍵名前綴,用于區(qū)分限流數(shù)據(jù)

2. 切面入口方法 - around()

@Around("@annotation(slidingWindowLimit)")
public Object around(ProceedingJoinPoint joinPoint, SlidingWindowLimit slidingWindowLimit) throws Throwable {
    //獲取方法簽名
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    Method method = signature.getMethod();
    String methodName = method.getName();
    //判斷是否為Http請(qǐng)求
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    if(attributes == null){
        log.warn("方法 {} 不在 Web 請(qǐng)求上下文中,跳過(guò)限流檢查。", methodName);
        return joinPoint.proceed();
    }
    try {
        Object parseSpEL = parseSpEL(joinPoint, signature, slidingWindowLimit.keySpEL());
        String rateLimitKey = buildRateLimitKey(parseSpEL, slidingWindowLimit);
        if(isRateLimited(rateLimitKey, slidingWindowLimit.windowSize(), slidingWindowLimit.timeUnit(), slidingWindowLimit.maxRequests())){
            throw new RateLimitExceededException("Rate limit exceeded");
        }
        return joinPoint.proceed();
    }catch (RateLimitExceededException e){
        log.warn("方法 {} 觸發(fā)了限流,已拒絕訪問(wèn)。", methodName);
        throw e;
    }catch (Exception e){
        log.error("方法 {} 觸發(fā)了異常,已拒絕訪問(wèn)。", methodName, e);
        throw e;
    }
}

執(zhí)行過(guò)程:

3. SpEL解析方法 - parseSpEL()

private Object parseSpEL(ProceedingJoinPoint joinPoint,MethodSignature signature, String keySpEL){
    StandardEvaluationContext context = new StandardEvaluationContext();

    Object[] args = joinPoint.getArgs();
    String[] parameterNames = signature.getParameterNames();

    for (int i = 0; i < args.length; i++) {
        if(parameterNames!=null && i< parameterNames.length){
            context.setVariable(parameterNames[i], args[i]);
        }else {
            context.setVariable("arg" + i, args[i]);
        }
    }

    ExpressionParser parser = new SpelExpressionParser();
    return parser.parseExpression(keySpEL).getValue(context);
}

功能說(shuō)明

  • 動(dòng)態(tài)解析注解中的SpEL表達(dá)式(如#email
  • 將方法參數(shù)注入表達(dá)式上下文
  • 支持靈活的限流鍵生成策略

4.限流鍵構(gòu)建方法 - buildRateLimitKey()

private String buildRateLimitKey(Object keyValue, SlidingWindowLimit slidingWindowLimit){
    if(keyValue == null){
        throw new IllegalArgumentException("限流參數(shù)不能為空");
    }
    return String.format("%s:%s:%s",RATE_LIMIT_PREFIX,slidingWindowLimit.keySpEL(), keyValue);
}

鍵格式說(shuō)明

rate_limit:SpEL表達(dá)式:參數(shù)值
↓          ↓         ↓
rate_limit:#email:user@example.com

5. 限流核心邏輯 - isRateLimited()

private boolean isRateLimited(String key, int windowSize, TimeUnit timeUnit, int maxRequests){
    //獲取當(dāng)前時(shí)間數(shù)
    long currentTime = System.currentTimeMillis();
    long windowStartTime = currentTime - convertToMillis(windowSize, timeUnit);
    // 獲取 Redisson 的 ZSet 操作對(duì)象
    RScoredSortedSet<Long> scoredSortedSet = redissonClient.getScoredSortedSet(key);

    // 1. 刪除窗口外的過(guò)期請(qǐng)求
    scoredSortedSet.removeRangeByScore(0, true, windowStartTime, true); // [0, windowStartTime]

    // 2. 添加當(dāng)前請(qǐng)求的時(shí)間戳到 ZSet
    scoredSortedSet.add(currentTime, currentTime); // score 和 value 均為時(shí)間戳

    // 3. 統(tǒng)計(jì)窗口內(nèi)請(qǐng)求數(shù)量
    int count = scoredSortedSet.size();
    return count > maxRequests;
}

Redis操作序列

  1. ZREMRANGEBYSCORE key 0 windowStart:刪除過(guò)期請(qǐng)求
  2. ZADD key currentTime currentTime:添加當(dāng)前請(qǐng)求
  3. ZCARD key:獲取當(dāng)前請(qǐng)求數(shù)

6. 時(shí)間單位轉(zhuǎn)換 - convertToMillis()

private long convertToMillis(int windowSize, TimeUnit timeUnit) {
    return switch (timeUnit) {
        case SECONDS -> timeUnit.toMillis(windowSize);
        case MINUTES -> timeUnit.toMillis(windowSize);
        case HOURS -> timeUnit.toMillis(windowSize);
        case DAYS -> timeUnit.toMillis(windowSize);
        case MILLISECONDS -> windowSize;
        default -> throw new IllegalArgumentException("不支持的時(shí)間單位: " + timeUnit);
    };
}

完整代碼

@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
public class SlidingWindowLimitAspect {
    @Resource
    private RedissonClient redissonClient;
    private static final Logger log = LoggerFactory.getLogger(SlidingWindowLimitAspect.class);

    private static final String RATE_LIMIT_PREFIX = "rate_limit";

    @Around("@annotation(slidingWindowLimit)")
    public Object around(ProceedingJoinPoint joinPoint, SlidingWindowLimit slidingWindowLimit) throws Throwable {
        //獲取方法簽名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String methodName = method.getName();
        //判斷是否為Http請(qǐng)求
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if(attributes == null){
            log.warn("方法 {} 不在 Web 請(qǐng)求上下文中,跳過(guò)限流檢查。", methodName);
            return joinPoint.proceed();
        }
        try {
            Object parseSpEL = parseSpEL(joinPoint, signature, slidingWindowLimit.keySpEL());
            String rateLimitKey = buildRateLimitKey(parseSpEL, slidingWindowLimit);
            if(isRateLimited(rateLimitKey, slidingWindowLimit.windowSize(), slidingWindowLimit.timeUnit(), slidingWindowLimit.maxRequests())){
                throw new RateLimitExceededException("Rate limit exceeded");
            }
            return joinPoint.proceed();
        }catch (RateLimitExceededException e){
            log.warn("方法 {} 觸發(fā)了限流,已拒絕訪問(wèn)。", methodName);
            throw e;
        }catch (Exception e){
            log.error("方法 {} 觸發(fā)了異常,已拒絕訪問(wèn)。", methodName, e);
            throw e;
        }
    }

    private Object parseSpEL(ProceedingJoinPoint joinPoint,MethodSignature signature, String keySpEL){
        StandardEvaluationContext context = new StandardEvaluationContext();

        Object[] args = joinPoint.getArgs();
        String[] parameterNames = signature.getParameterNames();

        for (int i = 0; i < args.length; i++) {
            if(parameterNames!=null && i< parameterNames.length){
                context.setVariable(parameterNames[i], args[i]);
            }else {
                context.setVariable("arg" + i, args[i]);
            }
        }

        ExpressionParser parser = new SpelExpressionParser();
        return parser.parseExpression(keySpEL).getValue(context);
    }

    private String buildRateLimitKey(Object keyValue, SlidingWindowLimit slidingWindowLimit){
        if(keyValue == null){
            throw new IllegalArgumentException("限流參數(shù)不能為空");
        }
        return String.format("%s:%s:%s",RATE_LIMIT_PREFIX,slidingWindowLimit.keySpEL(), keyValue);
    }

    private boolean isRateLimited(String key, int windowSize, TimeUnit timeUnit, int maxRequests){
        //獲取當(dāng)前時(shí)間數(shù)
        long currentTime = System.currentTimeMillis();
        long windowStartTime = currentTime - convertToMillis(windowSize, timeUnit);
        // 獲取 Redisson 的 ZSet 操作對(duì)象
        RScoredSortedSet<Long> scoredSortedSet = redissonClient.getScoredSortedSet(key);

        // 1. 刪除窗口外的過(guò)期請(qǐng)求
        scoredSortedSet.removeRangeByScore(0, true, windowStartTime, true); // [0, windowStartTime]

        // 2. 添加當(dāng)前請(qǐng)求的時(shí)間戳到 ZSet
        scoredSortedSet.add(currentTime, currentTime); // score 和 value 均為時(shí)間戳

        // 3. 統(tǒng)計(jì)窗口內(nèi)請(qǐng)求數(shù)量
        int count = scoredSortedSet.size();
        return count > maxRequests;
    }

    /**
     * 時(shí)間單位轉(zhuǎn)換,將時(shí)間單位轉(zhuǎn)換為毫秒數(shù)
     * @param windowSize 窗口大小
     * @param timeUnit 時(shí)間單位
     * @return
     */
    private long convertToMillis(int windowSize, TimeUnit timeUnit){
        return switch (timeUnit){
            case NANOSECONDS, SECONDS, MICROSECONDS, MINUTES, HOURS, DAYS -> timeUnit.toMillis(windowSize);
            case MILLISECONDS -> windowSize;
            default -> throw new IllegalArgumentException("不支持的時(shí)間單位: " + timeUnit);
        };
    }
}

注解使用

比如說(shuō)我們現(xiàn)在定義發(fā)送驗(yàn)證碼的方法60秒內(nèi)只能發(fā)送三次

@PostMapping("/send/code")
@SlidingWindowLimit(keySpEL = "#email.email",windowSize = 60, maxRequests = 3)
public Result sendVerificationCode(@RequestBody EmailSendDTO email) {
    userService.sendVerificationCode(email.getEmail());
    return Result.success();
}

每一次訪問(wèn)時(shí),Redis都會(huì)記錄下時(shí)間戳,如果第四次訪問(wèn)時(shí)的時(shí)間戳與第一次訪問(wèn)的時(shí)間戳之間少于60秒,則返回

{
    "code": 429,
    "message": "請(qǐng)求過(guò)于頻繁,請(qǐng)稍后再試",
    "data": null
}

優(yōu)化

private boolean isRateLimited(String key, int windowSize, TimeUnit timeUnit, int maxRequests){
    //獲取當(dāng)前時(shí)間數(shù)
    long currentTime = System.currentTimeMillis();
    long windowStartTime = currentTime - convertToMillis(windowSize, timeUnit);
    // 獲取 Redisson 的 ZSet 操作對(duì)象
    RScoredSortedSet<Long> scoredSortedSet = redissonClient.getScoredSortedSet(key);

    // 1. 刪除窗口外的過(guò)期請(qǐng)求
    scoredSortedSet.removeRangeByScore(0, true, windowStartTime, true); // [0, windowStartTime]

    // 2. 添加當(dāng)前請(qǐng)求的時(shí)間戳到 ZSet
    scoredSortedSet.add(currentTime, currentTime); // score 和 value 均為時(shí)間戳

    // 3. 統(tǒng)計(jì)窗口內(nèi)請(qǐng)求數(shù)量
    int count = scoredSortedSet.size();
    return count > maxRequests;
}

在這個(gè)方法中,存在幾個(gè)問(wèn)題:

代碼中直接將時(shí)間戳作為ZSET的成員(member)和分?jǐn)?shù)(score),當(dāng)同一毫秒內(nèi)有多個(gè)請(qǐng)求時(shí),后寫(xiě)入的請(qǐng)求會(huì)覆蓋先前的請(qǐng)求(ZSET成員唯一),導(dǎo)致計(jì)數(shù)不準(zhǔn)確。

當(dāng)前操作序列:

在并發(fā)場(chǎng)景下,多個(gè)請(qǐng)求可能同時(shí)通過(guò)計(jì)數(shù)檢查,導(dǎo)致實(shí)際請(qǐng)求量超過(guò)閾值

  • 刪除過(guò)期請(qǐng)求
  • 添加當(dāng)前請(qǐng)求
  • 獲取當(dāng)前計(jì)數(shù)

當(dāng)某個(gè)限流鍵長(zhǎng)時(shí)間無(wú)請(qǐng)求時(shí),對(duì)應(yīng)的空Z(yǔ)SET會(huì)永久占用內(nèi)存

那么優(yōu)化時(shí),可以利用UUID作為member 這樣不會(huì)出現(xiàn)覆蓋的情況,使用Lua腳本進(jìn)行執(zhí)行避免多個(gè)請(qǐng)求同時(shí)通過(guò)計(jì)數(shù)檢查的情況,針對(duì)問(wèn)題三可以通過(guò)設(shè)置過(guò)期時(shí)間來(lái)解決,優(yōu)化后的代碼如下:

private boolean isRateLimited(String key, int windowSize, TimeUnit timeUnit, int maxRequests) {
    // 1. 計(jì)算窗口大?。ê撩耄?
    long windowMillis = convertToMillis(windowSize, timeUnit);
    // 2. 獲取當(dāng)前時(shí)間和窗口起始時(shí)間
    long currentTime = System.currentTimeMillis();
    long windowStartTime = currentTime - windowMillis;
    // 3. 生成唯一請(qǐng)求ID
    String requestId = UUID.randomUUID().toString();
    // 4. 計(jì)算過(guò)期時(shí)間(秒)
    long expireSeconds = calculateExpireSeconds(windowMillis);
    // 5. Lua腳本(使用分?jǐn)?shù)范圍精確統(tǒng)計(jì))
    String luaScript =
            "redis.call('ZREMRANGEBYSCORE', KEYS[1], '-inf', ARGV[2])\n" +  // 清理過(guò)期數(shù)據(jù)
                    "local count = redis.call('ZCOUNT', KEYS[1], ARGV[2], ARGV[1])\n" +  // 精確統(tǒng)計(jì)窗口內(nèi)請(qǐng)求
                    "if count >= tonumber(ARGV[4]) then\n" +
                    "    return 1\n" +  // 觸發(fā)限流
                    "end\n" +
                    "redis.call('ZADD', KEYS[1], ARGV[1], ARGV[3])\n" +  // 添加當(dāng)前請(qǐng)求
                    "redis.call('EXPIRE', KEYS[1], ARGV[5])\n" +  // 設(shè)置過(guò)期時(shí)間
                    "return 0";  // 允許通過(guò)

    try {
        RScript script = redissonClient.getScript();
        Long result = script.eval(
                RScript.Mode.READ_WRITE,
                luaScript,
                RScript.ReturnType.INTEGER,
                Collections.singletonList(key),
                currentTime, windowStartTime, requestId, maxRequests, expireSeconds
        );
        return result != null && result == 1;
    } catch (Exception e) {
        log.error("限流服務(wù)異常,降級(jí)放行", e);
        return false; // Redis故障時(shí)允許請(qǐng)求
    }
}
private long calculateExpireSeconds(long windowMillis) {
    // 過(guò)期時(shí)間 = 2 * 窗口大小(秒),向上取整
    double expireSec = (windowMillis * 2.0) / 1000;
    long result = (long) Math.ceil(expireSec);
    return Math.max(1, result); // 至少1秒
}

Lua 腳本執(zhí)行邏輯:

  • redis.call('ZREMRANGEBYSCORE', KEYS[1], '-inf', ARGV[2]):清理過(guò)期數(shù)據(jù),將 ZSET 中分?jǐn)?shù)小于等于窗口起始時(shí)間的成員刪除。
  • local count = redis.call('ZCOUNT', KEYS[1], ARGV[2], ARGV[1]):精確統(tǒng)計(jì)窗口內(nèi)請(qǐng)求數(shù)量,即分?jǐn)?shù)在窗口起始時(shí)間和當(dāng)前時(shí)間之間的成員數(shù)量。
  • if count >= tonumber(ARGV[4]) then return 1 end:如果統(tǒng)計(jì)的請(qǐng)求數(shù)量大于等于閾值,則返回 1,表示觸發(fā)限流。
  • redis.call('ZADD', KEYS[1], ARGV[1], ARGV[3]):添加當(dāng)前請(qǐng)求,將當(dāng)前請(qǐng)求的唯一 ID 作為成員,當(dāng)前時(shí)間作為分?jǐn)?shù)添加到 ZSET 中。
  • redis.call('EXPIRE', KEYS[1], ARGV[5]):設(shè)置 ZSET 的過(guò)期時(shí)間,避免長(zhǎng)時(shí)間無(wú)請(qǐng)求時(shí)空 ZSET 占用內(nèi)存。
  • return 0:如果未觸發(fā)限流,則返回 0,表示允許請(qǐng)求通過(guò)。

我們從命令行可以看到,每一次請(qǐng)求后,都會(huì)在ZSET中多一條記錄,并且每次都會(huì)重置過(guò)期時(shí)間,當(dāng)觸發(fā)限流后,不再允許訪問(wèn)。

127.0.0.1:6379> ZRANGE rate_limit:#email.email:6888@example.com 0 -1
1) "fbd525dd-0e1e-4abf-a578-8c1207e8f6f0"
127.0.0.1:6379> TTL rate_limit:#email.email:6888@example.com
(integer) 355
127.0.0.1:6379> ZRANGE rate_limit:#email.email:6888@example.com 0 -1
1) "fbd525dd-0e1e-4abf-a578-8c1207e8f6f0"
2) "a7c8d5c2-f4da-46ce-9f98-472ad702e1ad"
127.0.0.1:6379> TTL rate_limit:#email.email:6888@example.com
(integer) 355
127.0.0.1:6379> ZRANGE rate_limit:#email.email:6888@example.com 0 -1
1) "fbd525dd-0e1e-4abf-a578-8c1207e8f6f0"
2) "a7c8d5c2-f4da-46ce-9f98-472ad702e1ad"
3) "12ed502c-6225-45bd-b85e-1d3ed9e46ef6"
127.0.0.1:6379> TTL rate_limit:#email.email:6888@example.com
(integer) 355

到此這篇關(guān)于基于AOP+Redis的簡(jiǎn)易滑動(dòng)窗口限流的文章就介紹到這了,更多相關(guān)AOP+Redis滑動(dòng)窗口限流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家! 

相關(guān)文章

  • @SpringBootConfiguration重復(fù)加載報(bào)錯(cuò)問(wèn)題解決

    @SpringBootConfiguration重復(fù)加載報(bào)錯(cuò)問(wèn)題解決

    @SpringBootApplication?注解的?exclude?屬性用于排除特定的自動(dòng)配置類(lèi),而不是用于排除主配置類(lèi)本身,因此,不能通過(guò)?exclude?屬性來(lái)排除主配置類(lèi)的加載,這篇文章主要介紹了@SpringBootConfiguration重復(fù)加載報(bào)錯(cuò),需要的朋友可以參考下
    2024-08-08
  • Java8 中使用Stream 讓List 轉(zhuǎn) Map使用問(wèn)題小結(jié)

    Java8 中使用Stream 讓List 轉(zhuǎn) Map使用問(wèn)題小結(jié)

    這篇文章主要介紹了Java8 中使用Stream 讓List 轉(zhuǎn) Map使用總結(jié),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-06-06
  • Hikari連接池使用SpringBoot配置JMX監(jiān)控實(shí)現(xiàn)

    Hikari連接池使用SpringBoot配置JMX監(jiān)控實(shí)現(xiàn)

    Hikari是Spring Boot默認(rèn)的數(shù)據(jù)庫(kù)連接池。區(qū)別于C3P0直接通過(guò)連接池對(duì)象獲取各項(xiàng)狀態(tài)指標(biāo),Hikari需要通過(guò)JMX來(lái)獲取。本文就詳細(xì)的來(lái)介紹一下,感興趣的可以了解一下
    2021-07-07
  • Spring?BeanFactory?與?FactoryBean?的區(qū)別詳情

    Spring?BeanFactory?與?FactoryBean?的區(qū)別詳情

    這篇文章主要介紹了Spring?BeanFactory?與?FactoryBean?的區(qū)別詳情,BeanFactory?和?FactoryBean?的區(qū)別卻是一個(gè)很重要的知識(shí)點(diǎn),在本文中將結(jié)合源碼進(jìn)行分析講解,需要的小伙伴可以參考一下
    2022-05-05
  • MyBatis中$與#的區(qū)別解析

    MyBatis中$與#的區(qū)別解析

    文章瀏覽閱讀314次,點(diǎn)贊4次,收藏6次。MyBatis使用#{}作為參數(shù)占位符時(shí),會(huì)創(chuàng)建預(yù)處理語(yǔ)句(Prepared Statement),并將參數(shù)值作為預(yù)處理語(yǔ)句的參數(shù)綁定到SQL語(yǔ)句中。使用。
    2025-07-07
  • java如何獲取yml配置文件工具類(lèi)

    java如何獲取yml配置文件工具類(lèi)

    本文介紹了java獲取yml配置文件的工具類(lèi)引入pom文件的方法,首先,創(chuàng)建一個(gè)與工具類(lèi)同名的類(lèi),然后將工具類(lèi)的代碼復(fù)制到新創(chuàng)建的類(lèi)中,運(yùn)行后輸出:0*/1***?完成操作,此方法簡(jiǎn)單易行,適合初學(xué)者使用
    2024-10-10
  • Java8的Stream()與ParallelStream()的區(qū)別說(shuō)明

    Java8的Stream()與ParallelStream()的區(qū)別說(shuō)明

    這篇文章主要介紹了Java8的Stream()與ParallelStream()的區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Spring中配置文件XML驗(yàn)證錯(cuò)誤全面解決指南

    Spring中配置文件XML驗(yàn)證錯(cuò)誤全面解決指南

    這篇文章主要為大家詳細(xì)介紹了Spring中配置文件XML驗(yàn)證錯(cuò)誤全面解決方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-11-11
  • SpringBoot整合RabbitMQ實(shí)現(xiàn)延遲隊(duì)列和死信隊(duì)列

    SpringBoot整合RabbitMQ實(shí)現(xiàn)延遲隊(duì)列和死信隊(duì)列

    RabbitMQ的死信隊(duì)列用于接收其他隊(duì)列中的“死信”消息,所謂“死信”,是指滿(mǎn)足一定條件而無(wú)法被消費(fèi)者正確處理的消息,死信隊(duì)列通常與RabbitMQ的延遲隊(duì)列一起使用,本文給大家介紹了SpringBoot整合RabbitMQ實(shí)現(xiàn)延遲隊(duì)列和死信隊(duì)列,需要的朋友可以參考下
    2024-06-06
  • java實(shí)現(xiàn)的根據(jù)概率隨機(jī)中獎(jiǎng)測(cè)試類(lèi)

    java實(shí)現(xiàn)的根據(jù)概率隨機(jī)中獎(jiǎng)測(cè)試類(lèi)

    這篇文章主要介紹了java實(shí)現(xiàn)的根據(jù)概率隨機(jī)中獎(jiǎng)測(cè)試類(lèi),結(jié)合完整實(shí)例形式詳細(xì)分析了java隨機(jī)數(shù)實(shí)現(xiàn)概率運(yùn)算相關(guān)操作技巧,需要的朋友可以參考下
    2019-09-09

最新評(píng)論

辽宁省| 小金县| 香港| 盈江县| 综艺| 特克斯县| 海宁市| 大连市| 遵化市| 华蓥市| 郓城县| 禄劝| 弥勒县| 苍山县| 贡山| 武穴市| 大港区| 柳州市| 清流县| 六枝特区| 安庆市| 土默特右旗| 大港区| 綦江县| 沁源县| 湖北省| 丹寨县| 涞水县| 如皋市| 新密市| 重庆市| 阿勒泰市| 滁州市| 遂溪县| 天峨县| 苏州市| 宿迁市| 白水县| 健康| 凤城市| 鄯善县|