在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Redis實(shí)現(xiàn)分布式多規(guī)則限流的方式介紹

jf_ro2CN3Fa ? 來(lái)源:稀土掘金 ? 2024-02-26 10:07 ? 次閱讀

簡(jiǎn)介

市面上很多介紹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ù)

解決方法

記錄某IP訪問(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 次

考慮并發(fā)問(wèn)題

假設(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)

思考下圖

26e79030-d449-11ee-a297-92fbcf53809c.jpg

代碼實(shí)現(xiàn): 比較簡(jiǎn)單,

Zset解決臨界值問(wèn)題

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

26f03032-d449-11ee-a297-92fbcf53809c.jpg

網(wǎng)上幾乎都有實(shí)現(xiàn),這里就不過(guò)多介紹

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

先確定最終需要的效果

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

能實(shí)現(xiàn)防重復(fù)提交

通過(guò)以上要求設(shè)計(jì)注解(先想象出最終實(shí)現(xiàn)效果)

@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,RateRule)

編寫 RateLimiter 注解。

/**
*@Description:請(qǐng)求接口限制
*@Author:yiFei
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public@interfaceRateLimiter{

/**
*限流key
*/
Stringkey()defaultRedisKeyConstants.RATE_LIMIT_CACHE_PREFIX;

/**
*限流類型(默認(rèn)Ip模式)
*/
LimitTypeEnumlimitType()defaultLimitTypeEnum.IP;

/**
*錯(cuò)誤提示
*/
ResultCodemessage()defaultResultCode.REQUEST_MORE_ERROR;

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

/**
*防重復(fù)提交值
*/
booleanpreventDuplicate()defaultfalse;

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

編寫RateRule注解

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

/**
*限流次數(shù)
*/
longcount()default10;

/**
*限流時(shí)間
*/
longtime()default60;

/**
*限流時(shí)間單位
*/
TimeUnittimeUnit()defaultTimeUnit.SECONDS;

}

攔截注解 RateLimiter

確定redis存儲(chǔ)方式

RedisKey = prefix : className : methodName

RedisScore = 時(shí)間戳

RedisValue = 任意分布式不重復(fù)的值即可

編寫生成 RedisKey 的方法

/**
*通過(guò)rateLimiter和joinPoint拼接prefix:ip/userId:classSimpleName-methodName
*
*@paramrateLimiter提供prefix
*@paramjoinPoint提供classSimpleName:methodName
*@return
*/
publicStringgetCombineKey(RateLimiterrateLimiter,JoinPointjoinPoint){
StringBufferkey=newStringBuffer(rateLimiter.key());
//不同限流類型使用不同的前綴
switch(rateLimiter.limitType()){
//XXX可以新增通過(guò)參數(shù)指定參數(shù)進(jìn)行限流
caseIP:
key.append(IpUtil.getIpAddr(((ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest())).append(":");
break;
caseUSER_ID:
SysUserDetailsuser=SecurityUtil.getUser();
if(!ObjectUtils.isEmpty(user))key.append(user.getUserId()).append(":");
break;
caseGLOBAL:
break;
}
MethodSignaturesignature=(MethodSignature)joinPoint.getSignature();
Methodmethod=signature.getMethod();
ClasstargetClass=method.getDeclaringClass();
key.append(targetClass.getSimpleName()).append("-").append(method.getName());
returnkey.toString();
}

編寫lua腳本

編寫lua腳本 (兩種將時(shí)間添加到Redis的方法)。

Zset的UUID value值

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ù)
localkey=KEYS[1]
localuuid=KEYS[2]
localcurrentTime=tonumber(KEYS[3])
--2.以數(shù)組最大值為ttl最大值
localexpireTime=-1;
--3.遍歷數(shù)組查看是否超過(guò)限流規(guī)則
fori=1,#ARGV,2do
localrateRuleCount=tonumber(ARGV[i])
localrateRuleTime=tonumber(ARGV[i+1])
--3.1判斷在單位時(shí)間內(nèi)訪問(wèn)次數(shù)
localcount=redis.call('ZCOUNT',key,currentTime-rateRuleTime,currentTime)
--3.2判斷是否超過(guò)規(guī)定次數(shù)
iftonumber(count)>=rateRuleCountthen
returntrue
end
--3.3判斷元素最大值,設(shè)置為最終過(guò)期時(shí)間
ifrateRuleTime>expireTimethen
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)
returnfalse

根據(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ù)
localkey=KEYS[1]
localcurrentTime=KEYS[2]
--2.以數(shù)組最大值為ttl最大值
localexpireTime=-1;
--3.遍歷數(shù)組查看是否越界
fori=1,#ARGV,2do
localrateRuleCount=tonumber(ARGV[i])
localrateRuleTime=tonumber(ARGV[i+1])
--3.1判斷在單位時(shí)間內(nèi)訪問(wèn)次數(shù)
localcount=redis.call('ZCOUNT',key,currentTime-rateRuleTime,currentTime)
--3.2判斷是否超過(guò)規(guī)定次數(shù)
iftonumber(count)>=rateRuleCountthen
returntrue
end
--3.3判斷元素最大值,設(shè)置為最終過(guò)期時(shí)間
ifrateRuleTime>expireTimethen
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.1maxRetries最大重試次數(shù)retries重試次數(shù)
localmaxRetries=5
localretries=0
whiletruedo
localresult=redis.call('ZADD',key,currentTime,currentTime)
ifresult==1then
--6.2添加成功則跳出循環(huán)
break
else
--6.3未添加成功則value+1再次進(jìn)行嘗試
retries=retries+1
ifretries>=maxRetriesthen
--6.4超過(guò)最大嘗試次數(shù)采用添加隨機(jī)數(shù)策略
localrandom_value=math.random(1,1000)
currentTime=currentTime+random_value
else
currentTime=currentTime+1
end
end
end

returnfalse

編寫 AOP 攔截

@Autowired
privateRedisTemplateredisTemplate;

@Autowired
privateRedisScriptlimitScript;

/**
*限流
*XXX對(duì)限流要求比較高,可以使用在Redis中對(duì)規(guī)則進(jìn)行存儲(chǔ)校驗(yàn)或者使用中間件
*
*@paramjoinPointjoinPoint
*@paramrateLimiter限流注解
*/
@Before(value="@annotation(rateLimiter)")
publicvoidboBefore(JoinPointjoinPoint,RateLimiterrateLimiter){
//1.生成key
Stringkey=getCombineKey(rateLimiter,joinPoint);
try{
//2.執(zhí)行腳本返回是否限流
Booleanflag=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);
thrownewServiceException(rateLimiter.message());
}
}catch(ServiceExceptione){
throwe;
}catch(Exceptione){
e.printStackTrace();
}
}

/**
*獲取規(guī)則
*
*@paramrateLimiter獲取其中規(guī)則信息
*@return
*/
privateLong[]getRules(RateLimiterrateLimiter){
intcapacity=rateLimiter.rules().length<



審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • lua腳本
    +關(guān)注

    關(guān)注

    0

    文章

    21

    瀏覽量

    7613
  • Redis
    +關(guān)注

    關(guān)注

    0

    文章

    379

    瀏覽量

    10966

原文標(biāo)題:Redis 多規(guī)則限流和防重復(fù)提交方案實(shí)現(xiàn)

文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    redis分布式鎖場(chǎng)景實(shí)現(xiàn)

    今天帶大家深入剖析一下Redis分布式鎖,徹底搞懂它。 場(chǎng)景 既然要搞懂Redis分布式鎖,那肯定要有一個(gè)需要它的場(chǎng)景。 高并發(fā)售票問(wèn)題就是一個(gè)經(jīng)典案例。 搭建環(huán)境 準(zhǔn)備
    的頭像 發(fā)表于 09-25 17:09 ?772次閱讀

    在 Java 中利用 redis 實(shí)現(xiàn)一個(gè)分布式鎖服務(wù)

    在 Java 中利用 redis 實(shí)現(xiàn)一個(gè)分布式鎖服務(wù)
    發(fā)表于 07-05 13:14

    分布式Redis的五種數(shù)據(jù)類型

    分布式_Redis》_概述匯總
    發(fā)表于 10-15 10:55

    Redis 分布式鎖的正確實(shí)現(xiàn)方式

    分布式鎖一般有三種實(shí)現(xiàn)方式:1. 數(shù)據(jù)庫(kù)樂觀鎖;2. 基于Redis分布式鎖;3. 基于ZooKeeper的
    的頭像 發(fā)表于 05-31 14:19 ?3642次閱讀

    Redis分布式鎖真的安全嗎?

    今天我們來(lái)聊一聊Redis分布式鎖。
    的頭像 發(fā)表于 11-02 14:07 ?1049次閱讀

    手?jǐn)]了個(gè)Redis分布式

    實(shí)現(xiàn)分布式鎖的方式有很多,其中 Redis 是最常見的一種。而相較于 Java + Redis 的方案,我個(gè)人更傾向于 Go+
    的頭像 發(fā)表于 11-03 14:44 ?733次閱讀

    Redis實(shí)現(xiàn)限流的三種方式分享

    當(dāng)然,限流有許多種實(shí)現(xiàn)方式Redis具有很強(qiáng)大的功能,我用Redis實(shí)踐了三種的實(shí)現(xiàn)
    的頭像 發(fā)表于 02-22 09:52 ?1138次閱讀

    如何使用注解實(shí)現(xiàn)redis分布式鎖!

    使用 Redis 作為分布式鎖,將鎖的狀態(tài)放到 Redis 統(tǒng)一維護(hù),解決集群中單機(jī) JVM 信息不互通的問(wèn)題,規(guī)定操作順序,保護(hù)用戶的數(shù)據(jù)正確。
    發(fā)表于 04-25 12:42 ?696次閱讀
    如何使用注解<b class='flag-5'>實(shí)現(xiàn)</b><b class='flag-5'>redis</b><b class='flag-5'>分布式</b>鎖!

    分布式限流簡(jiǎn)介

    限流是生產(chǎn)中經(jīng)常遇到的一個(gè)場(chǎng)景, 目前現(xiàn)有的一個(gè)工具大部分是提供單機(jī)限流的能力, 例如 google 的 guava 中提供的 RateLimiter. 但是生產(chǎn)環(huán)境大部分是分布式環(huán)境, 在多臺(tái)機(jī)器的環(huán)境下, 需要的是能對(duì)多臺(tái)機(jī)
    的頭像 發(fā)表于 05-16 16:40 ?1109次閱讀
    <b class='flag-5'>分布式</b><b class='flag-5'>限流</b>簡(jiǎn)介

    深入理解redis分布式

    深入理解redis分布式鎖 哈嘍,大家好,我是指北君。 本篇文件我們來(lái)介紹如何Redis實(shí)現(xiàn)分布式
    的頭像 發(fā)表于 10-08 14:13 ?1018次閱讀
    深入理解<b class='flag-5'>redis</b><b class='flag-5'>分布式</b>鎖

    redis分布式鎖如何實(shí)現(xiàn)

    的情況,分布式鎖的作用就是確保在同一時(shí)間只有一個(gè)客戶端可以訪問(wèn)共享資源,從而保證數(shù)據(jù)的一致性和正確性。 下面將詳細(xì)介紹Redis分布式鎖的實(shí)現(xiàn)
    的頭像 發(fā)表于 11-16 11:29 ?580次閱讀

    redis分布式鎖死鎖處理方案

    引言: 隨著分布式系統(tǒng)的廣泛應(yīng)用,尤其是在大規(guī)模并發(fā)操作下,對(duì)并發(fā)控制的需求越來(lái)越高。Redis分布式鎖作為一種常見的分布式實(shí)現(xiàn)方案,由于
    的頭像 發(fā)表于 11-16 11:44 ?1823次閱讀

    redis分布式鎖的應(yīng)用場(chǎng)景有哪些

    Redis分布式鎖是一種基于Redis實(shí)現(xiàn)分布式鎖機(jī)制,可以在分布式環(huán)境下確保資源的獨(dú)占性,避
    的頭像 發(fā)表于 12-04 11:21 ?1514次閱讀

    如何實(shí)現(xiàn)Redis分布式

    機(jī)制,下面將詳細(xì)介紹如何實(shí)現(xiàn)Redis分布式鎖。 一、引言 在分布式系統(tǒng)中,多個(gè)節(jié)點(diǎn)可能同時(shí)讀寫同一共享資源。如果沒有
    的頭像 發(fā)表于 12-04 11:24 ?757次閱讀

    redis分布式鎖的缺點(diǎn)

    Redis分布式鎖是一種常見的用于解決分布式系統(tǒng)中資源爭(zhēng)用問(wèn)題的解決方案。盡管Redis分布式鎖具有很多優(yōu)點(diǎn),但它也存在一些缺點(diǎn)。本文將從幾
    的頭像 發(fā)表于 12-04 14:05 ?1332次閱讀
    主站蜘蛛池模板: 欧美成人精品久久精品 | 奇米影色777四色在线首页 | 国产综合在线视频 | 黄色免费网站视频 | 国产成人精品日本亚洲直接 | eeuss久久久精品影院 | 99久久99久久久精品齐齐鬼色 | 在线jlzzjlzz免费播放 | 欧美乱乱| 日本理论在线 | 99久久网站 | 在线www| 国产精品777 | 人人射人人澡 | 亚洲操综合 | 五月婷婷中文字幕 | 三级网站视频 | 四虎影院一级片 | 色综合天天五月色 | 男女爱爱福利 | 国产看片视频 | 在线人成精品免费视频 | 亚洲播放| 久久久噜噜噜久久中文字幕色伊伊 | 在线视频91 | 午夜精品一区二区三区在线观看 | 午夜小视频网站 | 色综合天天操 | 黄色网址视频在线观看 | 国产在线观看www鲁啊鲁免费 | 国产免费一级高清淫日本片 | 康熙古代高h细节肉爽文全文 | 91网站免费在线观看 | 欧美不卡1卡2卡三卡老狼 | 天天黄色 | 国产三级一区 | 国产天天色 | 一级aaaaaa片毛片在线播放 | 天天干天天草 | 亚洲精品在线不卡 | 爱逼色|