前段時間不少人最想作的事多是看劉德華演唱會。是的,劉德華計劃於2018年的12月15日至2019年的1月3日在香港連唱20場。java
他的演唱會火爆到什麼程度呢?有個女粉絲花了600美圓買了張只需580港幣的門票,並且仍是從美國飛到HK。爲何人家願意多花好幾倍的錢去買呢,是由於演唱會門票有限,並且這多是劉德華最後一次演唱會,不少人也是爲了圓兒時的夢不惜找各類渠道,各類加價,只爲了一睹天王的風采。nginx
主辦方只賣固定數量的門票並非爲了讓別人多花錢去買黃牛票,而是場館只能容納指定數量的人,爲了限制場館人流量,構建一個安全的環境。算法
同理,軟件應用系統也是如此。一臺服務器的處理能力有限,若是對請求不加限制最後形成對後果是響應緩慢,甚至形成服務宕機,因此瞭解限流並在應用系統中對請求限流頗有必要。數據庫
1、什麼是服務限流緩存
什麼是服務限流呢?限流即限制併發量,限制某一段時間只有指定數量的請求進入後臺服務器,遇到流量高峯期或者流量突增時,把流量速率限制在系統所能接受的合理範圍以內,不至於讓系統被高流量擊垮。限流與緩存、降級統稱爲分佈式系統的三大利器,最終目的都是用來保護系統穩定運行。tomcat
2、如何實現服務限流安全
怎麼實現服務限流呢?開發過程當中或多或少會接觸到服務限流,好比tomcat限制最大鏈接數、數據庫鏈接池限制鏈接數、nginx限制ip訪問數、秒殺、搶購等。這些都是經過限制一個時間窗口內的請求數,當達到設置的最大請求數後,會讓後續請求進入等待隊列或直接拒絕,防止系統過載。這些限流是怎麼作到的呢,或者說限流主要有哪些方式呢?不少人把限流概括成4種場景,其實也不外乎這4種場景,它們分別是:服務器
1)限制總併發數或請求數併發
從系統層面維護一個計數器,每來一個請求就將計數器加1,請求處理完後將計數器減1,當計數器大於設定的閥值時,拒絕請求或將請求放入等待隊列。這是最簡單粗暴的限流方式,實現起來比較簡單,但它有個很明顯的缺陷是若是某一時間段系統受到惡意攻擊(即突發請求),若併發數大於閥值時,後續有效的請求都會被拒絕,於是這種方法在現實中的使用並不常見,實現以下:框架
try { if(holder.incrementAndGet() > limit) { //refuse-business:拒絕請求 } else { //do-business:業務處理 } } finally { holder.decrementAndGet(); }
2)限制接口的總併發或請求數
針對每一個接口維護一個計數器,每來一個請求就將對應接口的計數器加1,請求處理完後將計數器減1,當計數器大於設定的閥值時,拒絕請求或將請求放入等待隊列。這種方式較之第一種稍複雜些,若是要用aop去實現的話,則須要定義多個切面,維護成本也隨之加大,它雖然不能應對接口受到惡意攻擊(即突發請求),但較之第一種方式有個好處是,即便遭到攻擊,也只會形成某個接口不可用,影響面小不少。實現同場景1。
3)限制接口每秒的請求數
這種方式和第二種很類似,只是它把時間窗口縮小到1秒,故而影響範圍更小,實現以下:
// 存放令牌的緩存,失效時間爲1s,緩存的key爲當前時間秒值,value爲這一秒對應的請求數,以當前時間秒值做爲key的好處是方 // 便統計一秒產生的請求總數,且下一秒到來時,數據會被自動清空 LoadingCache<Long, AtomicLong> tokenCache = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.SECONDS).build(new CacheLoader<Long, AtomicLong>() { @Override public AtomicLong load(Long seconds) throws Exception { return new AtomicLong(0); } }); // 每秒最大請求數 long limit = 200; while(true) { Long currentSeconds = System.currentTimeMillis() / 1000; if(tokenCache.get(currentSeconds).incrementAndGet() > limit) { log.info("該請求被限流了"); continue; } //do-business }
4)平滑限流接口的請求數
平滑限流接口是爲了對突發請求進行整形,以平均速率處理請求,故而能夠應對突發請求,也是目前用的較多的限流方式。其分爲平滑突發限流(SmoothBursty)、平滑預熱限流(SmoothWarmingUp)兩種模式,限流算法有令牌桶法、漏桶法。Google對Guava工具包提供了令牌桶的算法實現,使用起來也很簡單、方便,下面會介紹令牌桶法、漏桶法。Guava實現限流以下:
RateLimiter limiter = RateLimiter.create(2); System.out.println(limiter.acquire());
4、限流算法
1)令牌桶法
令牌桶法是以固定速率往桶裏添加令牌,當令牌數達到上限時,便會丟棄或拒絕,其流程圖以下:
2)漏桶法
漏桶法容許以任意速率流入桶內,但流出速率是恆定的,桶滿了則丟棄令牌,桶內沒有令牌時則阻塞,其流程圖以下:
因爲令牌桶算法是以恆定速率流入桶中,令牌能夠以任意速率流出,於是只需調整令牌流入速率即可解決流量突發狀況,同時也支持預先消費,而漏桶算法是令牌以任意速率流入桶中,以恆定速率流出,從而不能解決流量突發狀況(如經常使用的隊列),如此對比令牌桶法相對漏桶法更優,但業務上不能一味的追求使用令牌桶法去實現,主要還需根據業務需求來判定使用那個限流算法。
5、總結
限流作爲保護系統的一種經常使用方式,以限制系統的輸入和輸出流量已達到保護系統,業界也提供了不少現有的框架可直接使用,如jdk提供的Semaphore、google提供的Guava、Netflix提供的hystrix等,Semaphore是最簡單粗暴的,直接使用計數器來控制。guava則採用了令牌桶法,能夠平滑的進行限流。hystrix則提供了多種限流策略,使用起來更靈活,同時使用複雜度相對大一些,後續會介紹Semaphore、Guava、hystrix的實現原理。