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

Redis 多規(guī)則限流和防重復(fù)提交方案實(shí)現(xiàn)小結(jié)

 更新時(shí)間:2025年02月06日 10:14:27   作者:A塵埃  
本文主要介紹了Redis 多規(guī)則限流和防重復(fù)提交方案實(shí)現(xiàn)小結(jié),包括使用String結(jié)構(gòu)和Zset結(jié)構(gòu)來(lái)記錄用戶IP的訪問(wèn)次數(shù),具有一定的參考價(jià)值,感興趣的可以了解一下

Redis 如何實(shí)現(xiàn)限流的,但是大部分都有一個(gè)缺點(diǎn),就是只能實(shí)現(xiàn)單一的限流,比如 1 分鐘訪問(wèn) 1 次或者 60 分鐘訪問(wèn) 10 次這種,
但是如果想一個(gè)接口兩種規(guī)則都需要滿足呢,項(xiàng)目又是分布式項(xiàng)目,應(yīng)該如何解決,下面就介紹一下 Redis 實(shí)現(xiàn)分布式多規(guī)則限流的方式。

  • 如何一分鐘只能發(fā)送一次驗(yàn)證碼,一小時(shí)只能發(fā)送 10 次驗(yàn)證碼等等多種規(guī)則的限流;
  • 如何防止接口被惡意打擊(短時(shí)間內(nèi)大量請(qǐng)求);
  • 如何限制接口規(guī)定時(shí)間內(nèi)訪問(wèn)次數(shù)。

一:使用 String 結(jié)構(gòu)記錄固定時(shí)間段內(nèi)某用戶 IP 訪問(wèn)某接口的次數(shù)

  • RedisKey = prefix : className : methodName
  • RedisVlue = 訪問(wèn)次數(shù)

攔截請(qǐng)求:

  • 初次訪問(wèn)時(shí)設(shè)置 [RedisKey] [RedisValue=1] [規(guī)定的過(guò)期時(shí)間];
  • 獲取 RedisValue 是否超過(guò)規(guī)定次數(shù),超過(guò)則攔截,未超過(guò)則對(duì) RedisKey 進(jìn)行加1。

規(guī)則是每分鐘訪問(wèn) 1000 次

  • 假設(shè)目前 RedisKey => RedisValue 為 999;
  • 目前大量請(qǐng)求進(jìn)行到第一步( 獲取 Redis 請(qǐng)求次數(shù) ),那么所有線程都獲取到了值為999,進(jìn)行判斷都未超過(guò)限定次數(shù)則不攔截,導(dǎo)致實(shí)際次數(shù)超過(guò) 1000 次
  • 解決辦法: 保證方法執(zhí)行原子性(加鎖、Lua)。

考慮在臨界值進(jìn)行訪問(wèn)

在這里插入圖片描述

二:使用 Zset 進(jìn)行存儲(chǔ),解決臨界值訪問(wèn)問(wèn)題

在這里插入圖片描述

三:實(shí)現(xiàn)多規(guī)則限流

①、先確定最終需要的效果(能實(shí)現(xiàn)多種限流規(guī)則+能實(shí)現(xiàn)防重復(fù)提交)

@RateLimiter(
        rules = {
                // 60秒內(nèi)只能訪問(wèn)10次
                @RateRule(count = 10, time = 60, timeUnit = TimeUnit.SECONDS),
                // 120秒內(nèi)只能訪問(wèn)20次
                @RateRule(count = 20, time = 120, timeUnit = TimeUnit.SECONDS)

        },
        // 防重復(fù)提交 (5秒鐘只能訪問(wèn)1次)
        preventDuplicate = true
)

②、注解編寫

RateLimiter 注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface RateLimiter {

    /**
     * 限流key
     */
    String key() default RedisKeyConstants.RATE_LIMIT_CACHE_PREFIX;

    /**
     * 限流類型 ( 默認(rèn) Ip 模式 )
     */
    LimitTypeEnum limitType() default LimitTypeEnum.IP;

    /**
     * 錯(cuò)誤提示
     */
    ResultCode message() default ResultCode.REQUEST_MORE_ERROR;

    /**
     * 限流規(guī)則 (規(guī)則不可變,可多規(guī)則)
     */
    RateRule[] rules() default {};

    /**
     * 防重復(fù)提交值
     */
    boolean preventDuplicate() default false;

    /**
     * 防重復(fù)提交默認(rèn)值
     */
    RateRule preventDuplicateRule() default @RateRule(count = 1, time = 5);
}

RateRule 注解:

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface RateRule {

    /**
     * 限流次數(shù)
     */
    long count() default 10;

    /**
     * 限流時(shí)間
     */
    long time() default 60;

    /**
     * 限流時(shí)間單位
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

}

③、攔截注解 RateLimiter

  • 確定 Redis 存儲(chǔ)方式
    RedisKey = prefix : className : methodName
    RedisScore = 時(shí)間戳
    RedisValue = 任意分布式不重復(fù)的值即可
  • 編寫生成 RedisKey 的方法
public String getCombineKey(RateLimiter rateLimiter, JoinPoint joinPoint) {
    StringBuffer key = new StringBuffer(rateLimiter.key());
    // 不同限流類型使用不同的前綴
    switch (rateLimiter.limitType()) {
        // XXX 可以新增通過(guò)參數(shù)指定參數(shù)進(jìn)行限流
        case IP:
            key.append(IpUtil.getIpAddr(((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest())).append(":");
            break;
        case USER_ID:
            SysUserDetails user = SecurityUtil.getUser();
            if (!ObjectUtils.isEmpty(user)) key.append(user.getUserId()).append(":");
            break;
        case GLOBAL:
            break;
    }
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    Method method = signature.getMethod();
    Class<?> targetClass = method.getDeclaringClass();
    key.append(targetClass.getSimpleName()).append("-").append(method.getName());
    return key.toString();
}

④、編寫Lua腳本(兩種將事件添加到Redis的方法)

Ⅰ:UUID(可用其他有相同的特性的值)為 Zset 中的 value 值

  • 參數(shù)介紹:
    KEYS[1] = prefix : ? : className : methodName
    KEYS[2] = 唯一ID
    KEYS[3] = 當(dāng)前時(shí)間
    ARGV = [次數(shù),單位時(shí)間,次數(shù),單位時(shí)間, 次數(shù), 單位時(shí)間 …]
  • 由 Java傳入分布式不重復(fù)的 value 值
-- 1. 獲取參數(shù)
local key = KEYS[1]
local uuid = KEYS[2]
local currentTime = tonumber(KEYS[3])
-- 2. 以數(shù)組最大值為 ttl 最大值
local expireTime = -1;
-- 3. 遍歷數(shù)組查看是否超過(guò)限流規(guī)則
for i = 1, #ARGV, 2 do
    local rateRuleCount = tonumber(ARGV[i])
    local rateRuleTime = tonumber(ARGV[i + 1])
    -- 3.1 判斷在單位時(shí)間內(nèi)訪問(wèn)次數(shù)
    local count = redis.call('ZCOUNT', key, currentTime - rateRuleTime, currentTime)
    -- 3.2 判斷是否超過(guò)規(guī)定次數(shù)
    if tonumber(count) >= rateRuleCount then
        return true
    end
    -- 3.3 判斷元素最大值,設(shè)置為最終過(guò)期時(shí)間
    if rateRuleTime > expireTime then
        expireTime = rateRuleTime
    end
end
-- 4. redis 中添加當(dāng)前時(shí)間
redis.call('ZADD', key, currentTime, uuid)
-- 5. 更新緩存過(guò)期時(shí)間
redis.call('PEXPIRE', key, expireTime)
-- 6. 刪除最大時(shí)間限度之前的數(shù)據(jù),防止數(shù)據(jù)過(guò)多
redis.call('ZREMRANGEBYSCORE', key, 0, currentTime - expireTime)
return false

Ⅱ、根據(jù)時(shí)間戳作為 Zset 中的 value 值

  • 參數(shù)介紹
    KEYS[1] = prefix : ? : className : methodName
    KEYS[2] = 當(dāng)前時(shí)間
    ARGV = [次數(shù),單位時(shí)間,次數(shù),單位時(shí)間, 次數(shù), 單位時(shí)間 …]
  • 根據(jù)時(shí)間進(jìn)行生成 value 值,考慮同一毫秒添加相同時(shí)間值問(wèn)題
    以下為第二種實(shí)現(xiàn)方式,在并發(fā)高的情況下效率低,value 是通過(guò)時(shí)間戳進(jìn)行添加,但是訪問(wèn)量大的話會(huì)使得一直在調(diào)用 redis.call(‘ZADD’, key, currentTime, currentTime),但是在不沖突 value 的情況下,會(huì)比生成 UUID 好。
-- 1. 獲取參數(shù)
local key = KEYS[1]
local currentTime = KEYS[2]
-- 2. 以數(shù)組最大值為 ttl 最大值
local expireTime = -1;
-- 3. 遍歷數(shù)組查看是否越界
for i = 1, #ARGV, 2 do
    local rateRuleCount = tonumber(ARGV[i])
    local rateRuleTime = tonumber(ARGV[i + 1])
    -- 3.1 判斷在單位時(shí)間內(nèi)訪問(wèn)次數(shù)
    local count = redis.call('ZCOUNT', key, currentTime - rateRuleTime, currentTime)
    -- 3.2 判斷是否超過(guò)規(guī)定次數(shù)
    if tonumber(count) >= rateRuleCount then
        return true
    end
    -- 3.3 判斷元素最大值,設(shè)置為最終過(guò)期時(shí)間
    if rateRuleTime > expireTime then
        expireTime = rateRuleTime
    end
end
-- 4. 更新緩存過(guò)期時(shí)間
redis.call('PEXPIRE', key, expireTime)
-- 5. 刪除最大時(shí)間限度之前的數(shù)據(jù),防止數(shù)據(jù)過(guò)多
redis.call('ZREMRANGEBYSCORE', key, 0, currentTime - expireTime)
-- 6. redis 中添加當(dāng)前時(shí)間  ( 解決多個(gè)線程在同一毫秒添加相同 value 導(dǎo)致 Redis 漏記的問(wèn)題 )
-- 6.1 maxRetries 最大重試次數(shù) retries 重試次數(shù)
local maxRetries = 5
local retries = 0
while true do
    local result = redis.call('ZADD', key, currentTime, currentTime)
    if result == 1 then
        -- 6.2 添加成功則跳出循環(huán)
        break
    else
        -- 6.3 未添加成功則 value + 1 再次進(jìn)行嘗試
        retries = retries + 1
        if retries >= maxRetries then
            -- 6.4 超過(guò)最大嘗試次數(shù) 采用添加隨機(jī)數(shù)策略
            local random_value = math.random(1, 1000)
            currentTime = currentTime + random_value
        else
            currentTime = currentTime + 1
        end
    end
end

return false

⑤、編寫AOP攔截

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private RedisScript<Boolean> limitScript;

/**
 * 限流
 * XXX 對(duì)限流要求比較高,可以使用在 Redis中對(duì)規(guī)則進(jìn)行存儲(chǔ)校驗(yàn) 或者使用中間件
 *
 * @param joinPoint   joinPoint
 * @param rateLimiter 限流注解
 */
@Before(value = "@annotation(rateLimiter)")
public void boBefore(JoinPoint joinPoint, RateLimiter rateLimiter) {
    // 1. 生成 key
    String key = getCombineKey(rateLimiter, joinPoint);
    try {
        // 2. 執(zhí)行腳本返回是否限流
        Boolean flag = redisTemplate.execute(limitScript,
                ListUtil.of(key, String.valueOf(System.currentTimeMillis())),
                (Object[]) getRules(rateLimiter));
        // 3. 判斷是否限流
        if (Boolean.TRUE.equals(flag)) {
            log.error("ip: '{}' 攔截到一個(gè)請(qǐng)求 RedisKey: '{}'",
                    IpUtil.getIpAddr(((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest()),
                    key);
            throw new ServiceException(rateLimiter.message());
        }
    } catch (ServiceException e) {
        throw e;
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * 獲取規(guī)則
 *
 * @param rateLimiter 獲取其中規(guī)則信息
 * @return
 */
private Long[] getRules(RateLimiter rateLimiter) {
    int capacity = rateLimiter.rules().length << 1;
    // 1. 構(gòu)建 args
    Long[] args = new Long[rateLimiter.preventDuplicate() ? capacity + 2 : capacity];
    // 3. 記錄數(shù)組元素
    int index = 0;
    // 2. 判斷是否需要添加防重復(fù)提交到redis進(jìn)行校驗(yàn)
    if (rateLimiter.preventDuplicate()) {
        RateRule preventRateRule = rateLimiter.preventDuplicateRule();
        args[index++] = preventRateRule.count();
        args[index++] = preventRateRule.timeUnit().toMillis(preventRateRule.time());
    }
    RateRule[] rules = rateLimiter.rules();
    for (RateRule rule : rules) {
        args[index++] = rule.count();
        args[index++] = rule.timeUnit().toMillis(rule.time());
    }
    return args;
}

到此這篇關(guān)于Redis 多規(guī)則限流和防重復(fù)提交方案實(shí)現(xiàn)小結(jié)的文章就介紹到這了,更多相關(guān)Redis 多規(guī)則限流和防重復(fù)提交內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家! 

相關(guān)文章

  • 解析Redis未授權(quán)訪問(wèn)漏洞復(fù)現(xiàn)與利用危害

    解析Redis未授權(quán)訪問(wèn)漏洞復(fù)現(xiàn)與利用危害

    這篇文章主要介紹了Redis未授權(quán)訪問(wèn)漏洞復(fù)現(xiàn)與利用,介紹了redis未授權(quán)訪問(wèn)漏洞的基本概念及漏洞的危害,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-01-01
  • Redis命令處理過(guò)程源碼解析

    Redis命令處理過(guò)程源碼解析

    這篇文章主要介紹了Redis命令處理過(guò)程源碼解析,本文是基于社區(qū)版redis4.0.8,通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-02-02
  • Redis連接錯(cuò)誤的情況總結(jié)分析

    Redis連接錯(cuò)誤的情況總結(jié)分析

    這篇文章主要給大家總結(jié)介紹了關(guān)于Redis連接錯(cuò)誤的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-02-02
  • Redis的Cluster集群搭建的實(shí)現(xiàn)步驟

    Redis的Cluster集群搭建的實(shí)現(xiàn)步驟

    本文檔只對(duì)Redis的Cluster集群做簡(jiǎn)單的介紹,并沒有對(duì)分布式系統(tǒng)的所涉及到的概念做深入的探討。感興趣的小伙伴們可以參考一下
    2021-07-07
  • RedisDesktopManager?連接redis的方法

    RedisDesktopManager?連接redis的方法

    這篇文章主要介紹了RedisDesktopManager?連接redis,需要的朋友可以參考下
    2023-08-08
  • AOP?Redis自定義注解實(shí)現(xiàn)細(xì)粒度接口IP訪問(wèn)限制

    AOP?Redis自定義注解實(shí)現(xiàn)細(xì)粒度接口IP訪問(wèn)限制

    這篇文章主要為大家介紹了AOP?Redis自定義注解實(shí)現(xiàn)細(xì)粒度接口IP訪問(wèn)限制,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • 如何保證Redis與數(shù)據(jù)庫(kù)的數(shù)據(jù)一致性

    如何保證Redis與數(shù)據(jù)庫(kù)的數(shù)據(jù)一致性

    這篇文章主要介紹了如何保證Redis與數(shù)據(jù)庫(kù)的數(shù)據(jù)一致性,文中舉了兩個(gè)場(chǎng)景例子介紹的非常詳細(xì),需要的朋友可以參考下
    2023-05-05
  • Redis 單機(jī)安裝和哨兵模式集群安裝的實(shí)現(xiàn)

    Redis 單機(jī)安裝和哨兵模式集群安裝的實(shí)現(xiàn)

    本文主要介紹了Redis 單機(jī)安裝和哨兵模式集群安裝的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • caffeine_redis自定義二級(jí)緩存

    caffeine_redis自定義二級(jí)緩存

    這篇文章詳細(xì)介紹了caffeine_redis 自定義二級(jí)緩存,文中有相關(guān)的背景前提與出現(xiàn)的問(wèn)題,感興趣的同學(xué)可以參考一下
    2023-04-04
  • redis3.2配置文件redis.conf詳細(xì)說(shuō)明

    redis3.2配置文件redis.conf詳細(xì)說(shuō)明

    redis3.2配置詳解,Redis啟動(dòng)的時(shí)候,可以指定配置文件,詳細(xì)說(shuō)明請(qǐng)看本文說(shuō)明
    2018-03-03

最新評(píng)論

乃东县| 绍兴县| 博兴县| 兰考县| 大城县| 金沙县| 贺兰县| 山西省| 高阳县| 玉门市| 庄浪县| 苍溪县| 理塘县| 忻城县| 会东县| 红安县| 盱眙县| 宁安市| 黎城县| 玛纳斯县| 镇坪县| 岚皋县| 南京市| 安国市| 高雄市| 上思县| 东平县| 启东市| 禄丰县| 封丘县| 库伦旗| 云龙县| 阿勒泰市| 颍上县| 莱芜市| 新郑市| 仪陇县| 曲水县| 永春县| 长乐市| 潜山县|