最近在學(xué)習(xí)流量控制的RateLImiter,就想著也實(shí)現(xiàn)一個(gè)試試,大致思路是,請(qǐng)求來(lái)了判斷一下是否需要限流,不需要就正常走,記錄一下時(shí)間,需要的話,就去獲取令牌,獲取到了就執(zhí)行,沒(méi)有就等待一下,如果等待超時(shí)就返回,等待中獲取到了令牌就執(zhí)行,量很多,令牌桶不夠用了,就限制一下不是很著急的那些個(gè)請(qǐng)求,保證著急的請(qǐng)求先執(zhí)行。
流程如下:

根據(jù)流程將模塊劃分為:

閥值(開(kāi)關(guān)):主要負(fù)責(zé)正在運(yùn)行線程的加減、平均時(shí)間計(jì)算和限流開(kāi)關(guān);
方法轉(zhuǎn)化:主要負(fù)責(zé)接口或者url轉(zhuǎn)化為著急非著急的請(qǐng)求,以便我們進(jìn)行不同請(qǐng)求的緊急程度的控制;
令牌桶:主要是生成令牌的工具類(lèi);
配置:從配置文件limiter-config.properties讀取最大運(yùn)行線程,最大平均執(zhí)行時(shí)間等一系列參數(shù)。
定義了一個(gè)LimiterUtils,調(diào)用call方法來(lái)進(jìn)行流量控制處理,參數(shù)為2個(gè)接口和一個(gè)VO對(duì)象
public staticT call(LimiterCall rc,DBCall db,LimiterVO lVo)
LimiterCall:需要重寫(xiě)success和fail方法,作為獲取到鎖和沒(méi)有獲取到鎖的處理;
DBCall:是根據(jù)LimiterVO讀取數(shù)據(jù)庫(kù)等持久化數(shù)據(jù),以作為是否是著急接口或者url的判斷
LimiterVO:主要是記錄請(qǐng)求參數(shù):
private String interfaceName;
private String methodName;
private String url;
private LimiterType? limiterType;
有2個(gè)構(gòu)造函數(shù),來(lái)進(jìn)行url或者接口的控制。
主要邏輯:
1.判斷開(kāi)關(guān):
打開(kāi):當(dāng)系統(tǒng)中正在執(zhí)行線程達(dá)到最大線程數(shù)和線程平均處理時(shí)間大于了最大平均時(shí)間時(shí),開(kāi)關(guān)打開(kāi)(當(dāng)然也可以加上QPS的判斷,當(dāng)前QPS已經(jīng)算出來(lái),只是還沒(méi)有用進(jìn)去),亦可以手動(dòng)打開(kāi)。
關(guān)閉:當(dāng)系統(tǒng)正在執(zhí)行線程數(shù)小于最大線程數(shù),平均時(shí)間也低于最大平均時(shí)間,可認(rèn)為峰值已經(jīng)過(guò)去,可以將開(kāi)關(guān)關(guān)閉。也可以手動(dòng)關(guān)閉,不過(guò)盡量不要手動(dòng)去關(guān)閉。
2.著急令牌桶和非著急令牌桶
如果在限制時(shí)間內(nèi),著急令牌桶都是可以獲取到令牌的,所有請(qǐng)求都會(huì)走這個(gè)桶,如果在限定時(shí)間內(nèi)比如2s著急令牌桶已經(jīng)不能夠獲取到令牌,說(shuō)明限制請(qǐng)求已經(jīng)很大了,我們需要對(duì)非著急的請(qǐng)求進(jìn)行限制,打開(kāi)非著急令牌桶,以一個(gè)比較小的速率來(lái)放令牌,所有的非著急請(qǐng)求都走非著急令牌桶。
3.實(shí)時(shí)計(jì)算平均時(shí)間
當(dāng)前的處理邏輯是,當(dāng)執(zhí)行了正常邏輯,我會(huì)將當(dāng)前線程執(zhí)行時(shí)間寫(xiě)進(jìn)一個(gè)大小為100的static 的Collections.synchronizedList(new LinkedList()),以O(shè)rdering.natural().sortedCopy(xx)進(jìn)行排序并取去掉前后10%的中間部分來(lái)計(jì)算線程平均時(shí)間,根據(jù)Little定理算出QPS。
4.大量請(qǐng)求同時(shí)到達(dá)服務(wù)器,導(dǎo)致CPU處理繁忙
這一點(diǎn)也是大家容易忽視的,就比如同一個(gè)服務(wù)器,一下子又10000個(gè)請(qǐng)求過(guò)來(lái)了,是開(kāi)啟了限流,但是這個(gè)限流等待時(shí)間,這個(gè)多線程阻塞,系統(tǒng)完全扛不住,還是沒(méi)有達(dá)到限流的目的,我在請(qǐng)求之前,進(jìn)行邏輯處理,當(dāng)正在處理線程大于一個(gè)配置值時(shí),多余請(qǐng)求全部拒絕,或者服務(wù)器JVM內(nèi)存使用率超過(guò)了95%時(shí),請(qǐng)求也拒絕,這個(gè)是可以沒(méi)有的,如果以簡(jiǎn)單為主的話,內(nèi)存的判斷是不需要的,直接判斷當(dāng)前正在執(zhí)行線程就行,多了就們不要,當(dāng)然大家也可以單獨(dú)去計(jì)算CPU使用率,來(lái)作為判斷條件。JVM內(nèi)存的話,我使用的是:
long jvmfree=Runtime.getRuntime().freeMemory();
long jvmTotal =Runtime.getRuntime().totalMemory();
long jvmMax =Runtime.getRuntime().maxMemory();
logger.info("java 虛擬機(jī)空閑內(nèi)存"+jvmfree / 1024 / 1024);
logger.info("java 虛擬機(jī)獲得總內(nèi)存"+jvmTotal / 1024 / 1024);
logger.info("java 虛擬機(jī)最大內(nèi)存"+jvmMax / 1024 / 1024);
double jvmUseRate = (jvmTotal-jvmfree)*1.0/jvmMax;
測(cè)試了一下,流量起來(lái)之后是會(huì)自動(dòng)打開(kāi)限流的,流量小了,自動(dòng)回關(guān)閉,
因?yàn)槲业墓ぷ餍再|(zhì),所以我的配置文件的參數(shù),或者說(shuō)默認(rèn)參數(shù):
sys_max_running_thread = 500//開(kāi)關(guān)判斷的最大線程數(shù),
sys_max_qps = 250//系統(tǒng)最大qps
request_timeout = 2500//請(qǐng)求令牌最多等待時(shí)間,是令牌控制參數(shù),不是等待線程最大等待時(shí)間
non_worry_token_speed = 30//非著急令牌速率
max_await_thread_length =500//最大等待線程數(shù)
max_avg_handle_date = 3000//開(kāi)關(guān)判斷的最大平均處理時(shí)間
代碼:由于是在自己的工程里面寫(xiě)的,所以就帶有的工程的包路徑,勉強(qiáng)看吧。
https://github.com/learnPeopleHard/network-speed-limit