第一種:基于Redis的setnx的操作
我們在使用Redis的分布式鎖的時(shí)候剔交,大家都知道是依靠了setnx的指令肆饶,在CAS(Compare and swap)的操作的時(shí)候,同時(shí)給指定的key設(shè)置了過期實(shí)踐(expire)岖常,我們在限流的主要目的就是為了在單位時(shí)間內(nèi)驯镊,有且僅有N數(shù)量的請求能夠訪問我的代碼程序。所以依靠setnx可以很輕松的做到這方面的功能腥椒。
比如我們需要在10秒內(nèi)限定20個(gè)請求阿宅,那么我們在setnx的時(shí)候可以設(shè)置過期時(shí)間10,當(dāng)請求的setnx數(shù)量達(dá)到20時(shí)候即達(dá)到了限流效果笼蛛。代碼比較簡單就不做展示了洒放。
當(dāng)然這種做法的弊端是很多的,比如當(dāng)統(tǒng)計(jì)1-10秒的時(shí)候滨砍,無法統(tǒng)計(jì)2-11秒之內(nèi)往湿,如果需要統(tǒng)計(jì)N秒內(nèi)的M個(gè)請求妖异,那么我們的Redis中需要保持N個(gè)key等等問題
第二種:基于Redis的數(shù)據(jù)結(jié)構(gòu)zset
其實(shí)限流涉及的最主要的就是滑動窗口,上面也提到1-10怎么變成2-11领追。其實(shí)也就是起始值和末端值都各+1即可他膳。
而我們?nèi)绻肦edis的list數(shù)據(jù)結(jié)構(gòu)可以輕而易舉的實(shí)現(xiàn)該功能
我們可以將請求打造成一個(gè)zset數(shù)組,當(dāng)每一次請求進(jìn)來的時(shí)候绒窑,value保持唯一棕孙,可以用UUID生成,而score可以用當(dāng)前時(shí)間戳表示些膨,因?yàn)閟core我們可以用來計(jì)算當(dāng)前時(shí)間戳之內(nèi)有多少的請求數(shù)量蟀俊。而zset數(shù)據(jù)結(jié)構(gòu)也提供了range方法讓我們可以很輕易的獲取到2個(gè)時(shí)間戳內(nèi)有多少請求
代碼如下
public?Response?limitFlow(){
Long?currentTime?=?new?Date().getTime();
System.out.println(currentTime);
if(redisTemplate.hasKey("limit"))?{
Integer?count?=?redisTemplate.opsForZSet().rangeByScore("limit",?currentTime?-??intervalTime,?currentTime).size();????????//?intervalTime是限流的時(shí)間
System.out.println(count);
if?(count?!=?null?&&?count?>?5)?{
return?Response.ok("每分鐘最多只能訪問5次");
}
}
redisTemplate.opsForZSet().add("limit",UUID.randomUUID().toString(),currentTime);
return?Response.ok("訪問成功");
}
通過上述代碼可以做到滑動窗口的效果,并且能保證每N秒內(nèi)至多M個(gè)請求订雾,缺點(diǎn)就是zset的數(shù)據(jù)結(jié)構(gòu)會越來越大肢预。實(shí)現(xiàn)方式相對也是比較簡單的。
第三種:基于Redis的令牌桶算法
提到限流就不得不提到令牌桶算法了洼哎。令牌桶算法又稱之為水桶算法烫映,具體可以參照度娘的解釋 令牌桶算法
令牌桶算法提及到輸入速率和輸出速率,當(dāng)輸出速率大于輸入速率噩峦,那么就是超出流量限制了锭沟。
也就是說我們每訪問一次請求的時(shí)候,可以從Redis中獲取一個(gè)令牌壕探,如果拿到令牌了冈钦,那就說明沒超出限制,而如果拿不到李请,則結(jié)果相反瞧筛。
依靠上述的思想,我們可以結(jié)合Redis的List數(shù)據(jù)結(jié)構(gòu)很輕易的做到這樣的代碼
依靠List的leftPop來獲取令牌
//?輸出令牌
public?Response?limitFlow2(Long?id){
Object?result?=?redisTemplate.opsForList().leftPop("limit_list");
if(result?==?null){
return?Response.ok("當(dāng)前令牌桶中無令牌");
}
return?Response.ok(articleDescription2);
}
再依靠Java的定時(shí)任務(wù)导盅,定時(shí)往List中rightPush令牌较幌,當(dāng)然令牌也需要唯一性,所以我這里還是用UUID進(jìn)行了生成
//?10S的速率往令牌桶中添加UUID白翻,只為保證唯一性
@Scheduled(fixedDelay?=?10_000,initialDelay?=?0)
public?void?setIntervalTimeTask(){
redisTemplate.opsForList().rightPush("limit_list",UUID.randomUUID().toString());
}
綜上乍炉,代碼實(shí)現(xiàn)起始都不是很難,針對這些限流方式我們可以在AOP或者filter中加入以上代碼滤馍,用來做到接口的限流岛琼,最終保護(hù)你的網(wǎng)站。
Redis其實(shí)還有很多其他的用處巢株,他的作用不僅僅是緩存槐瑞,分布式鎖的作用。他的數(shù)據(jù)結(jié)構(gòu)也不僅僅是只有String阁苞,Hash困檩,List祠挫,Set,Zset悼沿。有興趣的可以后續(xù)了解下他的GeoHash算法等舔;BitMap,HLL以及布隆過濾器數(shù)據(jù)(Redis4.0之后加入糟趾,可以用Docker直接安裝redislabs/rebloom)結(jié)構(gòu)慌植。
本文來自PHP中文網(wǎng)的redis教程?欄目:https://www.php.cn/redis/