国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

SpringBoot限制接口訪問(wèn)頻率 - 這些錯(cuò)誤千萬(wàn)不能犯

這篇具有很好參考價(jià)值的文章主要介紹了SpringBoot限制接口訪問(wèn)頻率 - 這些錯(cuò)誤千萬(wàn)不能犯。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

最近在基于SpringBoot做一個(gè)面向普通用戶(hù)的系統(tǒng),為了保證系統(tǒng)的穩(wěn)定性,防止被惡意攻擊,我想控制用戶(hù)訪問(wèn)每個(gè)接口的頻率。為了實(shí)現(xiàn)這個(gè)功能,可以設(shè)計(jì)一個(gè)annotation,然后借助AOP在調(diào)用方法之前檢查當(dāng)前ip的訪問(wèn)頻率,如果超過(guò)設(shè)定頻率,直接返回錯(cuò)誤信息。

常見(jiàn)的錯(cuò)誤設(shè)計(jì)

在開(kāi)始介紹具體實(shí)現(xiàn)之前,我先列舉幾種我在網(wǎng)上找到的幾種常見(jiàn)錯(cuò)誤設(shè)計(jì)。

1. 固定窗口

有人設(shè)計(jì)了一個(gè)在每分鐘內(nèi)只允許訪問(wèn)1000次的限流方案,如下圖01:00s-02:00s之間只允許訪問(wèn)1000次,這種設(shè)計(jì)最大的問(wèn)題在于,請(qǐng)求可能在01:59s-02:00s之間被請(qǐng)求1000次,02:00s-02:01s之間被請(qǐng)求了1000次,這種情況下01:59s-02:01s間隔0.02s之間被請(qǐng)求2000次,很顯然這種設(shè)計(jì)是錯(cuò)誤的。

2. 緩存時(shí)間更新錯(cuò)誤

我在研究這個(gè)問(wèn)題的時(shí)候,發(fā)現(xiàn)網(wǎng)上有一種很常見(jiàn)的方式來(lái)進(jìn)行限流,思路是基于redis,每次有用戶(hù)的request進(jìn)來(lái),就會(huì)去以用戶(hù)的ip和request的url為key去判斷訪問(wèn)次數(shù)是否超標(biāo),如果有就返回錯(cuò)誤,否則就把redis中的key對(duì)應(yīng)的value加1,并重新設(shè)置key的過(guò)期時(shí)間為用戶(hù)指定的訪問(wèn)周期。核心代碼如下:

// core logic
int limit = accessLimit.limit();
long sec = accessLimit.sec();
String key = IPUtils.getIpAddr(request) + request.getRequestURI();
Integer maxLimit =null;
Object value =redisService.get(key);
if(value!=null && !value.equals("")) {
    maxLimit = Integer.valueOf(String.valueOf(value));
}
if (maxLimit == null) {
    redisService.set(key, "1", sec);
} else if (maxLimit < limit) {
    Integer i = maxLimit+1;
    redisService.set(key, i.toString(), sec);
} else {
	throw new BusinessException(500,"請(qǐng)求太頻繁!");
}

// redis related
    public boolean set(final String key, Object value, Long expireTime) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

這里面很大的問(wèn)題,就是每次都會(huì)更新key的緩存過(guò)期時(shí)間,這樣相當(dāng)于變相延長(zhǎng)了每個(gè)計(jì)數(shù)周期, 可能我們想控制用戶(hù)一分鐘內(nèi)只能訪問(wèn)5次,但是如果用戶(hù)在前一分鐘只訪問(wèn)了三次,后一分鐘訪問(wèn)了三次,在上面的實(shí)現(xiàn)里面,很可能在第6次訪問(wèn)的時(shí)候返回錯(cuò)誤,但這樣是有問(wèn)題的,因?yàn)橛脩?hù)確實(shí)在兩分鐘內(nèi)都沒(méi)有超過(guò)對(duì)應(yīng)的訪問(wèn)頻率閾值。

關(guān)于key的刷新這塊,可以參看redis官方文檔,每次refreh都會(huì)更新key的過(guò)期時(shí)間。

基于滑動(dòng)窗口的正確設(shè)計(jì)

指定時(shí)間T內(nèi),只允許發(fā)生N次。我們可以將這個(gè)指定時(shí)間T,看成一個(gè)滑動(dòng)時(shí)間窗口(定寬)。我們采用Redis的zset基本數(shù)據(jù)類(lèi)型的score來(lái)圈出這個(gè)滑動(dòng)時(shí)間窗口。在實(shí)際操作zset的過(guò)程中,我們只需要保留在這個(gè)滑動(dòng)時(shí)間窗口以?xún)?nèi)的數(shù)據(jù),其他的數(shù)據(jù)不處理即可。

比如在上面的例子里面,假設(shè)用戶(hù)的要求是60s內(nèi)訪問(wèn)頻率控制為3次。那么我永遠(yuǎn)只會(huì)統(tǒng)計(jì)當(dāng)前時(shí)間往前倒數(shù)60s之內(nèi)的訪問(wèn)次數(shù),隨著時(shí)間的推移,整個(gè)窗口會(huì)不斷向前移動(dòng),窗口外的請(qǐng)求不會(huì)計(jì)算在內(nèi),保證了永遠(yuǎn)只統(tǒng)計(jì)當(dāng)前60s內(nèi)的request。

為什么選擇Redis zset ?

為了統(tǒng)計(jì)固定時(shí)間區(qū)間內(nèi)的訪問(wèn)頻率,如果是單機(jī)程序,可能采用concurrentHashMap就夠了,但是如果是分布式的程序,我們需要引入相應(yīng)的分布式組件來(lái)進(jìn)行計(jì)數(shù)統(tǒng)計(jì),而Redis zset剛好能夠滿(mǎn)足我們的需求。

Redis zset(有序集合)中的成員是有序排列的,它和 set 集合的相同之處在于,集合中的每一個(gè)成員都是字符串類(lèi)型,并且不允許重復(fù);而它們最大區(qū)別是,有序集合是有序的,set 是無(wú)序的,這是因?yàn)橛行蚣现忻總€(gè)成員都會(huì)關(guān)聯(lián)一個(gè) double(雙精度浮點(diǎn)數(shù))類(lèi)型的 score (分?jǐn)?shù)值),Redis 正是通過(guò) score 實(shí)現(xiàn)了對(duì)集合成員的排序。

Redis 使用以下命令創(chuàng)建一個(gè)有序集合:

ZADD key score member [score member ...]

這里面有三個(gè)重要參數(shù),

  • key:指定一個(gè)鍵名;
  • score:分?jǐn)?shù)值,用來(lái)描述? member,它是實(shí)現(xiàn)排序的關(guān)鍵;
  • member:要添加的成員(元素)。

當(dāng) key 不存在時(shí),將會(huì)創(chuàng)建一個(gè)新的有序集合,并把分?jǐn)?shù)/成員(score/member)添加到有序集合中;當(dāng) key 存在時(shí),但 key 并非 zset 類(lèi)型,此時(shí)就不能完成添加成員的操作,同時(shí)會(huì)返回一個(gè)錯(cuò)誤提示。

在我們這個(gè)場(chǎng)景里面,key就是用戶(hù)ip+request uri,score直接用當(dāng)前時(shí)間的毫秒數(shù)表示,至于member不重要,可以也采用和score一樣的數(shù)值即可。

限流過(guò)程是怎么樣的?

整個(gè)流程如下:

  1. 首先用戶(hù)的請(qǐng)求進(jìn)來(lái),將用戶(hù)ip和uri組成key,timestamp為value,放入zset
  2. 更新當(dāng)前key的緩存過(guò)期時(shí)間,這一步主要是為了定期清理掉冷數(shù)據(jù),和上面我提到的常見(jiàn)錯(cuò)誤設(shè)計(jì)2中的意義不同。
  3. 刪除窗口之外的數(shù)據(jù)記錄。
  4. 統(tǒng)計(jì)當(dāng)前窗口中的總記錄數(shù)。
  5. 如果記錄數(shù)大于閾值,則直接返回錯(cuò)誤,否則正常處理用戶(hù)請(qǐng)求。

基于SpringBoot和AOP的限流

這一部分主要介紹具體的實(shí)現(xiàn)邏輯。

定義注解和處理邏輯

首先是定義一個(gè)注解,方便后續(xù)對(duì)不同接口使用不同的限制頻率。

/**  
 * 接口訪問(wèn)頻率注解,默認(rèn)一分鐘只能訪問(wèn)5次  
 */  
@Documented  
@Target(ElementType.METHOD)  
@Retention(RetentionPolicy.RUNTIME)  
public @interface RequestLimit {  
  
    // 限制時(shí)間 單位:秒(默認(rèn)值:一分鐘)  
    long period() default 60;  
  
    // 允許請(qǐng)求的次數(shù)(默認(rèn)值:5次)  
    long count() default 5;  
  
}

在實(shí)現(xiàn)邏輯這塊,我們定義一個(gè)切面函數(shù),攔截用戶(hù)的request,具體實(shí)現(xiàn)流程和上面介紹的限流流程一致,主要涉及到redis zset的操作。


@Aspect
@Component
@Log4j2
public class RequestLimitAspect {

    @Autowired
    RedisTemplate redisTemplate;

    // 切點(diǎn)
    @Pointcut("@annotation(requestLimit)")
    public void controllerAspect(RequestLimit requestLimit) {}

    @Around("controllerAspect(requestLimit)")
    public Object doAround(ProceedingJoinPoint joinPoint, RequestLimit requestLimit) throws Throwable {
        // get parameter from annotation
        long period = requestLimit.period();
        long limitCount = requestLimit.count();

        // request info
        String ip = RequestUtil.getClientIpAddress();
        String uri = RequestUtil.getRequestUri();
        String key = "req_limit_".concat(uri).concat(ip);

        ZSetOperations zSetOperations = redisTemplate.opsForZSet();

        // add current timestamp
        long currentMs = System.currentTimeMillis();
        zSetOperations.add(key, currentMs, currentMs);

        // set the expiration time for the code user
        redisTemplate.expire(key, period, TimeUnit.SECONDS);

        // remove the value that out of current window
        zSetOperations.removeRangeByScore(key, 0, currentMs - period * 1000);

        // check all available count
        Long count = zSetOperations.zCard(key);

        if (count > limitCount) {
            log.error("接口攔截:{} 請(qǐng)求超過(guò)限制頻率【{}次/{}s】,IP為{}", uri, limitCount, period, ip);
            throw new AuroraRuntimeException(ResponseCode.TOO_FREQUENT_VISIT);
        }

        // execute the user request
        return  joinPoint.proceed();
    }

}

使用注解進(jìn)行限流控制

這里我定義了一個(gè)接口類(lèi)來(lái)做測(cè)試,使用上面的annotation來(lái)完成限流,每分鐘允許用戶(hù)訪問(wèn)3次。

@Log4j2  
@RestController  
@RequestMapping("/user")  
public class UserController {    

    @GetMapping("/test")  
    @RequestLimit(count = 3)  
    public GenericResponse<String> testRequestLimit() {  
        log.info("current time: " + new Date());  
        return new GenericResponse<>(ResponseCode.SUCCESS);  
    }  
  
}

我接著在不同機(jī)器上,訪問(wèn)該接口,可以看到不同機(jī)器的限流是隔離的,并且每臺(tái)機(jī)器在周期之內(nèi)只能訪問(wèn)三次,超過(guò)后,需要等待一定時(shí)間才能繼續(xù)訪問(wèn),達(dá)到了我們預(yù)期的效果。

2023-05-21 11:23:15.733  INFO 99636 --- [nio-8080-exec-1] c.v.c.a.api.controller.UserController    : current time: Sun May 21 11:23:15 CST 2023
2023-05-21 11:23:21.848  INFO 99636 --- [nio-8080-exec-3] c.v.c.a.api.controller.UserController    : current time: Sun May 21 11:23:21 CST 2023
2023-05-21 11:23:23.044  INFO 99636 --- [nio-8080-exec-4] c.v.c.a.api.controller.UserController    : current time: Sun May 21 11:23:23 CST 2023
2023-05-21 11:23:25.920 ERROR 99636 --- [nio-8080-exec-5] c.v.c.a.annotation.RequestLimitAspect    : 接口攔截:/user/test 請(qǐng)求超過(guò)限制頻率【3次/60s】,IP為0:0:0:0:0:0:0:1
2023-05-21 11:23:28.761 ERROR 99636 --- [nio-8080-exec-6] c.v.c.a.annotation.RequestLimitAspect    : 接口攔截:/user/test 請(qǐng)求超過(guò)限制頻率【3次/60s】,IP為0:0:0:0:0:0:0:1
2023-05-21 11:24:12.207  INFO 99636 --- [io-8080-exec-10] c.v.c.a.api.controller.UserController    : current time: Sun May 21 11:24:12 CST 2023
2023-05-21 11:24:19.100  INFO 99636 --- [nio-8080-exec-2] c.v.c.a.api.controller.UserController    : current time: Sun May 21 11:24:19 CST 2023
2023-05-21 11:24:20.117  INFO 99636 --- [nio-8080-exec-1] c.v.c.a.api.controller.UserController    : current time: Sun May 21 11:24:20 CST 2023
2023-05-21 11:24:21.146 ERROR 99636 --- [nio-8080-exec-3] c.v.c.a.annotation.RequestLimitAspect    : 接口攔截:/user/test 請(qǐng)求超過(guò)限制頻率【3次/60s】,IP為192.168.31.114
2023-05-21 11:24:26.779 ERROR 99636 --- [nio-8080-exec-4] c.v.c.a.annotation.RequestLimitAspect    : 接口攔截:/user/test 請(qǐng)求超過(guò)限制頻率【3次/60s】,IP為192.168.31.114
2023-05-21 11:24:29.344 ERROR 99636 --- [nio-8080-exec-5] c.v.c.a.annotation.RequestLimitAspect    : 接口攔截:/user/test 請(qǐng)求超過(guò)限制頻率【3次/60s】,IP為192.168.31.114

歡迎關(guān)注公眾號(hào)【碼老思】,只講最通俗易懂的原創(chuàng)技術(shù)干貨。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-453458.html

到了這里,關(guān)于SpringBoot限制接口訪問(wèn)頻率 - 這些錯(cuò)誤千萬(wàn)不能犯的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶(hù)投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Nginx 限流模塊:限制高并發(fā)和IP訪問(wèn)頻率

    Nginx 限流模塊:限制高并發(fā)和IP訪問(wèn)頻率

    Nginx 是我們常用的負(fù)載均衡和反向代理服務(wù)器,并發(fā)性能非常優(yōu)秀。 但是在并發(fā)量極大的情況下,必要限流措施還是需要的,Nginx 的有對(duì)應(yīng)的模塊插件可通過(guò)簡(jiǎn)單配置來(lái)完成這個(gè)功能。 1、添加 limit_conn_zone 這個(gè)變量只能在http使用。

    2023年04月25日
    瀏覽(27)
  • 使用Redis控制表單重復(fù)提交控制接口訪問(wèn)頻率

    防重提交有很多方案,從前端的按鈕置灰,到后端synchronize鎖、Lock鎖、借助Redis語(yǔ)法實(shí)現(xiàn)簡(jiǎn)單鎖、Redis+Lua分布式鎖、Redisson分布式鎖,再到DB的悲觀鎖、樂(lè)觀鎖、借助表唯一索引等等都可以實(shí)現(xiàn)防重提交,以保證數(shù)據(jù)的安全性。 這篇文章我們介紹其中一種方案– 借助Redis語(yǔ)法實(shí)

    2024年02月09日
    瀏覽(19)
  • 限制API接口訪問(wèn)速率

    免責(zé)聲明:本人無(wú)意侵權(quán),奈何找不到原文作者,也找不到網(wǎng)址,于是自己記錄一下,如果有侵權(quán)之嫌,請(qǐng)聯(lián)系我刪除文章

    2024年01月19日
    瀏覽(14)
  • SpringBoot實(shí)現(xiàn)圖形驗(yàn)證碼功能+訪問(wèn)頻率設(shè)置+緩存

    SpringBoot實(shí)現(xiàn)圖形驗(yàn)證碼功能+訪問(wèn)頻率設(shè)置+緩存

    目錄 1、springboot實(shí)現(xiàn)圖形驗(yàn)證碼生成 1.1、導(dǎo)入Maven依賴(lài) 1.2、寫(xiě)一個(gè)生成圖片的工具類(lèi) 1.3、編寫(xiě)接口生成驗(yàn)證碼并存入Redis 2、實(shí)現(xiàn)圖形驗(yàn)證碼判斷是否正確 2.1、編寫(xiě)驗(yàn)證圖形驗(yàn)證碼接口 2.2、前端代碼 2.3、請(qǐng)求發(fā)送 3、實(shí)現(xiàn)訪問(wèn)頻率限制 3.1、創(chuàng)建自定義注解 3.2、創(chuàng)建自定義

    2024年04月17日
    瀏覽(26)
  • 反爬蟲(chóng)策略:使用FastAPI限制接口訪問(wèn)速率

    反爬蟲(chóng)策略:使用FastAPI限制接口訪問(wèn)速率

    目錄 引言 一、網(wǎng)絡(luò)爬蟲(chóng)的威脅 二、FastAPI 簡(jiǎn)介 三、反爬蟲(chóng)策略 四、具體實(shí)現(xiàn) 五、其他反爬蟲(chóng)策略 六、總結(jié) 在當(dāng)今的數(shù)字時(shí)代,數(shù)據(jù)已經(jīng)成為了一種寶貴的資源。無(wú)論是商業(yè)決策、科學(xué)研究還是日常生活,我們都需要從大量的數(shù)據(jù)中獲取有價(jià)值的信息。為了獲取這些數(shù)據(jù),

    2024年01月21日
    瀏覽(18)
  • Java接口訪問(wèn)限制次數(shù)(使用IP作為唯一標(biāo)識(shí))

    1、獲取用戶(hù)IP工具類(lèi) 2、接口限制注解(切面) (1)controller層 (2)注解類(lèi) (3)切面方法

    2024年02月13日
    瀏覽(18)
  • 基于Springboot 限制IP訪問(wèn)指定的網(wǎng)址

    添加一個(gè)簡(jiǎn)單的白名單,然后只有白名單上面的 IP 才能訪問(wèn)網(wǎng)站,否則不能訪問(wèn) 這是只是一個(gè)很簡(jiǎn)單的實(shí)現(xiàn)方法 首先:在 application.yml 配置 IP 白名單 想要引用到配置文件里面的 String 數(shù)組,如果使用普通的 @Value 是不行的,必須使用其他方法 步驟一:創(chuàng)建一個(gè)實(shí)體類(lèi) 這樣配

    2024年01月18日
    瀏覽(45)
  • Springboot接口添加IP白名單限制

    Springboot接口添加IP白名單限制

    實(shí)現(xiàn)流程: 自定義攔截器——注入攔截器——獲取請(qǐng)求IP——對(duì)比IP是否一致——請(qǐng)求返回 文章背景: 接口添加IP白名單限制,只有規(guī)定的IP可以訪問(wèn)項(xiàng)目。 實(shí)現(xiàn)思路: 添加攔截器,攔截項(xiàng)目所有的請(qǐng)求,獲取請(qǐng)求的網(wǎng)絡(luò)IP,查詢(xún)IP是否在白名單之中,白名單設(shè)置在數(shù)據(jù)庫(kù)中

    2024年02月04日
    瀏覽(21)
  • Taurus .Net Core 微服務(wù)開(kāi)源框架:Admin 插件【4-8】 - 配置管理-Mvc【Plugin-Limit 接口訪問(wèn)限制、IP限制、Ack限制】

    Taurus .Net Core 微服務(wù)開(kāi)源框架:Admin 插件【4-8】 - 配置管理-Mvc【Plugin-Limit 接口訪問(wèn)限制、IP限制、Ack限制】

    繼上篇:Taurus .Net Core 微服務(wù)開(kāi)源框架:Admin 插件【4-7】 - 配置管理-Mvc【Plugin-Metric 接口調(diào)用次數(shù)統(tǒng)計(jì)】 本篇繼續(xù)介紹下一個(gè)內(nèi)容: 配置界面如下: 限制目前提供以下三個(gè)類(lèi)別的限制: 對(duì)三種類(lèi)別限制都有效。 對(duì)三種類(lèi)別限制都有效。 對(duì)三種類(lèi)別限制都有效。 對(duì)三種類(lèi)別

    2024年02月04日
    瀏覽(23)
  • SpringBoot——SpringBoot訪問(wèn)外部接口

    在SpringBoot接口開(kāi)發(fā)中,存在著本模塊的代碼需要訪問(wèn)外面模塊接口或外部url鏈接的需求, 比如調(diào)用外部的地圖API或者天氣API。那么有哪些方式可以調(diào)用外部接口呢?本博文將介紹SpringBoot常見(jiàn)的訪問(wèn)外部接口方式。幫助大家更好的使用SpringBoot訪問(wèn)外部接口。 調(diào)用其它模塊的

    2024年02月07日
    瀏覽(19)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包