日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

在分布式領(lǐng)域,我們難免會(huì)遇到并發(fā)量突增,對(duì)后端服務(wù)造成高壓力,嚴(yán)重甚至?xí)?dǎo)致系統(tǒng)宕機(jī)。為避免這種問(wèn)題,我們通常會(huì)為接口添加限流、降級(jí)、熔斷等能力,從而使接口更為健壯。JAVA領(lǐng)域常見(jiàn)的開(kāi)源組件有Netflix的hystrix,阿里系開(kāi)源的sentinel等,都是蠻不錯(cuò)的限流熔斷框架。

今天我們就基于redis組件的特性,實(shí)現(xiàn)一個(gè)分布式限流組件,名字就定為shield-ratelimiter。

百度架構(gòu)師分享源碼實(shí)戰(zhàn)篇:通過(guò)分布式系統(tǒng)解決限流問(wèn)題

 

原理

首先解釋下為何采用Redis作為限流組件的核心。

通俗地講,假設(shè)一個(gè)用戶(用IP判斷)每秒訪問(wèn)某服務(wù)接口的次數(shù)不能超過(guò)10次,那么我們可以在Redis中創(chuàng)建一個(gè)鍵,并設(shè)置鍵的過(guò)期時(shí)間為60秒。

當(dāng)一個(gè)用戶對(duì)此服務(wù)接口發(fā)起一次訪問(wèn)就把鍵值加1,在單位時(shí)間(此處為1s)內(nèi)當(dāng)鍵值增加到10的時(shí)候,就禁止訪問(wèn)服務(wù)接口。PS:在某種場(chǎng)景中添加訪問(wèn)時(shí)間間隔還是很有必要的。我們本次不考慮間隔時(shí)間,只關(guān)注單位時(shí)間內(nèi)的訪問(wèn)次數(shù)。

需求

原理已經(jīng)講過(guò)了,說(shuō)下需求。

1、基于Redis的incr及過(guò)期機(jī)制開(kāi)發(fā) 2、調(diào)用方便,聲明式 3、Spring支持

基于上述需求,我們決定基于注解方式進(jìn)行核心功能開(kāi)發(fā),基于Spring-boot-starter作為基礎(chǔ)環(huán)境,從而能夠很好的適配Spring環(huán)境。

另外,在本次開(kāi)發(fā)中,我們不通過(guò)簡(jiǎn)單的調(diào)用Redis的java類庫(kù)API實(shí)現(xiàn)對(duì)Redis的incr操作。

原因在于,我們要保證整個(gè)限流的操作是原子性的,如果用Java代碼去做操作及判斷,會(huì)有并發(fā)問(wèn)題。這里我決定采用Lua腳本進(jìn)行核心邏輯的定義。

 

為何使用Lua

在正式開(kāi)發(fā)前,我簡(jiǎn)單介紹下對(duì)Redis的操作中,為何推薦使用Lua腳本。

1、減少網(wǎng)絡(luò)開(kāi)銷: 不使用 Lua 的代碼需要向 Redis 發(fā)送多次請(qǐng)求, 而腳本只需一次即可, 減少網(wǎng)絡(luò)傳輸; 2、原子操作: Redis 將整個(gè)腳本作為一個(gè)原子執(zhí)行, 無(wú)需擔(dān)心并發(fā), 也就無(wú)需事務(wù); 3、復(fù)用: 腳本會(huì)永久保存 Redis 中, 其他客戶端可繼續(xù)使用.

Redis添加了對(duì)Lua的支持,能夠很好的滿足原子性、事務(wù)性的支持,讓我們免去了很多的異常邏輯處理。對(duì)于Lua的語(yǔ)法不是本文的主要內(nèi)容,

正式開(kāi)發(fā)

到這里,我們正式開(kāi)始手寫(xiě)限流組件的進(jìn)程。

1. 工程定義

項(xiàng)目基于maven構(gòu)建,主要依賴Spring-boot-starter,我們主要在springboot上進(jìn)行開(kāi)發(fā),因此自定義的開(kāi)發(fā)包可以直接依賴下面這個(gè)坐標(biāo),方便進(jìn)行包管理。版本號(hào)自行選擇穩(wěn)定版。

2. Redis整合

由于我們是基于Redis進(jìn)行的限流操作,因此需要整合Redis的類庫(kù),上面已經(jīng)講到,我們是基于Springboot進(jìn)行的開(kāi)發(fā),因此這里可以直接整合RedisTemplate。

2.1 坐標(biāo)引入

這里我們引入spring-boot-starter-redis的依賴。

2.2 注入CacheManager及RedisTemplate

新建一個(gè)Redis的配置類,命名為RedisCacheConfig,使用javaconfig形式注入CacheManager及RedisTemplate。為了操作方便,我們采用了Jackson進(jìn)行序列化。代碼如下

注意要使用@Configuration 標(biāo)注此類為一個(gè)配置類,當(dāng)然你可以使用@Component , 但是不推薦,原因在于@Component 注解雖然也可以當(dāng)作配置類,但是并不會(huì)為其生成CGLIB代理Class,而使用@Configuration ,CGLIB會(huì)為其生成代理類,進(jìn)行性能的提升。

2.3 調(diào)用方Application.propertie需要增加Redis配置

我們的包開(kāi)發(fā)完畢之后,調(diào)用方的application.properties需要進(jìn)行相關(guān)配置如下:

如果有密碼的話,配置password即可。

這里為單機(jī)配置,如果需要支持哨兵集群,則配置如下,Java代碼不需要改動(dòng),只需要變動(dòng)配置即可。注意 兩種配置不能共存!

3. 定義注解

為了調(diào)用方便,我們定義一個(gè)名為RateLimiter 的注解,內(nèi)容如下

該注解明確只用于方法,主要有三個(gè)屬性。

1、key–表示限流模塊名,指定該值用于區(qū)分不同應(yīng)用,不同場(chǎng)景,推薦格式為:應(yīng)用名:模塊名:ip:接口名:方法名 2、limit–表示單位時(shí)間允許通過(guò)的請(qǐng)求數(shù) 3、expire–incr的值的過(guò)期時(shí)間,業(yè)務(wù)中表示限流的單位時(shí)間。

 

4. 解析注解

定義好注解后,需要開(kāi)發(fā)注解使用的切面,這里我們直接使用aspectj進(jìn)行切面的開(kāi)發(fā)。先看代碼

這里是注入了RedisTemplate,使用其API進(jìn)行Lua腳本的調(diào)用。

init() 方法在應(yīng)用啟動(dòng)時(shí)會(huì)初始化DefaultRedisScript,并加載Lua腳本,方便進(jìn)行調(diào)用。

PS: Lua腳本放置在classpath下,通過(guò)ClassPathResource進(jìn)行加載。

這里我們定義了一個(gè)切點(diǎn),表示只要注解了@RateLimiter 的方法,均可以觸發(fā)限流操作。

這段代碼的邏輯為,獲取 @RateLimiter 注解配置的屬性:key、limit、expire,并通過(guò)redisTemplate.execute(RedisScriptscript,Listkeys,Object…args) 方法傳遞給Lua腳本進(jìn)行限流相關(guān)操作,邏輯很清晰。

這里我們定義如果腳本返回狀態(tài)為0則為觸發(fā)限流,1表示正常請(qǐng)求。

5. Lua腳本

這里是我們整個(gè)限流操作的核心,通過(guò)執(zhí)行一個(gè)Lua腳本進(jìn)行限流的操作。腳本內(nèi)容如下

邏輯很通俗,我簡(jiǎn)單介紹下。

1、首先腳本獲取Java代碼中傳遞而來(lái)的要限流的模塊的key,不同的模塊key值一定不能相同,否則會(huì)覆蓋!2、redis.call(‘incr’, key1)對(duì)傳入的key做incr操作,如果key首次生成,設(shè)置超時(shí)時(shí)間ARGV[1];(初始值為1) 3、ttl是為防止某些key在未設(shè)置超時(shí)時(shí)間并長(zhǎng)時(shí)間已經(jīng)存在的情況下做的保護(hù)的判斷;4、每次請(qǐng)求都會(huì)做+1操作,當(dāng)限流的值val大于我們注解的閾值,則返回0表示已經(jīng)超過(guò)請(qǐng)求限制,觸發(fā)限流。否則為正常請(qǐng)求。

當(dāng)過(guò)期后,又是新的一輪循環(huán),整個(gè)過(guò)程是一個(gè)原子性的操作,能夠保證單位時(shí)間不會(huì)超過(guò)我們預(yù)設(shè)的請(qǐng)求閾值。

到這里我們便可以在項(xiàng)目中進(jìn)行測(cè)試。

測(cè)試

這里我貼一下核心代碼,我們定義一個(gè)接口,并注解@RateLimiter(key=“ratedemo:1.0.0”,limit=5,expire=100) 表示模塊ratedemo:sendPayment:1.0.0 在100s內(nèi)允許通過(guò)5個(gè)請(qǐng)求,這里的參數(shù)設(shè)置是為了方便看結(jié)果。實(shí)際中,我們通常會(huì)設(shè)置1s內(nèi)允許通過(guò)的次數(shù)。

我們通過(guò)RestClient請(qǐng)求接口,日志返回如下:

根據(jù)日志能夠看到,正常請(qǐng)求5次后,返回限流觸發(fā),說(shuō)明我們的邏輯生效,對(duì)前端而言也是可以看到false標(biāo)記,表明我們的Lua腳本限流邏輯是正確的,這里具體返回什么標(biāo)記需要調(diào)用方進(jìn)行明確的定義。

總結(jié)

我們通過(guò)Redis的incr及expire功能特性,開(kāi)發(fā)定義了一套基于注解的分布式限流操作,核心邏輯基于Lua保證了原子性。達(dá)到了很好的限流的目的,生產(chǎn)上,可以基于該特點(diǎn)進(jìn)行定制自己的限流組件,當(dāng)然你可以參考本文的代碼,相信你寫(xiě)的一定比我的demo更好!

分享到:
標(biāo)簽:分布式 系統(tǒng)
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定