分布式接口冪等性、分布式限流總結(jié)整理

一、接口冪等性

接口冪等性就是用戶(hù)對(duì)于同一操作發(fā)起的一次請(qǐng)求或者多次請(qǐng)求的結(jié)果是一致的,不會(huì)因?yàn)槎啻吸c(diǎn)擊而產(chǎn)生了副作用。舉個(gè)最簡(jiǎn)單的例子,那就是支付,用戶(hù)購(gòu)買(mǎi)商品后支付,支付扣款成功,但是返回結(jié)果的時(shí)候網(wǎng)絡(luò)異常,此時(shí)錢(qián)已經(jīng)扣了,用戶(hù)再次點(diǎn)擊按鈕,此時(shí)會(huì)進(jìn)行第二次扣款,返回結(jié)果成功,用戶(hù)查詢(xún)余額返發(fā)現(xiàn)多扣錢(qián)了,流水記錄也變成了兩條,這就沒(méi)有保證接口的冪等性。
??冪等性的核心思想:通過(guò)唯一的業(yè)務(wù)單號(hào)保障冪等性,非并發(fā)的情況下,查詢(xún)業(yè)務(wù)單號(hào)有沒(méi)有操作過(guò),沒(méi)有則執(zhí)行操作,并發(fā)情況下,這個(gè)操作過(guò)程需要加鎖。

1、Update操作的冪等性

1)根據(jù)唯一業(yè)務(wù)號(hào)去更新數(shù)據(jù)

通過(guò)版本號(hào)的方式,來(lái)控制update的操作的冪等性,用戶(hù)查詢(xún)出要修改的數(shù)據(jù),系統(tǒng)將數(shù)據(jù)返回給頁(yè)面,將數(shù)據(jù)版本號(hào)放入隱藏域,用戶(hù)修改數(shù)據(jù),點(diǎn)擊提交,將版本號(hào)一同提交給后臺(tái),后臺(tái)使用版本號(hào)作為更新條件

update set version = version +1 ,xxx=${xxx} where id =xxx and version = ${version};

2、使用Token機(jī)制,保證update、insert操作的冪等性

1)沒(méi)有唯一業(yè)務(wù)號(hào)的update與insert操作

進(jìn)入到注冊(cè)頁(yè)時(shí),后臺(tái)統(tǒng)一生成Token, 返回前臺(tái)隱藏域中,用戶(hù)在頁(yè)面點(diǎn)擊提交時(shí),將Token一同傳入后臺(tái),使用Token獲取分布式鎖,完成Insert操作,執(zhí)行成功后,不釋放鎖,等待過(guò)期自動(dòng)釋放。

二、分布式限流

1、分布式限流的幾種維度

  • 時(shí)間 限流基于某段時(shí)間范圍或者某個(gè)時(shí)間點(diǎn),也就是我們常說(shuō)的“時(shí)間窗口”,比如對(duì)每分鐘、每秒鐘的時(shí)間窗口做限定
  • 資源 基于可用資源的限制,比如設(shè)定最大訪(fǎng)問(wèn)次數(shù),或最高可用連接數(shù)
    上面兩個(gè)維度結(jié)合起來(lái)看,限流就是在某個(gè)時(shí)間窗口對(duì)資源訪(fǎng)問(wèn)做限制,比如設(shè)定每秒最多100個(gè)訪(fǎng)問(wèn)請(qǐng)求。但在真正的場(chǎng)景里,我們不止設(shè)置一種限流規(guī)則,而是會(huì)設(shè)置多個(gè)限流規(guī)則共同作用,主要的幾種限流規(guī)則如下:

1)QPS和連接數(shù)控制

針對(duì)上圖中的連接數(shù)和QPS(query per second)限流來(lái)說(shuō),我們可以設(shè)定IP維度的限流,也可以設(shè)置基于單個(gè)服務(wù)器的限流。在真實(shí)環(huán)境中通常會(huì)設(shè)置多個(gè)維度的限流規(guī)則,比如設(shè)定同一個(gè)IP每秒訪(fǎng)問(wèn)頻率小于10,連接數(shù)小于5,再設(shè)定每臺(tái)機(jī)器QPS最高1000,連接數(shù)最大保持200。更進(jìn)一步,我們可以把某個(gè)服務(wù)器組或整個(gè)機(jī)房的服務(wù)器當(dāng)做一個(gè)整體,設(shè)置更high-level的限流規(guī)則,這些所有限流規(guī)則都會(huì)共同作用于流量控制。

2)傳輸速率

對(duì)于“傳輸速率”大家都不會(huì)陌生,比如資源的下載速度。有的網(wǎng)站在這方面的限流邏輯做的更細(xì)致,比如普通注冊(cè)用戶(hù)下載速度為100k/s,購(gòu)買(mǎi)會(huì)員后是10M/s,這背后就是基于用戶(hù)組或者用戶(hù)標(biāo)簽的限流邏輯。

3)黑白名單

黑白名單是各個(gè)大型企業(yè)應(yīng)用里很常見(jiàn)的限流和放行手段,而且黑白名單往往是動(dòng)態(tài)變化的。舉個(gè)例子,如果某個(gè)IP在一段時(shí)間的訪(fǎng)問(wèn)次數(shù)過(guò)于頻繁,被系統(tǒng)識(shí)別為機(jī)器人用戶(hù)或流量攻擊,那么這個(gè)IP就會(huì)被加入到黑名單,從而限制其對(duì)系統(tǒng)資源的訪(fǎng)問(wèn),這就是我們俗稱(chēng)的“封IP”。
??我們平時(shí)見(jiàn)到的爬蟲(chóng)程序,比如說(shuō)爬知乎上的美女圖片,或者爬券商系統(tǒng)的股票分時(shí)信息,這類(lèi)爬蟲(chóng)程序都必須實(shí)現(xiàn)更換IP的功能,以防被加入黑名單。有時(shí)我們還會(huì)發(fā)現(xiàn)公司的網(wǎng)絡(luò)無(wú)法訪(fǎng)問(wèn)12306這類(lèi)大型公共網(wǎng)站,這也是因?yàn)槟承┕镜某鼍W(wǎng)IP是同一個(gè)地址,因此在訪(fǎng)問(wèn)量過(guò)高的情況下,這個(gè)IP地址就被對(duì)方系統(tǒng)識(shí)別,進(jìn)而被添加到了黑名單。使用家庭寬帶的同學(xué)們應(yīng)該知道,大部分網(wǎng)絡(luò)運(yùn)營(yíng)商都會(huì)將用戶(hù)分配到不同出網(wǎng)IP段,或者時(shí)不時(shí)動(dòng)態(tài)更換用戶(hù)的IP地址。
??白名單就更好理解了,相當(dāng)于御賜金牌在身,可以自由穿梭在各種限流規(guī)則里,暢行無(wú)阻。比如某些電商公司會(huì)將超大賣(mài)家的賬號(hào)加入白名單,因?yàn)檫@類(lèi)賣(mài)家往往有自己的一套運(yùn)維系統(tǒng),需要對(duì)接公司的IT系統(tǒng)做大量的商品發(fā)布、補(bǔ)貨等等操作。

4)分布式環(huán)境

所謂的分布式限流,其實(shí)道理很簡(jiǎn)單,一句話(huà)就可以解釋清楚。分布式區(qū)別于單機(jī)限流的場(chǎng)景,它把整個(gè)分布式環(huán)境中所有服務(wù)器當(dāng)做一個(gè)整體來(lái)考量。比如說(shuō)針對(duì)IP的限流,我們限制了1個(gè)IP每秒最多10個(gè)訪(fǎng)問(wèn),不管來(lái)自這個(gè)IP的請(qǐng)求落在了哪臺(tái)機(jī)器上,只要是訪(fǎng)問(wèn)了集群中的服務(wù)節(jié)點(diǎn),那么都會(huì)受到限流規(guī)則的制約。
??從上面的例子不難看出,我們必須將限流信息保存在一個(gè)“中心化”的組件上,這樣它就可以獲取到集群中所有機(jī)器的訪(fǎng)問(wèn)狀態(tài),目前有兩個(gè)比較主流的限流方案:

  • 網(wǎng)關(guān)層限流
    ??將限流規(guī)則應(yīng)用在所有流量的入口處
  • 中間件限流
    ??將限流信息存儲(chǔ)在分布式環(huán)境中某個(gè)中間件里(比如Redis緩存),每個(gè)組件都可以從這里獲取到當(dāng)前時(shí)刻的流量統(tǒng)計(jì),從而決定是拒絕服務(wù)還是放行流量

2、限流方案常用算法講解

1)令牌桶算法

Token Bucket令牌桶算法是目前應(yīng)用最為廣泛的限流算法,顧名思義,它有以下兩個(gè)關(guān)鍵角色:

  • 令牌 獲取到令牌的Request才會(huì)被處理,其他Requests要么排隊(duì)要么被直接丟棄
  • 桶 用來(lái)裝令牌的地方,所有Request都從這個(gè)桶里面獲取令牌

令牌生成

這個(gè)流程涉及到令牌生成器和令牌桶,前面我們提到過(guò)令牌桶是一個(gè)裝令牌的地方,既然是個(gè)桶那么必然有一個(gè)容量,也就是說(shuō)令牌桶所能容納的令牌數(shù)量是一個(gè)固定的數(shù)值。
??對(duì)于令牌生成器來(lái)說(shuō),它會(huì)根據(jù)一個(gè)預(yù)定的速率向桶中添加令牌,比如我們可以配置讓它以每秒100個(gè)請(qǐng)求的速率發(fā)放令牌,或者每分鐘50個(gè)。注意這里的發(fā)放速度是勻速,也就是說(shuō)這50個(gè)令牌并非是在每個(gè)時(shí)間窗口剛開(kāi)始的時(shí)候一次性發(fā)放,而是會(huì)在這個(gè)時(shí)間窗口內(nèi)勻速發(fā)放。
??在令牌發(fā)放器就是一個(gè)水龍頭,假如在下面接水的桶子滿(mǎn)了,那么自然這個(gè)水(令牌)就流到了外面。在令牌發(fā)放過(guò)程中也一樣,令牌桶的容量是有限的,如果當(dāng)前已經(jīng)放滿(mǎn)了額定容量的令牌,那么新來(lái)的令牌就會(huì)被丟棄掉。

令牌獲取

每個(gè)訪(fǎng)問(wèn)請(qǐng)求到來(lái)后,必須獲取到一個(gè)令牌才能執(zhí)行后面的邏輯。假如令牌的數(shù)量少,而訪(fǎng)問(wèn)請(qǐng)求較多的情況下,一部分請(qǐng)求自然無(wú)法獲取到令牌,那么這個(gè)時(shí)候我們可以設(shè)置一個(gè)“緩沖隊(duì)列”來(lái)暫存這些多余的令牌。
??緩沖隊(duì)列其實(shí)是一個(gè)可選的選項(xiàng),并不是所有應(yīng)用了令牌桶算法的程序都會(huì)實(shí)現(xiàn)隊(duì)列。當(dāng)有緩存隊(duì)列存在的情況下,那些暫時(shí)沒(méi)有獲取到令牌的請(qǐng)求將被放到這個(gè)隊(duì)列中排隊(duì),直到新的令牌產(chǎn)生后,再?gòu)年?duì)列頭部拿出一個(gè)請(qǐng)求來(lái)匹配令牌。
??當(dāng)隊(duì)列已滿(mǎn)的情況下,這部分訪(fǎng)問(wèn)請(qǐng)求將被丟棄。在實(shí)際應(yīng)用中我們還可以給這個(gè)隊(duì)列加一系列的特效,比如設(shè)置隊(duì)列中請(qǐng)求的存活時(shí)間,或者將隊(duì)列改造為PriorityQueue,根據(jù)某種優(yōu)先級(jí)排序,而不是先進(jìn)先出。算法是死的,人是活的,先進(jìn)的生產(chǎn)力來(lái)自于不斷的創(chuàng)造,在技術(shù)領(lǐng)域尤其如此。

2)漏桶算法

Leaky Bucket

漏桶算法的前半段和令牌桶類(lèi)似,但是操作的對(duì)象不同,令牌桶是將令牌放入桶里,而漏桶是將訪(fǎng)問(wèn)請(qǐng)求的數(shù)據(jù)包放到桶里。同樣的是,如果桶滿(mǎn)了,那么后面新來(lái)的數(shù)據(jù)包將被丟棄。
??漏桶算法的后半程是有鮮明特色的,它永遠(yuǎn)只會(huì)以一個(gè)恒定的速率將數(shù)據(jù)包從桶內(nèi)流出。打個(gè)比方,如果我設(shè)置了漏桶可以存放100個(gè)數(shù)據(jù)包,然后流出速度是1s一個(gè),那么不管數(shù)據(jù)包以什么速率流入桶里,也不管桶里有多少數(shù)據(jù)包,漏桶能保證這些數(shù)據(jù)包永遠(yuǎn)以1s一個(gè)的恒定速度被處理。

漏桶 vs 令牌桶的區(qū)別

根據(jù)它們各自的特點(diǎn)不難看出來(lái),這兩種算法都有一個(gè)“恒定”的速率和“不定”的速率。令牌桶是以恒定速率創(chuàng)建令牌,但是訪(fǎng)問(wèn)請(qǐng)求獲取令牌的速率“不定”,反正有多少令牌發(fā)多少,令牌沒(méi)了就干等。而漏桶是以“恒定”的速率處理請(qǐng)求,但是這些請(qǐng)求流入桶的速率是“不定”的。
??從這兩個(gè)特點(diǎn)來(lái)說(shuō),漏桶的天然特性決定了它不會(huì)發(fā)生突發(fā)流量,就算每秒1000個(gè)請(qǐng)求到來(lái),那么它對(duì)后臺(tái)服務(wù)輸出的訪(fǎng)問(wèn)速率永遠(yuǎn)恒定。而令牌桶則不同,其特性可以“預(yù)存”一定量的令牌,因此在應(yīng)對(duì)突發(fā)流量的時(shí)候可以在短時(shí)間消耗所有令牌,其突發(fā)流量處理效率會(huì)比漏桶高,但是導(dǎo)向后臺(tái)系統(tǒng)的壓力也會(huì)相應(yīng)增多。

3、分布式限流的主流方案

這里主要講nginx和lua的限流,gateway和hystrix放在后面springcloud中講

1)Guava RateLimiter客戶(hù)端限流

引入maven

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>

2.編寫(xiě)Controller

@RestController
@Slf4j
public class Controller{
    //每秒鐘可以創(chuàng)建兩個(gè)令牌
    RateLimiter limiter = RateLimiter.create(2.0);

    //非阻塞限流
    @GetMapping("/tryAcquire")
    public String tryAcquire(Integer count){
        //count 每次消耗的令牌
        if(limiter.tryAcquire(count)){
            log.info("成功,允許通過(guò),速率為{}",limiter.getRate());
            return "success";
        }else{
            log.info("錯(cuò)誤,不允許通過(guò),速率為{}",limiter.getRate());
            return "fail";
        }
    }

    //限定時(shí)間的非阻塞限流
    @GetMapping("/tryAcquireWithTimeout")
    public String tryAcquireWithTimeout(Integer count, Integer timeout){
        //count 每次消耗的令牌  timeout 超時(shí)等待的時(shí)間
        if(limiter.tryAcquire(count,timeout,TimeUnit.SECONDS)){
            log.info("成功,允許通過(guò),速率為{}",limiter.getRate());
            return "success";
        }else{
            log.info("錯(cuò)誤,不允許通過(guò),速率為{}",limiter.getRate());
            return "fail";
        }
    }

    //同步阻塞限流
    @GetMapping("/acquire")
    public String acquire(Integer count){
        limiter.acquire(count);
        log.info("成功,允許通過(guò),速率為{}",limiter.getRate());
        return "success";
    }
}

2)基于Nginx的限流

1.IP限流
編寫(xiě)Controller


@RestController
@Slf4j
public class Controller{
    //nginx測(cè)試使用
    @GetMapping("/nginx")
    public String nginx(){
        log.info("Nginx success");
    }
}

2、修改host文件,添加一個(gè)網(wǎng)址域名

127.0.0.1   www.test.com

3.修改nginx,將步驟2中的域名,添加到路由規(guī)則當(dāng)中

打開(kāi)nginx的配置文件

vim /usr/local/nginx/conf/nginx.conf

添加一個(gè)服務(wù)


#根據(jù)IP地址限制速度
#1)$binary_remote_addr   binary_目的是縮寫(xiě)內(nèi)存占用,remote_addr表示通過(guò)IP地址來(lái)限流
#2)zone=iplimit:20m   iplimit是一塊內(nèi)存區(qū)域(記錄訪(fǎng)問(wèn)頻率信息),20m是指這塊內(nèi)存區(qū)域的大小
#3)rate=1r/s  每秒放行1個(gè)請(qǐng)求
limit_req_zone $binary_remote_addr zone=iplimit:20m rate=1r/s;


server{
    server_name www.test.com;
    location /access-limit/ {
        proxy_pass http://127.0.0.1:8080/;

        #基于ip地址的限制
        #1)zone=iplimit 引用limit_rep_zone中的zone變量
        #2)burst=2  設(shè)置一個(gè)大小為2的緩沖區(qū)域,當(dāng)大量請(qǐng)求到來(lái),請(qǐng)求數(shù)量超過(guò)限流頻率時(shí),將其放入緩沖區(qū)域
        #3)nodelay   緩沖區(qū)滿(mǎn)了以后,直接返回503異常
        limit_req zone=iplimit burst=2 nodelay;
    }
}

4.訪(fǎng)問(wèn)地址,測(cè)試是否限流

www.test.com/access-limit/nginx

2.多維度限流

1.修改nginx配置


#根據(jù)IP地址限制速度
limit_req_zone $binary_remote_addr zone=iplimit:20m rate=10r/s;
#根據(jù)服務(wù)器級(jí)別做限流
limit_req_zone $server_name zone=serverlimit:10m rate=1r/s;
#根據(jù)ip地址的鏈接數(shù)量做限流
limit_conn_zone $binary_remote_addr zone=perip:20m;
#根據(jù)服務(wù)器的連接數(shù)做限流
limit_conn_zone $server_name zone=perserver:20m;

server{
    server_name www.test.com;
    location /access-limit/ {
        proxy_pass http://127.0.0.1:8080/;

        #基于ip地址的限制
        limit_req zone=iplimit burst=2 nodelay;
        #基于服務(wù)器級(jí)別做限流
        limit_req zone=serverlimit burst=2 nodelay;
        #基于ip地址的鏈接數(shù)量做限流  最多保持100個(gè)鏈接
        limit_conn zone=perip 100;
        #基于服務(wù)器的連接數(shù)做限流 最多保持100個(gè)鏈接
        limit_conn zone=perserver 1;
        #配置request的異常返回504(默認(rèn)為503)
        limit_req_status 504;
        limit_conn_status 504;
    }

     location /download/ {
        #前100m不限制速度
        limit_rate_affer 100m;
        #限制速度為256k
        limit_rate 256k;
     }
}

3)基于Redis+Lua的分布式限流

1.Lua腳本
??Lua是一個(gè)很小巧精致的語(yǔ)言,它的誕生(1993年)甚至比JDK 1.0還要早。Lua是由標(biāo)準(zhǔn)的C語(yǔ)言編寫(xiě)的,它的源碼部分不過(guò)2萬(wàn)多行C代碼,甚至一個(gè)完整的Lua解釋器也就200k的大小。
??Lua往大了說(shuō)是一個(gè)新的編程語(yǔ)言,往小了說(shuō)就是一個(gè)腳本語(yǔ)言。對(duì)于有編程經(jīng)驗(yàn)的同學(xué),拿到一個(gè)Lua腳本大體上就能把業(yè)務(wù)邏輯猜的八九不離十了。
Redis內(nèi)置了Lua解釋器,執(zhí)行過(guò)程保證原子性

2.Lua安裝
安裝Lua:

  1. 參考http://www.lua.org/ftp/教程,下載5.3.5_1版本,本地安裝如果你使用的是Mac,那建議用brew工具直接執(zhí)行brew install lua就可以順利安裝,有關(guān)brew工具的安裝可以參考https://brew.sh/網(wǎng)站,建議翻墻否則會(huì)很慢。
    使用brew安裝后的目錄在/usr/local/Cellar/lua/5.3.5_1
  2. 安裝IDEA插件,在IDEA->Preferences面板,Plugins,里面Browse repositories,在里面搜索lua,然后就選擇同名插件lua。安裝好后重啟IDEA
  3. 配置Lua SDK的位置:IDEA->File->Project Structure,選擇添加Lua,路徑指向Lua SDK的bin文件
  4. 都配置好之后,在項(xiàng)目中右鍵創(chuàng)建Module,左側(cè)欄選擇lua,點(diǎn)下一步,選擇lua的sdk,下一步,輸入lua項(xiàng)目名,完成

3.編寫(xiě)hello lua

print 'Hello Lua'

4.編寫(xiě)模擬限流

-- 模擬限流



-- 用作限流的key
local key = 'my key'


-- 限流的最大閾值
local limit = 2


-- 當(dāng)前限流大小
local currentLimit = 2


-- 是否超過(guò)限流標(biāo)準(zhǔn)
if currentLimit + 1 > limit then
    print 'reject'
    return false
else
    print 'accept'
    return true
end

5.限流組件封裝

1.添加maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>

2.添加Spring配置

不是重要內(nèi)容就隨便寫(xiě)點(diǎn),主要就是把reids配置一下

server.port=8080
spring.redis.database=0
spring.redis.host=localhost
spring.redis.port=6376

3.編寫(xiě)限流腳本

lua腳本放在resource目錄下就可以了

-- 獲取方法簽名特征
local methodKey = KEYS[1]
redis.log(redis.LOG_DEBUG,'key is',methodKey)


-- 調(diào)用腳本傳入的限流大小
local limit = tonumber(ARGV[1])


-- 獲取當(dāng)前流量大小
local count = tonumber(redis.call('get',methodKey) or "0")


--是否超出限流值
if count + 1 >limit then
    -- 拒絕訪(fǎng)問(wèn)
    return false
else
    -- 沒(méi)有超過(guò)閾值
    -- 設(shè)置當(dāng)前訪(fǎng)問(wèn)數(shù)量+1
    redis.call('INCRBY',methodKey,1)
    -- 設(shè)置過(guò)期時(shí)間
    redis.call('EXPIRE',methodKey,1)
    -- 放行
    return true
end

4.使用spring-data-redis組件集成Lua和Redis

創(chuàng)建限流類(lèi)

@Service
@Slf4j
public class AccessLimiter{
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisScript<Boolean> rateLimitLua;


    public void limitAccess(String key,Integer limit){
        boolean acquired = stringRedisTemplate.execute(
            rateLimitLua,//lua腳本的真身
            Lists.newArrayList(key),//lua腳本中的key列表
            limit.toString()//lua腳本的value列表
        );


        if(!acquired){
            log.error("Your access is blocked,key={}",key);
            throw new RuntimeException("Your access is blocked");
        }
    }
}

創(chuàng)建配置類(lèi)


@Configuration
public class RedisConfiguration{
    public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory factory){
        return new StringRedisTemplate(factory);
    }

    public DefaultRedisScript loadRedisScript(){
        DefaultRedisScript redisScript = new DefaultRedisScript();
        redisScript.setLocation(new ClassPathResource("rateLimiter.lua"));
        redisScript.setResultType(java.lang.Boolean.class);
        return redisScript;
    }
}

5.在Controller中添加測(cè)試方法驗(yàn)證限流效果

@RestController
@Slf4j
public class Controller{
    @Autowired
    private AccessLimiter accessLimiter;

    @GetMapping("test")
    public String test(){
        accessLimiter.limitAccess("ratelimiter-test",1);
        return "success";
    }
} 

6.編寫(xiě)限流注解
1.新增注解


@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AccessLimiterAop{
    int limit();

    String methodKey() default "";
}

2.新增切面

@Slf4j
@Aspect
@Component
public class AccessLimiterAspect{
    @Autowired
    private AccessLimiter  accessLimiter;


    //根據(jù)注解的位置,自己修改
    @Pointcut("@annotation(com.gyx.demo.annotation.AccessLimiter)")
    public void cut(){
        log.info("cut");
    }

    @Before("cut()")
    public void before(JoinPoint joinPoint){
        //獲取方法簽名,作為methodkey
        MethodSignature signature =(MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        AccessLimiterAop annotation = method.getAnnotation(AccessLimiterAop.class);

        if(annotation == null){
            return;
        }
        String key = annotation.methodKey();
        Integer limit = annotation.limit();
        //如果沒(méi)有設(shè)置methodKey,就自動(dòng)添加一個(gè)
        if(StringUtils.isEmpty(key)){
            Class[] type = method.getParameterType();
            key = method.getName();
            if (type != null){
                String paramTypes=Arrays.stream(type)
                    .map(Class::getName)
                    .collect(Collectors.joining(","));
                    key += "#"+paramTypes;
            }
        }

        //調(diào)用redis
        return accessLimiter.limitAccess(key,limit);
    }
}

3.在Controller中添加測(cè)試方法驗(yàn)證限流效果


@RestController
@Slf4j
public class Controller{
    @Autowired
    private AccessLimiter accessLimiter;

    @GetMapping("test")
    @AccessLImiterAop(limit =1)
    public String test(){
        return "success";
    }
} 
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容