楔子
"限流"這種事情即使在生活中也很常見,比如我們銀行辦理業務,銀行不可能給去的所有人同時服務,因為櫃台就那么幾個。所以可能一次只給5個人辦理業務,其他的人只能在后面排隊;再比如打飯等等,也是一樣的道理。因為能提供服務的數量有限,所以必須要通過限流的方式。
在程序的層面上也是一樣的,如果我們的系統只能支持10萬人同時在線購物,但是某一天突然來了100萬個用戶,那么后果顯然是服務器直接癱瘓。因此只能讓"限流"的功能來維護,先讓一部分用戶進行購物,其它的人進行排隊,這樣就等保證整個系統進行運轉了。
這里提一下微博,微博因為哪個明星出軌了,或者哪個明星戀愛官宣了,導致服務器掛掉不止一次了。這里也是因為人數驟增導致的,當然這只是表層的原因,關於背后詳細的架構、以及為什么這種情況一出現微博就又可能會掛掉的原因,我可能會在其它的系列中說,這里先不談,今天主要是談限流。
這里立一個flag,要是哪天所有人都以為還是單身的胡歌在微博上突然官宣:"大家好,這是我的妻子xxx,我們昨天領證了,希望得到大家的祝福。",然后再附帶兩人結婚照和結婚證,我跟你說微博必癱瘓,話就撂這了。
Redis如何實現限流功能
關於限流所用的算法有兩個:漏桶算法、令牌算法。
漏桶算法
漏桶算法的靈感源於漏斗,如下圖所示:
首先,如果讓你實現一個限流的算法你要怎么做呢?我們可以規定一個時間,比如60s,在60s之內只能處理100個請求,如果超過了100個,那么就將多余的請求丟棄掉。但是這樣存在一個問題:如果在10s內請求就已經100個,因此剩余的50s只能把再來的請求給丟棄掉。但是這100個請求又花了10s中就全部處理完了,那么剩余的40s做什么?顯然這樣做就存在這資源浪費的情況。於是可能有人想到使用隊列的方式,設置隊列的容量為100,任務處理完了就出隊,然后等待處理的入隊,這樣就保證了資源的利用率,恭喜你,漏桶算法就是這么做的。
說實話從漏斗本身上想,也能猜出使用的是隊列,一邊進一邊出。無論漏斗上面的水流有多少,漏斗下面的水都是均勻流出的。如果上面的水流量大於下面流出的水流量的話,那么漏斗會慢慢變滿;反之,漏斗永遠不會被裝滿,並一直流出。
漏桶算法的實現步驟是:先聲明一個隊列保存請求,這個隊列相當於漏斗,當隊列容量滿了之后,就放棄新來的請求。然后執行的話則是通過聲明另一個線程定期從隊列中獲取一個或多個任務執行,這樣就實現了漏桶算法。
漏桶算法可以在編程語言這一層實現。
令牌算法
令牌算法指的是有一個程序以某種恆定的速度生成令牌,並存入令牌桶中,而每個請求需要先獲取令牌才能執行,如果沒有獲取到令牌的話則放棄執行。如下圖所示:
這種令牌算法,我們也可以使用Python的threading模塊來實現。
更好的限流方案
在Redis4.0,已經為我們實現了限流功能,並提供了原子的限流指令,再加上Redis這個天生的分布式程序就可以完美地實現限流了。
實現限流需要使用Redis提供的Redis-Cell模塊,該模塊使用的便是漏桶算法,使用起來也很簡單,通過cl.throttle即可,但是我們需要提前安裝。
前往:https://github.com/brandur/redis-cell/releases
下載對應系統的安裝包,有源碼編譯和安裝包安裝兩種方式,源碼編譯需要rust環境、比較復雜,所以推薦安裝包安裝。安裝之后,直接解壓即可,里面會有一個libredis_cell.so
文件,執行redis-cli通過module load加載即可。
> cl.throttle mylimit 15 30 60
1)(integer)0 # 0 表示獲取成功,1 表示拒絕
2)(integer)15 # 漏斗容量
3)(integer)14 # 漏斗剩余容量
4)(integer)-1 # 被拒絕之后,多長時間之后再試(單位:秒)-1 表示無需重試
5)(integer)2 # 多久之后漏斗完全空出來
其中 15 為漏斗的容量,30 / 60s 為漏斗的速率。
通過Redis-Cell,我們則可以實現分布式原子級別的限流,更詳細的使用可以查看官網。