限流,永遠(yuǎn)都不是一件簡(jiǎn)單的事!
本文轉(zhuǎn)載自微信公眾號(hào)「 咖啡拿鐵」,作者 咖啡拿鐵。轉(zhuǎn)載本文請(qǐng)聯(lián)系 咖啡拿鐵公眾號(hào)。
背景
隨著微服務(wù)的流行,服務(wù)之間的穩(wěn)定性變得越發(fā)重要,往往我們會(huì)花很多經(jīng)歷在維護(hù)服務(wù)的穩(wěn)定性上,限流和熔斷降級(jí)是我們最常用的兩個(gè)手段。前段時(shí)間在群里有些小伙伴對(duì)限流的使用些疑問(wèn),再加上最近公司大促也做了限流相關(guān)的事,所以在這里總結(jié)一下寫(xiě)寫(xiě)自己對(duì)限流的一些看法。
剛才說(shuō)了限流是我們保證服務(wù)穩(wěn)定性的手段之一,但是他并不是所有場(chǎng)景的穩(wěn)定性都能保證,和他名字一樣他只能在大流量或者突發(fā)流量的場(chǎng)景下才能發(fā)揮出自己的作用。比如我們的系統(tǒng)最高支持100QPS,但是突然有1000QPS請(qǐng)求打了進(jìn)來(lái),可能這個(gè)時(shí)候系統(tǒng)就會(huì)直接掛掉,導(dǎo)致后面一個(gè)請(qǐng)求都處理不了,但是如果我們有限流的手段,無(wú)論他有多大的QPS,我們都只處理100QPS的請(qǐng)求,其他請(qǐng)求都直接拒絕掉,雖然有900的QPS的請(qǐng)求我們拒絕掉了,但是我們的系統(tǒng)沒(méi)有掛掉,我們系統(tǒng)仍然可以不斷的處理后續(xù)的請(qǐng)求,這個(gè)是我們所期望的。有同學(xué)可能會(huì)說(shuō),現(xiàn)在都上的云了,服務(wù)的動(dòng)態(tài)伸縮應(yīng)該是特別簡(jiǎn)單的吧,如果我們發(fā)現(xiàn)流量特別大的時(shí)候,自動(dòng)擴(kuò)容機(jī)器到可以支撐目標(biāo)QPS那不就不需要限流了嗎?其實(shí)有這個(gè)想法的同學(xué)應(yīng)該還挺多的,有些同學(xué)可能被一些吹牛的文章給唬到了,所以才會(huì)這么想,這個(gè)想法在特別理想化的時(shí)候是可以實(shí)現(xiàn)的,但是在現(xiàn)實(shí)中其實(shí)有下面幾個(gè)問(wèn)題:
- 擴(kuò)容是需要時(shí)間。擴(kuò)容簡(jiǎn)單來(lái)說(shuō)就是搞一個(gè)新的機(jī)器,然后重新發(fā)布代碼,做java的同學(xué)應(yīng)該是知道發(fā)布成功一個(gè)代碼的時(shí)間一般不是以秒級(jí)計(jì)算,而是以分鐘級(jí)別計(jì)算,有時(shí)候你擴(kuò)容完成,說(shuō)不定流量尖峰都過(guò)去了。
- 擴(kuò)容到多少是個(gè)特別復(fù)雜的問(wèn)題。擴(kuò)容幾臺(tái)機(jī)器這個(gè)是比較復(fù)雜的,需要大量的壓測(cè)計(jì)算,以及整條鏈路上的一個(gè)擴(kuò)容,如果擴(kuò)容了你這邊的機(jī)器之后,其他團(tuán)隊(duì)的機(jī)器沒(méi)有擴(kuò)容可能最后還是有瓶頸這個(gè)也是一個(gè)問(wèn)題。
所以單純的擴(kuò)容是解決不了這個(gè)問(wèn)題的,限流仍然是我們必須掌握的技能!
基本原理
想要掌握好限流,就需要先掌握他的一些基本算法,限流的算法基本上分為三種,計(jì)數(shù)器,漏斗,令牌桶,其他的一些都是在這些基礎(chǔ)上進(jìn)行演變而來(lái)。
計(jì)數(shù)器算法
首先我們來(lái)說(shuō)一下計(jì)數(shù)器算法,這個(gè)算法比較簡(jiǎn)單粗暴,我們只需要一個(gè)累加變量,然后每隔一秒鐘去刷新這個(gè)累加變量,然后再判斷這個(gè)累加變量是否大于我們的最大QPS。
- int curQps = 0;
- long lastTime = System.currentTimeMillis();
- int maxQps = 100;
- Object lock = new Object();
- boolean check(){
- synchronized (lock){
- long now = System.currentTimeMillis();
- if (now - lastTime > 1000){
- lastTime = now;
- curQps = 0;
- }
- curQps++;
- if (curQps > maxQps){
- return false;
- }
- }
- return true;
- }
這個(gè)代碼比較簡(jiǎn)單,我們定義了當(dāng)前的qps,以及上一次刷新累加變量的時(shí)間,還有我們的最大qps和我們的lock鎖,我們每次檢查的時(shí)候,都需要判斷是否需要刷新,如果需要刷新那么需要把時(shí)間和qps都進(jìn)行重置,然后再進(jìn)行qps的累加判斷。
這個(gè)算法因?yàn)樘?jiǎn)單了所以帶來(lái)的問(wèn)題也是特別明顯,如果我們最大的qps是100,在0.99秒的時(shí)候來(lái)了100個(gè)請(qǐng)求,然后在1.01秒的時(shí)候又來(lái)了100個(gè)請(qǐng)求,這個(gè)是可以通過(guò)我們的程序的,但是我們其實(shí)在0.03秒之內(nèi)通過(guò)了200個(gè)請(qǐng)求,這個(gè)肯定不符合我們的預(yù)期,因?yàn)楹苡锌赡苓@200個(gè)請(qǐng)求直接就會(huì)將我們機(jī)器給打掛。
滑動(dòng)窗口計(jì)數(shù)器
為了解決上面的臨界的問(wèn)題,我們這里可以使用滑動(dòng)窗口來(lái)解決這個(gè)問(wèn)題:
如上圖所示,我們將1s的普通計(jì)數(shù)器,分成了5個(gè)200ms,我們統(tǒng)計(jì)的當(dāng)前qps都需要統(tǒng)計(jì)最近的5個(gè)窗口的所有qps,再回到剛才的問(wèn)題,0.99秒和1.01秒其實(shí)都在我們的最近5個(gè)窗口之內(nèi),所以這里不會(huì)出現(xiàn)剛才的臨界的突刺問(wèn)題。
其實(shí)換個(gè)角度想,我們普通的計(jì)數(shù)器其實(shí)就是窗口數(shù)量為1的滑動(dòng)窗口計(jì)數(shù)器,只要我們分的窗口越多,我們使用計(jì)數(shù)器方案的時(shí)候統(tǒng)計(jì)就會(huì)越精確,但是相對(duì)來(lái)說(shuō)維護(hù)的窗口的成本就會(huì)增加,等會(huì)我們介紹sentinel的時(shí)候會(huì)詳細(xì)介紹他是怎么實(shí)現(xiàn)滑動(dòng)窗口計(jì)數(shù)的。
漏斗算法
解決計(jì)數(shù)器中臨界的突刺問(wèn)題也可以通過(guò)漏斗算法來(lái)實(shí)現(xiàn),如下圖所示:
在漏斗算法中我們需要關(guān)注漏桶和勻速流出,不論流量有多大都會(huì)先到漏桶中,然后以均勻的速度流出。如何在代碼中實(shí)現(xiàn)這個(gè)勻速呢?比如我們想讓勻速為100q/s,那么我們可以得到每流出一個(gè)流量需要消耗10ms,類似一個(gè)隊(duì)列,每隔10ms從隊(duì)列頭部取出流量進(jìn)行放行,而我們的隊(duì)列也就是漏桶,當(dāng)流量大于隊(duì)列的長(zhǎng)度的時(shí)候,我們就可以拒絕超出的部分。
漏斗算法同樣的也有一定的缺點(diǎn):無(wú)法應(yīng)對(duì)突發(fā)流量(和上面的臨界突刺不一樣,不要混淆)。比如一瞬間來(lái)了100個(gè)請(qǐng)求,在漏桶算法中只能一個(gè)一個(gè)的過(guò)去,當(dāng)最后一個(gè)請(qǐng)求流出的時(shí)候時(shí)間已經(jīng)過(guò)了一秒了,所以漏斗算法比較適合請(qǐng)求到達(dá)比較均勻,需要嚴(yán)格控制請(qǐng)求速率的場(chǎng)景。
令牌桶算法
為了解決突發(fā)流量情況,我們可以使用令牌桶算法,如下圖所示:
這個(gè)圖上需要關(guān)注三個(gè)階段:
- 生產(chǎn)令牌:我們?cè)谶@里同樣的還是假設(shè)最大qps是100,那么我們從漏斗的每10ms過(guò)一個(gè)流量轉(zhuǎn)化成每10ms生產(chǎn)一個(gè)令牌,直到達(dá)到最大令牌。
- 消耗令牌:我們每一個(gè)流量都會(huì)消耗令牌桶,這里的消耗的規(guī)則可以多變,既可以是簡(jiǎn)單的每個(gè)流量消耗一個(gè)令牌,又可以是根據(jù)不同的流量數(shù)據(jù)包大小或者流量類型來(lái)進(jìn)行不同的消耗規(guī)則,比如查詢的流量消耗1個(gè)令牌,寫(xiě)入的流量消耗2個(gè)令牌。
- 判斷是否通過(guò):如果令牌桶足夠那么我們就允許流量通過(guò),如果不足夠可以等待或者直接拒絕,這個(gè)就可以采用漏斗那種用隊(duì)列來(lái)控制。
單機(jī)限流
上面我們已經(jīng)介紹了限流的一些基本算法,我們把這些算法應(yīng)用到我們的分布式服務(wù)中又可以分為兩種,一個(gè)是單機(jī)限流,一個(gè)是集群限流。單機(jī)限流指的是每臺(tái)機(jī)器各自做自己的限流,互不影響。我們接下來(lái)看看單機(jī)限流怎么去實(shí)現(xiàn)呢?
guava
guava是谷歌開(kāi)源的java核心工具庫(kù),里面包括集合,緩存,并發(fā)等好用的工具,當(dāng)然也提供了我們這里所需要的的限流的工具,核心類就是RateLimiter。
- // RateLimiter rateLimiter = RateLimiter.create(100, 500, TimeUnit.MILLISECONDS); 預(yù)熱的rateLimit
- RateLimiter rateLimiter = RateLimiter.create(100); // 簡(jiǎn)單的rateLimit
- boolean limitResult = rateLimiter.tryAcquire();
使用方式比較簡(jiǎn)單,如上面代碼所示,我們只需要構(gòu)建一個(gè)RateLimiter,然后再調(diào)用tryAcquire方法,如果返回為true代表我們此時(shí)流量通過(guò),相反則被限流。在guava中RateLimiter也分為兩種,一個(gè)是普通的令牌桶算法的實(shí)現(xiàn),還有一個(gè)是帶有預(yù)熱的RateLimiter,可以讓我們令牌桶的釋放速度逐步增加直到最大,這個(gè)帶有預(yù)熱的在sentinel也有,這個(gè)可以在一些冷系統(tǒng)中比如數(shù)據(jù)庫(kù)連接池沒(méi)有完全填滿,還在不斷初始化的場(chǎng)景下使用。
在這里只簡(jiǎn)單的介紹一下guava的令牌桶怎么去實(shí)現(xiàn)的:
普通的令牌桶創(chuàng)建了一個(gè)SmoothBursty的類,這個(gè)類也就是我們實(shí)現(xiàn)限流的關(guān)鍵,具體怎么做限流的在我們的tryAcquire中:
這里分為四步:
- Step1: 加上一個(gè)同步鎖,需要注意一下這里在sentinel中并沒(méi)有加鎖這個(gè)環(huán)節(jié),在guava中是有這個(gè)的,后續(xù)也會(huì)將sentinel的一些問(wèn)題。
- Step2: 判斷是否能申請(qǐng)令牌桶,如果桶內(nèi)沒(méi)有足夠的令牌并且等待時(shí)間超過(guò)我們的timeout,這里我們就不進(jìn)行申請(qǐng)了。
- Step3: 申請(qǐng)令牌并獲取等待時(shí)間,在我們tryAcquire中的timeout參數(shù)就是就是我們的最大等待時(shí)間,如果我們只是調(diào)用tryAcquire(),不會(huì)出現(xiàn)等待,第二步的時(shí)候已經(jīng)快速失敗了。
- Step4: sleep等待的時(shí)間。
扣除令牌的方法具體在reserverEarliestAvailable方法中:
這里雖然看起來(lái)過(guò)程比較多,但是如果我們只是調(diào)用tryAcquire(),就只需要關(guān)注兩個(gè)紅框:
- Step1: 根據(jù)當(dāng)前最新時(shí)間發(fā)放token,在guava中沒(méi)有采用使用其他線程異步發(fā)放token的方式,把token的更新放在了我們每次調(diào)用限流方法中,這個(gè)設(shè)計(jì)可以值得學(xué)習(xí)一下,很多時(shí)候不一定需要異步線程去執(zhí)行也可以達(dá)到我們想要的目的,并且也沒(méi)有異步線程的復(fù)雜。
- Step2: 扣除令牌,這里我們已經(jīng)在canAcquire中校驗(yàn)過(guò)了,令牌一定能扣除成功。
guava的限流目前就提供了這兩種方式的限流,很多中間件或者業(yè)務(wù)服務(wù)都把guava的限流作為自己的工具,但是guava的方式比較局限,動(dòng)態(tài)改變限流,以及更多策略的限流都不支持,所以我們接下來(lái)介紹一下sentinel。
sentinel
sentinel是阿里巴巴開(kāi)源的分布式服務(wù)框架的輕量級(jí)流量控制框架,承接了阿里巴巴近 10 年的雙十一大促流量的核心場(chǎng)景,他的核心是流量控制但是不局限于流量控制,還支持熔斷降級(jí),監(jiān)控等等。
使用sentinel的限流稍微比guava復(fù)雜很多,下面寫(xiě)了一個(gè)最簡(jiǎn)單的代碼:
- String KEY = "test";
- // ============== 初始化規(guī)則 =========
- List<FlowRule> rules = new ArrayList<FlowRule>();
- FlowRule rule1 = new FlowRule();
- rule1.setResource(KEY);
- // set limit qps to 20
- rule1.setCount(20);
- rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
- rule1.setLimitApp("default");
- rules.add(rule1);
- rule1.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT);
- FlowRuleManager.loadRules(rules);
- // ================ 限流判定 ===========
- Entry entry = null;
- try {
- entry = SphU.entry(KEY);
- // do something
- } catch (BlockException e1) {
- // 限流會(huì)拋出BlockException 異常
- }finally {
- if (entry != null) {
- entry.exit();
- }
- }
- Step1:在sentinel中比較強(qiáng)調(diào)Resource這個(gè)概念,我們所保護(hù)的或者說(shuō)所作用于都是基于Resource來(lái)說(shuō),所以我們首先需要確定我們的Resource的key,這里我們簡(jiǎn)單的設(shè)置為test了。
- Step2:然后我們初始化我們這個(gè)Resource的一個(gè)限流規(guī)則,我們這里選擇的是針對(duì)QPS限流并且策略選擇的是默認(rèn),這里默認(rèn)的話就是使用的滑動(dòng)窗口版的計(jì)數(shù)器,然后加載到全局的規(guī)則管理器里面,整個(gè)規(guī)則的設(shè)置和guava的差別比較大。
- Step3: 在sentinel第二個(gè)比較重要的概念就是Entry,Entry表示一次資源操作,內(nèi)部會(huì)保存當(dāng)前invocation信息,在finally的時(shí)候需要對(duì)entry進(jìn)行退出。我們執(zhí)行限流判定的時(shí)候?qū)嶋H上也就是獲取Entry,SphU.entry也就是我們執(zhí)行我們上面限流規(guī)則的關(guān)鍵,這里和guava不一樣如果被限流了,就會(huì)拋出BlockException,我們?cè)谶M(jìn)行限流的處理。
雖然sentinel的使用整體比guava復(fù)雜很多,但是算法的可選比guava的限流也多一點(diǎn)。
基于并發(fā)數(shù)(線程數(shù))
我們之前介紹的都是基于QPS的,在sentinel中提供了基于并發(fā)數(shù)的策略,效果類似于信號(hào)量隔離,當(dāng)我們需要讓業(yè)務(wù)線程池不被慢調(diào)用耗盡,我們就可以使用這種模式。
通常來(lái)說(shuō)我們同一個(gè)服務(wù)提供的http接口都是使用的一個(gè)線程池,比如我們使用的tomcat-web服務(wù)器那么我們就會(huì)有個(gè)tomcat的業(yè)務(wù)線程池,如果在http中有兩個(gè)方法A和B,B的速度相對(duì)來(lái)說(shuō)比較快,A的速度相對(duì)來(lái)說(shuō)比較慢,如果大量的調(diào)用A這個(gè)方法,由于A的速度太慢,線程得不到釋放,有可能導(dǎo)致線程池被耗盡,另一個(gè)方法B就得不到線程。這個(gè)場(chǎng)景我們之前有遇到過(guò)直接導(dǎo)致整個(gè)服務(wù)所接收的請(qǐng)求全部被拒絕。有的同學(xué)說(shuō)限制A的QPS不是就可以了嗎,要注意的是QPS是每秒的,如果我們這個(gè)A接口的耗時(shí)大于1s,那么下一波A來(lái)了之后QPS是要重新計(jì)算的。
基于這個(gè)就提供了基于并發(fā)數(shù)的限流,我們?cè)O(shè)置Grade為FLOW_GRADE_THREAD,就可以實(shí)現(xiàn)這個(gè)限流模式。
基于QPS
基于QPS的限流sentinel也提供了4種策略:
- 默認(rèn)策略:設(shè)置Behavior為CONTROL_BEHAVIOR_DEFAULT,這個(gè)模式是滑動(dòng)窗口計(jì)數(shù)器模式。這種方式適用于對(duì)系統(tǒng)處理能力確切已知的情況下,比如通過(guò)壓測(cè)確定了系統(tǒng)的準(zhǔn)確水位時(shí)。
- Warm Up:設(shè)置為Behavior為CONTROL_BEHAVIOR_WARM_UP,類似之前guava中介紹的warmup。預(yù)熱啟動(dòng)方式。當(dāng)系統(tǒng)長(zhǎng)期處于低水位的情況下,當(dāng)流量突然增加時(shí),直接把系統(tǒng)拉升到高水位可能瞬間把系統(tǒng)壓垮。這個(gè)模式下QPS的曲線圖如下:
- 勻速排隊(duì):設(shè)置Behavior為CONTROL_BEHAVIOR_RATE_LIMITER,這個(gè)模式其實(shí)就是漏斗算法,優(yōu)缺點(diǎn)之前也講解過(guò)了
- Warm Up + 勻速排隊(duì):設(shè)置Behavior為CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER,之前warm up到高水位之后使用的是滑動(dòng)窗口的算法限流,這個(gè)模式下繼續(xù)使用勻速排隊(duì)的算法。
基于調(diào)用關(guān)系
sentinel提供了更為復(fù)雜的一種限流,可以基于調(diào)用關(guān)系去做更為靈活的限流:
- 根據(jù)調(diào)用方限流:調(diào)用方的限流使用比較復(fù)雜,需要調(diào)用ContextUtil.enter(resourceName, origin),origin就是我們的調(diào)用方標(biāo)識(shí),然后在我們的rule設(shè)置參數(shù)的時(shí)候,對(duì)limitApp進(jìn)行設(shè)置就可以進(jìn)行對(duì)調(diào)用方的限流:
- 設(shè)置為default,默認(rèn)對(duì)所有調(diào)用方都限流。
- 設(shè)置為{some_origin_name},代表對(duì)特定的調(diào)用者才限流。
- 設(shè)置為other,會(huì)對(duì)配置的一個(gè)referResource參數(shù)代表的調(diào)用者除外的進(jìn)行限流。
關(guān)聯(lián)流量控制:在sentinel中也支持,兩個(gè)有關(guān)聯(lián)的資源可以互相影響流量控制,比如有兩個(gè)接口都使用的是同一個(gè)資源,一個(gè)接口比較重要,另外一個(gè)接口不是那么重要,我們可以設(shè)置一個(gè)規(guī)則當(dāng)重要的接口大量訪問(wèn)的時(shí)候,就可以對(duì)另外一個(gè)不重要接口進(jìn)行限流,防止這個(gè)接口突然出現(xiàn)流量影響重要的接口。
sentinel的一些問(wèn)題
sentinel雖然提供了這么多算法,但是也有一些問(wèn)題:
- 首先來(lái)說(shuō)sentinel上手比較難,對(duì)比guava的兩行代碼來(lái)說(shuō),使用sentinel需要了解一些名詞,然后針對(duì)這些名詞再來(lái)使用,雖然sentinel提供了一些注解來(lái)幫助我們簡(jiǎn)化使用,但是整體來(lái)說(shuō)還是比guava要復(fù)雜。
- sentinel有一定的運(yùn)維成本,sentinel的使用往往需要搭建sentinel的server后臺(tái),對(duì)比guava的開(kāi)箱即用來(lái)說(shuō),有一定的運(yùn)維成本。
- sentinel的限流統(tǒng)計(jì)有一定的并發(fā)問(wèn)題,在sentinel的源碼中是沒(méi)有加鎖的地方的,極端情況下如果qps限制的是10,如果有100個(gè)同時(shí)過(guò)限流的邏輯,這個(gè)時(shí)候都會(huì)通過(guò),而guava不會(huì)發(fā)生這樣的情況。
這些問(wèn)題基本上都是和guava的限流來(lái)比較的,畢竟sentinel的功能更多,付出的成本相對(duì)來(lái)說(shuō)也會(huì)更多。
集群限流
之前說(shuō)的所有限流都是單機(jī)限流,但是我們現(xiàn)在都是微服務(wù)集群的架構(gòu)模式,通常一個(gè)服務(wù)會(huì)有多臺(tái)機(jī)器,比如有一個(gè)訂單服務(wù),這個(gè)服務(wù)有10臺(tái)機(jī)器,那么我們想做整個(gè)集群限流到500QPS,我們應(yīng)該怎么去做呢?這個(gè)很簡(jiǎn)單,直接每臺(tái)機(jī)器都限流50就好了,50*10就是500,但是在現(xiàn)實(shí)環(huán)境中會(huì)出現(xiàn)負(fù)載不均衡的情況,在微服務(wù)調(diào)用的時(shí)候負(fù)載均衡的算法多種多樣,比如同機(jī)房?jī)?yōu)先,輪訓(xùn),隨機(jī)等算法,這些算法都有可能導(dǎo)致我們的負(fù)載不是特別的均衡,就會(huì)導(dǎo)致我們整個(gè)集群的QPS可能有沒(méi)有500,甚至在400的時(shí)候就被限流了,這個(gè)是我們真實(shí)場(chǎng)景中所遇到過(guò)的。既然單機(jī)限流有問(wèn)題,那么我們應(yīng)該設(shè)計(jì)一個(gè)更加完善的集群限流的方案
Redis
這個(gè)方案不依賴限流的框架,我們整個(gè)集群使用同一個(gè)redis即可,需要自己封裝一下限流的邏輯,這里我們使用最簡(jiǎn)單的計(jì)數(shù)器去設(shè)計(jì),我們將我們的系統(tǒng)時(shí)間以秒為單位作為key,設(shè)置到redis里面(可以設(shè)置一定的過(guò)期時(shí)間用于空間清理),利用redis的int原子加法,每來(lái)一個(gè)請(qǐng)求都進(jìn)行+1,然后再判斷當(dāng)前值是否超過(guò)我們限流的最大值。
redis的方案實(shí)現(xiàn)起來(lái)整體來(lái)說(shuō)比較簡(jiǎn)單,但是強(qiáng)依賴我們的系統(tǒng)時(shí)間,如果不同機(jī)器之間的系統(tǒng)時(shí)間有偏差限流就有可能不準(zhǔn)確。
sentinel
在sentinel中提供了集群的解決方案,這個(gè)對(duì)比其他的一些限流框架是比較有特色的。在sentinel中提供了兩種模式:
- 獨(dú)立模式:限流服務(wù)作為單獨(dú)的server進(jìn)行部署,如下圖所示,所有的應(yīng)用都向單獨(dú)部署的token-server進(jìn)行獲取token,這種模式適用于跨服務(wù)之間的全局限流,比如下面圖中,A和B都會(huì)去token-server去拿,這個(gè)場(chǎng)景一般來(lái)說(shuō)比較少,更多的還是服務(wù)內(nèi)集群的限流比較多。
- 內(nèi)嵌模式:在內(nèi)嵌模式下,我們會(huì)把server部署到我們應(yīng)用實(shí)例中,我們也可以通過(guò)接口轉(zhuǎn)換我們的server-client身份,當(dāng)然我們可以自己引入一些zk的一些邏輯設(shè)置讓我們的leader去當(dāng)server,機(jī)器掛了也可以自動(dòng)切換。這種比較適合同一個(gè)服務(wù)集群之間的限流,靈活性比較好,但是要注意的是大量的token-server的訪問(wèn)也有可能影響我們自己的機(jī)器。
當(dāng)然sentinel也有一些兜底的策略,如果token-server掛了我們可以退化成我們單機(jī)限流的模式,不會(huì)影響我們正常的服務(wù)。
實(shí)戰(zhàn)
我們上面已經(jīng)介紹了很多限流的工具,但是很多同學(xué)對(duì)怎么去限流仍然比較迷惑。我們?nèi)绻麑?duì)一個(gè)場(chǎng)景或者一個(gè)資源做限流的話有下面幾個(gè)點(diǎn)需要確認(rèn)一下:
- 什么地方去做限流
- 限多少流
- 怎么去選擇工具
什么地方去做限流
這個(gè)問(wèn)題比較復(fù)雜,很多公司以及很多團(tuán)隊(duì)的做法都不相同,在美團(tuán)的時(shí)候搞了一波SOA,那個(gè)時(shí)候我記得所有的服務(wù)所有的接口都需要做限流,叫每個(gè)團(tuán)隊(duì)去給接口評(píng)估一個(gè)合理的QPS上限,這樣做理論上來(lái)說(shuō)是對(duì)的,我們每個(gè)接口都應(yīng)該給與一個(gè)上限,防止把整體系統(tǒng)拖垮,但是這樣做的成本是非常之高的,所以大部分公司還是選擇性的去做限流。
首先我們需要確定一些核心的接口,比如電商系統(tǒng)中的下單,支付,如果流量過(guò)大那么電商系統(tǒng)中的路徑就有問(wèn)題,比如像對(duì)賬這種邊緣的接口(不影響核心路徑),我們可以不設(shè)置限流。
其次我們不一定只在接口層才做限流,很多時(shí)候我們直接在網(wǎng)關(guān)層把限流做了,防止流量進(jìn)一步滲透到核心系統(tǒng)中。當(dāng)然前端也能做限流,當(dāng)前端捕獲到限流的錯(cuò)誤碼之后,前端可以提示等待信息,這個(gè)其實(shí)也算是限流的一部分。其實(shí)當(dāng)限流越在下游觸發(fā)我們的資源的浪費(fèi)就越大,因?yàn)樵谙掠蜗蘖髦吧嫌我呀?jīng)做了很多工作了,如果這時(shí)候觸發(fā)限流那么之前的工作就會(huì)白費(fèi),如果涉及到一些回滾的工作還會(huì)加大我們的負(fù)擔(dān),所以對(duì)于限流來(lái)說(shuō)應(yīng)該是越上層觸發(fā)越好。
限多少流
限多少流這個(gè)問(wèn)題大部分的時(shí)候可能就是一個(gè)歷史經(jīng)驗(yàn)值,我們可以通過(guò)日常的qps監(jiān)控圖,然后再在這個(gè)接觸上加一點(diǎn)冗余的QPS可能這個(gè)就是我們的限流了。但是有一個(gè)場(chǎng)景需要注意,那就是大促(這里指的是電商系統(tǒng)里面的場(chǎng)景,其他系統(tǒng)類比流量較高的場(chǎng)景)的時(shí)候,我們系統(tǒng)的流量就會(huì)突增,再也不是我們?nèi)粘5腝PS了,這種情況下,往往需要我們?cè)诖蟠僦敖o我們系統(tǒng)進(jìn)行全鏈路壓測(cè),壓測(cè)出一個(gè)合理的上限,然后限流就基于這個(gè)上限去設(shè)置。
怎么去選擇工具
一般來(lái)說(shuō)大一點(diǎn)的互聯(lián)網(wǎng)公司都有自己的統(tǒng)一限流的工具這里直接采用就好。對(duì)于其他情況的話,如果沒(méi)有集群限流或者熔斷這些需求,我個(gè)人覺(jué)得選擇RateLimter是一個(gè)比較不錯(cuò)的選擇,應(yīng)該其使用比較簡(jiǎn)單,基本沒(méi)有學(xué)習(xí)成本,如果有其他的一些需求我個(gè)人覺(jué)得選擇sentinel,至于hytrix的話我個(gè)人不推薦使用,因?yàn)檫@個(gè)已經(jīng)不再維護(hù)了。
總結(jié)
限流雖然只有兩個(gè)字,但是真正要理解限流,做好限流,是一件非常不容易的事,對(duì)于我個(gè)人而已,這篇文章也只是一些淺薄的見(jiàn)識(shí),如果大家有什么更好的意見(jiàn)可以關(guān)注我的公眾號(hào)留言進(jìn)行討論。