那曲檬骨新材料有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Redis實現分布式多規則限流的方式介紹

jf_ro2CN3Fa ? 來源:稀土掘金 ? 2024-02-26 10:07 ? 次閱讀

簡介

市面上很多介紹redis如何實現限流的,但是大部分都有一個缺點,就是只能實現單一的限流,比如1分鐘訪問1次或者60分鐘訪問10次這種,但是如果想一個接口兩種規則都需要滿足呢,我們的項目又是分布式項目,應該如何解決,下面就介紹一下redis實現分布式多規則限流的方式。

思考

如何一分鐘只能發送一次驗證碼,一小時只能發送10次驗證碼等等多種規則的限流

如何防止接口被惡意打擊(短時間內大量請求)

如何限制接口規定時間內訪問次數

解決方法

記錄某IP訪問次數

使用 String結構 記錄固定時間段內某用戶IP訪問某接口的次數

RedisKey = prefix : className : methodName

RedisVlue = 訪問次數

攔截請求:

初次訪問時設置 「[RedisKey] [RedisValue=1] [規定的過期時間]」

獲取 RedisValue 是否超過規定次數,超過則攔截,未超過則對 RedisKey 進行加1

分析: 規則是每分鐘訪問 1000 次

考慮并發問題

假設目前 RedisKey => RedisValue 為 999

目前大量請求進行到第一步( 獲取Redis請求次數 ),那么所有線程都獲取到了值為999,進行判斷都未超過限定次數則不攔截,導致實際次數超過 1000 次

「解決辦法:」

保證方法執行原子性(加鎖、lua)

考慮在臨界值進行訪問

思考下圖

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

代碼實現: 比較簡單,

Zset解決臨界值問題

使用 Zset 進行存儲,解決臨界值訪問問題

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

網上幾乎都有實現,這里就不過多介紹

實現多規則限流

先確定最終需要的效果

能實現多種限流規則

能實現防重復提交

通過以上要求設計注解(先想象出最終實現效果)

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

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

編寫注解(RateLimiter,RateRule)

編寫 RateLimiter 注解。

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

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

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

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

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

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

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

編寫RateRule注解

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

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

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

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

}

攔截注解 RateLimiter

確定redis存儲方式

RedisKey = prefix : className : methodName

RedisScore = 時間戳

RedisValue = 任意分布式不重復的值即可

編寫生成 RedisKey 的方法

/**
*通過rateLimiter和joinPoint拼接prefix:ip/userId:classSimpleName-methodName
*
*@paramrateLimiter提供prefix
*@paramjoinPoint提供classSimpleName:methodName
*@return
*/
publicStringgetCombineKey(RateLimiterrateLimiter,JoinPointjoinPoint){
StringBufferkey=newStringBuffer(rateLimiter.key());
//不同限流類型使用不同的前綴
switch(rateLimiter.limitType()){
//XXX可以新增通過參數指定參數進行限流
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腳本 (兩種將時間添加到Redis的方法)。

Zset的UUID value值

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

參數介紹

KEYS[1] = prefix : ? : className : methodName

KEYS[2] = 唯一ID

KEYS[3] = 當前時間

ARGV = [次數,單位時間,次數,單位時間, 次數, 單位時間 ...]

java傳入分布式不重復的 value 值

--1.獲取參數
localkey=KEYS[1]
localuuid=KEYS[2]
localcurrentTime=tonumber(KEYS[3])
--2.以數組最大值為ttl最大值
localexpireTime=-1;
--3.遍歷數組查看是否超過限流規則
fori=1,#ARGV,2do
localrateRuleCount=tonumber(ARGV[i])
localrateRuleTime=tonumber(ARGV[i+1])
--3.1判斷在單位時間內訪問次數
localcount=redis.call('ZCOUNT',key,currentTime-rateRuleTime,currentTime)
--3.2判斷是否超過規定次數
iftonumber(count)>=rateRuleCountthen
returntrue
end
--3.3判斷元素最大值,設置為最終過期時間
ifrateRuleTime>expireTimethen
expireTime=rateRuleTime
end
end
--4.redis中添加當前時間
redis.call('ZADD',key,currentTime,uuid)
--5.更新緩存過期時間
redis.call('PEXPIRE',key,expireTime)
--6.刪除最大時間限度之前的數據,防止數據過多
redis.call('ZREMRANGEBYSCORE',key,0,currentTime-expireTime)
returnfalse

根據時間戳作為Zset中的value值

參數介紹

KEYS[1] = prefix : ? : className : methodName

KEYS[2] = 當前時間

ARGV = [次數,單位時間,次數,單位時間, 次數, 單位時間 ...]

根據時間進行生成value值,考慮同一毫秒添加相同時間值問題

以下為第二種實現方式,在并發高的情況下效率低,value是通過時間戳進行添加,但是訪問量大的話會使得一直在調用 redis.call('ZADD', key, currentTime, currentTime),但是在不沖突value的情況下,會比生成 UUID 好

--1.獲取參數
localkey=KEYS[1]
localcurrentTime=KEYS[2]
--2.以數組最大值為ttl最大值
localexpireTime=-1;
--3.遍歷數組查看是否越界
fori=1,#ARGV,2do
localrateRuleCount=tonumber(ARGV[i])
localrateRuleTime=tonumber(ARGV[i+1])
--3.1判斷在單位時間內訪問次數
localcount=redis.call('ZCOUNT',key,currentTime-rateRuleTime,currentTime)
--3.2判斷是否超過規定次數
iftonumber(count)>=rateRuleCountthen
returntrue
end
--3.3判斷元素最大值,設置為最終過期時間
ifrateRuleTime>expireTimethen
expireTime=rateRuleTime
end
end
--4.更新緩存過期時間
redis.call('PEXPIRE',key,expireTime)
--5.刪除最大時間限度之前的數據,防止數據過多
redis.call('ZREMRANGEBYSCORE',key,0,currentTime-expireTime)
--6.redis中添加當前時間(解決多個線程在同一毫秒添加相同value導致Redis漏記的問題)
--6.1maxRetries最大重試次數retries重試次數
localmaxRetries=5
localretries=0
whiletruedo
localresult=redis.call('ZADD',key,currentTime,currentTime)
ifresult==1then
--6.2添加成功則跳出循環
break
else
--6.3未添加成功則value+1再次進行嘗試
retries=retries+1
ifretries>=maxRetriesthen
--6.4超過最大嘗試次數采用添加隨機數策略
localrandom_value=math.random(1,1000)
currentTime=currentTime+random_value
else
currentTime=currentTime+1
end
end
end

returnfalse

編寫 AOP 攔截

@Autowired
privateRedisTemplateredisTemplate;

@Autowired
privateRedisScriptlimitScript;

/**
*限流
*XXX對限流要求比較高,可以使用在Redis中對規則進行存儲校驗或者使用中間件
*
*@paramjoinPointjoinPoint
*@paramrateLimiter限流注解
*/
@Before(value="@annotation(rateLimiter)")
publicvoidboBefore(JoinPointjoinPoint,RateLimiterrateLimiter){
//1.生成key
Stringkey=getCombineKey(rateLimiter,joinPoint);
try{
//2.執行腳本返回是否限流
Booleanflag=redisTemplate.execute(limitScript,
ListUtil.of(key,String.valueOf(System.currentTimeMillis())),
(Object[])getRules(rateLimiter));
//3.判斷是否限流
if(Boolean.TRUE.equals(flag)){
log.error("ip:'{}'攔截到一個請求RedisKey:'{}'",
IpUtil.getIpAddr(((ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest()),
key);
thrownewServiceException(rateLimiter.message());
}
}catch(ServiceExceptione){
throwe;
}catch(Exceptione){
e.printStackTrace();
}
}

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



審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • lua腳本
    +關注

    關注

    0

    文章

    21

    瀏覽量

    7608
  • Redis
    +關注

    關注

    0

    文章

    378

    瀏覽量

    10936

原文標題:Redis 多規則限流和防重復提交方案實現

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    redis分布式鎖場景實現

    今天帶大家深入剖析一下Redis分布式鎖,徹底搞懂它。 場景 既然要搞懂Redis分布式鎖,那肯定要有一個需要它的場景。 高并發售票問題就是一個經典案例。 搭建環境 準備
    的頭像 發表于 09-25 17:09 ?761次閱讀

    在 Java 中利用 redis 實現一個分布式鎖服務

    在 Java 中利用 redis 實現一個分布式鎖服務
    發表于 07-05 13:14

    分布式Redis的五種數據類型

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

    Redis 分布式鎖的正確實現方式

    分布式鎖一般有三種實現方式:1. 數據庫樂觀鎖;2. 基于Redis分布式鎖;3. 基于ZooKeeper的
    的頭像 發表于 05-31 14:19 ?3633次閱讀

    Redis分布式鎖真的安全嗎?

    今天我們來聊一聊Redis分布式鎖。
    的頭像 發表于 11-02 14:07 ?1042次閱讀

    手擼了個Redis分布式

    實現分布式鎖的方式有很多,其中 Redis 是最常見的一種。而相較于 Java + Redis 的方案,我個人更傾向于 Go+
    的頭像 發表于 11-03 14:44 ?728次閱讀

    Redis實現限流的三種方式分享

    當然,限流有許多種實現方式Redis具有很強大的功能,我用Redis實踐了三種的實現
    的頭像 發表于 02-22 09:52 ?1133次閱讀

    如何使用注解實現redis分布式鎖!

    使用 Redis 作為分布式鎖,將鎖的狀態放到 Redis 統一維護,解決集群中單機 JVM 信息不互通的問題,規定操作順序,保護用戶的數據正確。
    發表于 04-25 12:42 ?690次閱讀
    如何使用注解<b class='flag-5'>實現</b><b class='flag-5'>redis</b><b class='flag-5'>分布式</b>鎖!

    分布式限流簡介

    限流是生產中經常遇到的一個場景, 目前現有的一個工具大部分是提供單機限流的能力, 例如 google 的 guava 中提供的 RateLimiter. 但是生產環境大部分是分布式環境, 在多臺機器的環境下, 需要的是能對多臺機
    的頭像 發表于 05-16 16:40 ?1104次閱讀
    <b class='flag-5'>分布式</b><b class='flag-5'>限流</b>簡介

    深入理解redis分布式

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

    redis分布式鎖如何實現

    的情況,分布式鎖的作用就是確保在同一時間只有一個客戶端可以訪問共享資源,從而保證數據的一致性和正確性。 下面將詳細介紹Redis分布式鎖的實現
    的頭像 發表于 11-16 11:29 ?574次閱讀

    redis分布式鎖死鎖處理方案

    引言: 隨著分布式系統的廣泛應用,尤其是在大規模并發操作下,對并發控制的需求越來越高。Redis分布式鎖作為一種常見的分布式實現方案,由于
    的頭像 發表于 11-16 11:44 ?1814次閱讀

    redis分布式鎖的應用場景有哪些

    Redis分布式鎖是一種基于Redis實現分布式鎖機制,可以在分布式環境下確保資源的獨占性,避
    的頭像 發表于 12-04 11:21 ?1498次閱讀

    如何實現Redis分布式

    機制,下面將詳細介紹如何實現Redis分布式鎖。 一、引言 在分布式系統中,多個節點可能同時讀寫同一共享資源。如果沒有
    的頭像 發表于 12-04 11:24 ?750次閱讀

    redis分布式鎖的缺點

    Redis分布式鎖是一種常見的用于解決分布式系統中資源爭用問題的解決方案。盡管Redis分布式鎖具有很多優點,但它也存在一些缺點。本文將從幾
    的頭像 發表于 12-04 14:05 ?1320次閱讀
    定做百家乐桌子| 百家乐必胜法技巧| 互联网百家乐官网的玩法技巧和规则| 宝格丽百家乐娱乐城| 百家乐和怎么算输赢| 克拉克娱乐城| 百家乐官网规律打法| 百家乐试玩全讯网2| 百家乐路珠价格| 永凡棋牌游戏| 至尊百家乐官网停播| 百家乐连闲几率| 大发888娱乐城下载电脑怎么上乐讯新足球今日比分 | 百家乐官网平台注册| 新时代百家乐娱乐城| 百家乐实战玩法| 168棋牌游戏| 百家乐官网技巧打| 百家乐套利| 百家乐官网视频游戏道具| 百家乐官网赌场公司| 百家乐赔率计算| 金道百家乐官网游戏| 百家乐必胜绝技| 德州扑克怎么算牌| 百家乐官网路单破| 威尼斯人娱乐城 老品牌值得您信赖| 伟博百家乐官网现金网| 百家乐视频游戏客服| 太阳城官网| 百家乐怎样算大小| 棋牌游戏| 澳门百家乐自杀| 王牌国际| 百家乐最常见的路子| 循化| 百家乐游戏机压法| 百家乐官网网址| 百家乐官网平六亿财富| 大发888备用网站| 百家乐官网网上真钱娱乐场开户注册 |