我是如何實現限流的?

我是3y,一年CRUD經驗用十年的markdown程序員👨🏻‍💻常年被譽爲職業八股文選手

今天繼續來更新austin項目的內容,主要講講限流這塊

01、爲什麼AUSTIN項目需要限流

衆所周知,服務器能處理的請求數是有限的,如果請求量特別大,我們就可能需要做限流。

限流處理的姿勢:要麼就讓請求等待,要麼就把請求給扔了

從系統架構來看,我們的統一處理入口austin-api接入層上,austin-api接入層做完簡單的參數校驗以及參數拼接後,就將請求轉發到消息隊列上了

按正常來說,因爲接了消息隊列且接入層沒有什麼耗時的操作,那對外的接口壓力不大的。

沒錯的,austin要接入限流也並不是在austin-api接入層上做,是在austin-handler消息處理下發層。austin-handler消息處理下發層我們是用線程池去隔離不同的消息渠道不同的消息類型

在系統本身上其實沒有性能相關的問題,但我們下發的渠道可能就需要我們去控制調用的速率

騰訊雲短信默認限制3000次/秒調用下發接口

釘釘渠道對應用消息和羣機器人消息都有接口調用的限制

....

在保證下發速度的前提下,爲了讓業務方所下發的消息其用戶能正常接收到和下游渠道的穩定性,我們需要給某些渠道進行限流

於是在這個背景下,我目前定義了兩種限流策略:

1、按照請求數限流

2、按照下發用戶數限流

02、如何實現限流?

想要實現限流,擺在我們面前有兩個選擇:

1、單機限流

2、分佈式限流

咋一看,線上不可能只部署一臺機器去發送整個公司的消息推送的,我們的系統應用在線上環境絕對是集羣部署的,那肯定就需要上分佈式限流了,對吧?

但實際上分佈式限流實現並不簡單,目前分佈式限流的方案一般藉助兩個中間件

1、Redis

2、Sentinel

我們可能會用Redis的setnx/incrby+expire命令(從而實現計數器、令牌桶限流)/zset數據結構(從而實現滑動窗口限流)

Redis實現的限流想要比較準確,無論是哪種方式,都要依靠lua腳本

而Sentinel支持單機限流和分佈式限流,Sentinel分佈式限流需要部署Token服務器

對於分佈式限流而言,不管用哪一種方案,使用的成本和技術挑戰都是比較大的。

如果用單機限流的話,那就簡單得多了,省事直接用Guava包下的RateLimiter就完了。缺點就在於:它只能控制單機的限流,如果發生了服務器擴容和縮容,它是感知不到的

有的人就給出了方案:那我用Zookeeper監聽服務器的數量不就好了嗎。理論上確實是這樣的:每臺機器限流值=限流總值/服務器數量

不過這又要去依賴Zookeeper,Zookeeper集羣本身也有一堆狀態相關的問題。

我是怎麼實現的?單機限流一把梭

03、代碼設計

從上面的描述可以看到,austin的限流我是要做在具體渠道上的,根據現有的代碼設計我要的就是在各個的Handler上寫上限流的代碼。

我本身就設計了BaseHandler抽象類作爲模板方法設計模式,此次限流的設計我的是:

1、將flowControl定義爲abstract抽象方法,子類渠道去實現限流的代碼

2、子類在初始化的時候定義限流參數,BaseHandler父類根據限流參數統一實現限流的邏輯

我選擇了第二種方式,主要是我認爲對於各個渠道而言,只是限流值是不同的,限流的邏輯應該都是一樣的,沒必要在每個子類上實現類似的邏輯。

而限流的邏輯就比較簡單了,主要就使用RateLimit提供的易用API實現

image-20220420171918482

沒錯,限流值的大小我是配置在apollo分佈式配置中心的。假設以後真的要擴縮容了,那到時候提前把分佈式配置中心的值給改掉,也能解決一部分的問題。

04、總結

扯了半天,原來就用了Guava包的RateLimit實現了單機限流,就這麼簡單,只是把限流值配置在分佈式配置中心上而已。

很多時候,設計簡單的代碼可能實現並不完美,並不智能,並不優雅,但它付出的代價往往是最小的。

雖說如此,如果大家想要了解Redis+lua實現的同學可以fetch下austin最新的代碼,就我寫文章這段時間裏,已經有老哥提了pull requestRedis+lua實現了滑動窗口去重的功能了,本質上是一樣的。我已經merge到master分支了。

austin消息推送平臺項目源碼Gitee鏈接:gitee.com/austin

austin消息推送平臺項目源碼GitHub鏈接:github.com/austin

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章