大量新老項目接入,服務限流如何排除差異快速落地?
一、背景
1、場景
某一天有一個項目服務突然出現(xiàn)異常,我們定位到的原因是有大量的突發(fā)流量進來,那么我們會先采取被動的臨時手段去處理當前故障,接著上線Nginx的限流功能進行快速止損,防止二次故障。但是Nginx的限流功能是比較粗糙的,所以我們有一個更好的長期措施,即項目接入限流功能,并實現(xiàn)按維度進行精細化的限流,化被動解決為主動防御、主動治理。對于這一個項目來說,做到這一步應該是比較好的效果。
那么我們能否更進一步?更進一步我們需要考慮經(jīng)驗積累、通用性、整體效益這三個方面。
- 經(jīng)驗積累:有什么經(jīng)驗總結可以被其他項目借鑒?
- 通用性:解決方案其他項目可以直接使用嗎?
- 整體效益:可以降低團隊的整體成本嗎?
2、問題與目標
我們的方案設計需要考慮不同項目之間的差異性,因為我們需要接入的項目可能有十幾個甚至更多,不同項目的差異性會體現(xiàn)在開發(fā)語言、請求類型、生命周期、部署環(huán)境、鏈路節(jié)點等方面。
其中我們特別需要注意的是生命周期,有的項目可能是十幾年前就存在的老項目,有的可能是最近兩三年的較新的項目,還有的可能是以后會建立的全新項目,我們的方案需要能夠同時匹配這三種項目,以及需要抹平其他的幾種差異。
此外,我們的方案也有一些其他的期望目標,比如低成本、高效率、高質量,以及專業(yè)性、穩(wěn)定性、可擴展性、高性能。
- 低成本:接入所需的成本是比較低的,包括開發(fā)、運維、硬件等成本。
- 高效率:能夠快速完成接入落地工作。
- 高質量:提供了整套限流的解決方案,接入方使用前后都能夠得到有效的技術支持和指引。
其他幾點則比較好理解,那么擺在我們眼前的問題就是項目的數(shù)量多,差異性大,期望目標和要求也多,那么我們應該如何設計這個方案?
3、協(xié)作方式
在介紹具體的技術方案之前,我簡單講述一下我們的協(xié)作方式。
在公司內(nèi)部推廣一個技術落地,我們通常會采用單項目的方式進行,即單獨某個項目了解需求、設計、開發(fā),在本項目落地之后再逐步推廣到其他項目,這種方式雖然沒有問題,但是存在幾個缺點:
- 需求的全面性:不能充分兼顧到其他項目的需求,方案設計上可能會有缺漏,導致后期推廣困難,因為只考慮到了本項目的需求和場景。
- 成本負擔:成本由單一項目承擔,可能會影響到項目組原本的工作,對項目本身也會有比較大的影響。
基于以上情況,我們設計了一個專業(yè)組機制,希望能夠讓專業(yè)的人做專業(yè)的事,工作流程如下:
首先從多個有需求的項目組里招募感興趣或者有經(jīng)驗的成員加入專業(yè)組,比如圖示里從項目組1、項目組2和項目組3里各抽出一個人成立專業(yè)組,然后針對這幾個項目設計總體方案,以滿足他們的需求,在項目落地之后,再逐步推廣到其他項目。
與單項目推進相比,專業(yè)組機制的優(yōu)點如下:
- 充分兼顧到了典型場景的需求。因為我們會從原先的三個項目組里去對接需求,那么這三個項目組的場景是比較明確的。
- 成本由多個項目分攤。每個項目只需要出1~2個人即可,對項目組原先的工作影響程度較低。
- 專業(yè)組的進出機制可以充分發(fā)揮成員的技術積累優(yōu)勢和主觀能動性。能夠進專業(yè)組的成員一般是在專業(yè)問題上比較有技術積累的,或者是對該問題很感興趣,會主動研究問題。
在解決現(xiàn)有功能上,專業(yè)組在整個過程中發(fā)揮了很大的作用。
二、技術方案
下面我們介紹具體的技術方案。
1、限流實現(xiàn)層
首先的問題是我們的限流應該做在哪一層?一般來說我們可以在應用層限流,也就是在API服務節(jié)點再增加一個web中間件,在請求進入API服務時判斷請求是否被限流,這是比較常見的方式,對于單個項目而言成本也是最低的。
除了應用層實現(xiàn)限流之外,還有接入層實現(xiàn)限流的做法。在原先的LB到API節(jié)點之間,我們可以增加一個網(wǎng)關層,將限流功能做在網(wǎng)關層。
兩種方法相比較,應用層限流有以下缺陷:
- 異常流量依然落在API服務。如果有大量的突發(fā)流量進來,還是會把壓力落在API服務,那么效果相對而言是比較差的。
- 邏輯耦合,無法獨立變更限流功能。
- 職責不明確,增加服務復雜性。
- 多項目情況下難以復用,可行性低,無法達到我們的需求。比如我們前面提到的差異性,不同項目的開發(fā)語言、部署方式等不一樣,如果我們在應用層實現(xiàn)限流,就需要針對每一個項目單獨適配,那么成本是比較高的。
基于以上情況我們選擇了接入層實現(xiàn)限流。在接入層實現(xiàn),我們就需要一個網(wǎng)關作為統(tǒng)一的記錄層。
2、Kong網(wǎng)關
按照官方的說法,Kong網(wǎng)關是一個輕量、快速、靈活的云原生API網(wǎng)關,Kong是基于OpenResty和Nginx實現(xiàn)的。我們在解決這一個問題的情況下,為什么要選擇Kong?也是基于我們常見的高性能、高可用、靈活、易擴展等方面。其中我們特別在意以下兩點:
- 一是輕量靈活,在最輕量部署的情況下,僅需一個主進程和一個yaml配置文件。
- 二是易擴展,Kong的插件機制可以實現(xiàn)在請求生命周期的各階段執(zhí)行自定義邏輯,也就是我們可以做很多自己需要的工作。
3、Kong插件
Kong的插件機制可以支持多種語言,如Golang、Lua、Python、JS。我們選擇了Lua和Golang作為我們的開發(fā)插件。兩種之間的區(qū)別是Lua插件在Kong進程中是直接執(zhí)行的,也就是它和Kong是同一個進程,而每一個Go插件是一個單獨的插件,和Kong主進程之間的通信需要通過IPC進行。
Lua和Golang相比較,在團隊技術棧匹配度、生態(tài)、工程化難度、開發(fā)維護成本上,Golang都占有比較明顯的優(yōu)勢,而在性能方面Lua會更高。因為Golang在插件每次執(zhí)行的時候都需要進行IPC通信,在IPC通信次數(shù)較高的情況下,性能會受到較大影響。但是我們在實踐過程中發(fā)現(xiàn),在現(xiàn)有場景下,我們的IPC通信次數(shù)相對較少,對性能的影響也比較低。所以總體上我們是優(yōu)先使用Golang開發(fā)插件,如果Golang實現(xiàn)不了再退而求其次用Lua進行開發(fā)。
4、插件模塊化
前面我們通過把Kong作為接入層解決了不同項目在環(huán)境上的差異性,接著我們可以使用插件機制解決不同項目在需求上的差異性。
我們可以將一個項目的限流需求分為業(yè)務需求模塊和限流功能模塊兩個部分。
- 業(yè)務需求模塊是比較頻繁迭代的,這一方面的需求包括解析用戶名等,根據(jù)路徑、IP、請求方法等不同,每個項目也會有不同的需求。
- 限流功能模塊相對而言比較穩(wěn)定,比如我們需要進行短連接的頻率限流、并發(fā)數(shù)限流等。
在具體的限流算法的實現(xiàn)之上,還有一個策略的應用,即對于同一個限流方法,比如令牌桶,我們可能會設計不同的策略進行應用。
基于以上模塊劃分,我們在實際開發(fā)時分了三層,從下到上依次是限流算法層、限流插件層、業(yè)務插件層。
1)限流算法層
限流算法層主要是SDK的形式,因為我們的限流場景比較多,算法也不同,所以我們對于每一個不同的場景都會單獨開發(fā)SDK,比如短連接限制頻率的場景可能會有令牌桶或時間窗口等,短連接并發(fā)數(shù)、長連接也有具體的實現(xiàn)。
需要特別注意的是客戶端節(jié)流的場景,我們在實際應用時發(fā)現(xiàn),該場景除了接入Kong服務端性能之外,客戶端請求時也需要進行限流??蛻舳讼蘖髦傅氖强蛻舳酥鲃影l(fā)起請求調(diào)用第三方頻率時,我們也希望能夠控制頻率,保護第三方服務。
限流算法的SDK是以代碼嵌入的形式嵌入到限流插件層。
2)限流插件層
限流插件層會調(diào)用SDK,針對不同的場景需要設置不同的策略,比如我們的令牌桶限制短連接頻率,一個插件可能會實現(xiàn)令牌預知功能,而另一個插件可能會做限流額度、動態(tài)調(diào)控等,也就是已經(jīng)往業(yè)務層走了一步。
3)業(yè)務插件層
業(yè)務插件層會根據(jù)項目組不同的需求制定單獨的業(yè)務場景,比如不同的項目組可能會有不同的限流維度進行解析,需要項目組自己適配等。
如配置監(jiān)聽,以及業(yè)務組的其他業(yè)務定制功能,也可以做在這一層。
我們的分層主要分為三層,實際Kong執(zhí)行會有兩個插件,分別是業(yè)務插件和限流插件,這里的插件指的是Kong本身自帶的執(zhí)行插件。
接下來以長輪詢場景為例,講述需要限制接入的客戶端連接數(shù)場景。
首先請求進入Kong網(wǎng)關時,在業(yè)務插件我們會解析它的限流維度,比如user、path、method、ip,然后把這些維度生成一個字符串限流KEY,它可能是一個比較長的字符串。
接著它會匹配特定的限流策略,如果匹配不到則會進入兜底策略,把這些信息生成限流的協(xié)議數(shù)據(jù)。業(yè)務插件的配置會記錄請求的維度,比如path、method等,匹配到了限流策略之后,就會去生成它的限流協(xié)議,限流協(xié)議里會設置請求最大并發(fā)數(shù)、請求超時時間等。接著業(yè)務插件會把這些信息組裝成一個協(xié)議,協(xié)議的格式如圖右下角。
然后把這些數(shù)據(jù)通過Kong的Context機制傳遞到限流插件,限流插件則會解析該協(xié)議數(shù)據(jù),然后執(zhí)行限流邏輯判斷,來判斷這個請求應該被限流還是透傳到上游服務。
5、分工
項目組的需求,如果已有的插件可滿足,則直接使用;如果不滿足,可定制自己的業(yè)務插件。定制插件時項目組只需要關注項目需要的業(yè)務邏輯即可,相關的生態(tài)已由專業(yè)組提供,專業(yè)組負責所有限流插件、部分通用業(yè)務插件的開發(fā),網(wǎng)關、插件公用功能開發(fā),搭建安全性、穩(wěn)定性保障,以及不同場景下的限流算法的設計實現(xiàn)等工作。插件開發(fā)完成后提交到插件庫,后續(xù)其他項目組若有相同的項目,也可以直接使用。
6、分發(fā)與使用
接下來我們簡單了解一下插件的分發(fā)與使用機制。
首先插件開發(fā)完成之后,我們會在GitLab上給它打一個tag,然后觸發(fā)一個CI流程。CI流程做的工作主要是編譯、寫入?yún)?shù)信息、壓縮打包,把插件打包成一個tag壓縮文件,將其上傳到文件服務,再發(fā)布到git release界面,那么項目組使用時就可以在release界面看到我們可以使用的插件及其功能。
而項目組在具體使用的時候,需要Fork部署倉庫,然后設置需要使用的多個插件及版本。接著再去觸發(fā)CI流程,我們會把插件重新下載回來,并且進行解析、解包等工作,然后把這些插件和它們的信息寫入到Kong里,將其構建、推送成為一個完整的鏡像,有了鏡像之后,我們就可以在部署平臺上進行部署,那么項目組就完成了Kong和插件的使用。
7、接入方式
接下來簡單介紹項目組實際的接入方式。前面我們提到項目組的差異比較多,因此我們分了5種情況:
1)如果是一個全新的項目,則直接接入。
2)現(xiàn)存項目,但是沒有網(wǎng)關,那么主要考慮業(yè)務場景,整體評估之后再進行接入。
3)現(xiàn)存項目,且已有Kong網(wǎng)關,這種情況下直接安裝和使用我們的限流插件即可。因為我們開發(fā)的插件也是Kong的標準插件,項目組使用限流插件時只需要對接其限流協(xié)議。
4)現(xiàn)存項目,且已有其他網(wǎng)關,那么就不能使用Kong網(wǎng)關了,直接對接限流算法的SDK即可。
5)客戶端限流的情況下不需要Kong網(wǎng)關,也是直接對接客戶端限流的SDK即可。
8、整體架構
該方案的整體結構是比較簡單的,便于適配不同的項目。
總體而言就是在LB和API節(jié)點之間加了一個Kong網(wǎng)關,網(wǎng)關方面首先我們會插入一個tracing插件,這個插件內(nèi)嵌了Open-Tracing的實現(xiàn),接著項目組根據(jù)自身需求選擇業(yè)務插件和限流插件。
業(yè)務插件有一個功能是動態(tài)監(jiān)聽限流配置,需要外部的配置中心實現(xiàn)。動態(tài)監(jiān)聽主要是考慮到當有異常流量進來時,我們可能需要動態(tài)調(diào)整額度,比如場景可能是原先給該項目定的額度已經(jīng)不能滿足其需求了,該項目最近的用戶量猛增,那么在這種情況下,我們不能限制它的流量,因此需要將額度調(diào)大,避免對業(yè)務造成不好的影響。
限流插件根據(jù)不同限流算法的實現(xiàn),我們也有不同的依賴,比如我們在大部分情況下都會做分布式限流,我們選擇通過Redis加一個Lua腳本實現(xiàn)分布式限流,那么這種情況下依賴還需要增加一個Redis。也有少部分情況下它可能是作為一個本地限流,需要根據(jù)項目組的具體實現(xiàn)。
三、實施方案
實施方案指的是一個項目組從0接觸到整個方案完成上線的過程,前期包括需求、評估和開發(fā)這幾個步驟。
1、需求
在需求階段,項目組根據(jù)我們提供的模板提issue,里面會包含以下內(nèi)容:
- 限流場景、類型
- 限流維度、策略
- 項目組需要特化的業(yè)務需求
- 現(xiàn)有插件能否滿足項目組的需求
2、評估
接著進行整體的評估工作,評估的方面包括:
- 開發(fā)、維護、硬件等成本
- 部署架構
- 性能、鏈路的影響
3、開發(fā)(若需要)
如果我們現(xiàn)有的插件無法滿足項目組的需要,那么就需要進行開發(fā)工作。對于開發(fā)工作,我們有一個比較詳細的指南,因此上手開發(fā)的成本比較低。項目組需要了解以下幾個方面:
- 本地環(huán)境部署:我們提供了一個docker-compose環(huán)境可以一鍵拉起本地的開發(fā)環(huán)境。
- 相關知識快速了解:包括Kong的一些概念等。
- 完善的開發(fā)文檔:包括具體插件如何寫之類的,簡單來說就是我們提供了一個手把手教會項目組進行開發(fā)的文檔。
接下來是測試、部署與上線的環(huán)節(jié)。
4、測試
在測試環(huán)節(jié)我們的目的是需要保證接入Kong和插件之后的質量。
- 首先需要保證我們的限流功能是符合預期的。
- 原來的API服務的功能回歸之后需要與之前保持一致。
鏡像流量和故障演練對于我們而言是比較重要的。
- 鏡像流量指的是把生產(chǎn)環(huán)境的流量導入到測試環(huán)境,可以提前驗證限流之后我們的流量是否異常。也就是我們需要一個單獨的鏡像流量的環(huán)境,將生產(chǎn)環(huán)境的流量在該環(huán)境里提前進行驗證,因為我們增加了一個新的網(wǎng)關,變更的影響面是比較廣的,而且可能還會遇到奇怪的問題。如果我們提前把生產(chǎn)環(huán)境的流量導入過來,就可以提前發(fā)現(xiàn)這些問題。
- 故障演練也是為了提前發(fā)現(xiàn)可能存在的問題和風險。
關于這兩點,我們內(nèi)部有兩個最佳實踐說明,分別是《如何做好鏡像流量》和《如何做好從場景出發(fā)的故障演練》。
5、部署
接下來是部署環(huán)節(jié)。
- 我們基于內(nèi)部的一致性交付系統(tǒng)實現(xiàn)了模板部署,即不同的部署平臺,比如物理機或公有云、私有云,都可以用一致性交付的平臺實現(xiàn)統(tǒng)一的部署,那么項目組在部署時可以不用考慮太多的細節(jié)問題。
- 其次我們在部署的時候可以一鍵接入監(jiān)控、日志、告警、鏈路等功能。
- 我們部署時有一個完善的部署指南會介紹方方面面的問題,比如我們接入Kong網(wǎng)關之后,對原先的服務拆分問題是如何處理的。
6、上線
最后的步驟是上線,我們也提供了詳細的上線步驟,以及checklist和最重要的灰度、回滾的技術方案。
總體而言我們這幾個環(huán)節(jié)也達到了手把手指引、實現(xiàn)的整個過程。
四、小結
前面我們提到的目標是低成本、高效率、高質量這幾個方面。
1、低成本
1)開發(fā)成本:項目組對接時基本上是0成本,或者只有少量的開發(fā)工作。
2)維護成本:后續(xù)的維護、升級工作都是由專業(yè)組負責的。
3)硬件成本:主要的是Kong的集群部署,根據(jù)項目組本身的流量大小,可以部署不同的副本數(shù)。如果選擇了分布式限流,那么還需要部署一個Redis,我們一般建議部署一個新的Redis,但是為了節(jié)約成本也可以和原先項目已有的Redis共用。
2、高效率
1)接入時間:我們在項目實踐時,最快的接入時間是三天,三天是在項目組沒有自己定制化的需求,不需要開發(fā)的情況下走完我們之前介紹的環(huán)節(jié),即評估、測試、部署、上線。
2)硬件配置:我們有一個推薦配置,也就是根據(jù)我們不同的流量進行單獨測試,以及我們提供了比較完整的性能測試數(shù)據(jù)作為參考。
3)高效部署:網(wǎng)易內(nèi)部的平臺相對完善,借助于我們的CI自動化、配置管理系統(tǒng)、容器云系統(tǒng)和一致性交付系統(tǒng)可以實現(xiàn)高效部署。
4)文檔建設:文檔建設我們是比較完善的,從剛開始接觸限流這套方案到上線的整個過程中各個環(huán)節(jié),我們都有很詳細的文檔指引,遇到問題可以從文檔中尋找,也可以找專業(yè)組的同事了解。
3、高質量
1)技術支持:由專業(yè)組的同事提供技術支撐。
2)多樣場景:我們的插件機制適配了多樣化的限流場景和業(yè)務需求。
3)經(jīng)驗復用:在各個項目對接和使用過程中的經(jīng)驗或優(yōu)化的問題,都可以總結復用,因為我們不同項目使用的是同一套技術體系,便于積累經(jīng)驗。
4)自定擴展:我們雖然定制化了一些插件,以及一些Kong的部署過程,但是在實際開發(fā)時也考慮到了Kong原生態(tài)的兼容性,也就是項目組依然可以使用Kong原生態(tài)提供的豐富功能。
5)運維體系:接入了整套運維設施相關的功能體系,也就是前面我們提到的監(jiān)控、報警、日志等。
6)充分驗證:我們的插件功能和性能得到了充分的測試,上線流程也比較完善,可以保證我們的可靠性。
通過以上環(huán)節(jié)可以保證服務限流方案的低成本、高效率和高質量。
Q&A
Q1:這個限流方案在哪些類型的項目上具有通用性?
A1:這個問題其實回歸到了我們項目的差異性上,總體來說,我們對于不管是普通的http短連接的項目,或者是一些長輪詢的場景以及長連接的場景,我們都有單獨做適配。因為本身Kong對于這幾種請求都可以支持,也就是我們只需要針對這幾種場景開發(fā)對應的插件即可。
Q2:Kong和插件對性能的影響大嗎?
A2:這一點我們在實際設計的時候也考慮過,經(jīng)過實踐發(fā)現(xiàn)對性能的影響是比較小的。性能一方面是對我們的請求耗時的影響,經(jīng)過我們的測試,耗時大概是在5毫秒左右,另外一方面是QPS,因為Kong和插件本身是支持橫向擴展的,以及Kong本身的性能也比較高,所以對QPS的影響是比較小的,我們在測試時最高達到過五六萬QPS,因此基本上不需要過于擔心這個問題。
Q3:如何實現(xiàn)長輪詢連接數(shù)限制?
A3:長輪詢本質是請求并發(fā)數(shù)的一個性質,只不過它是http請求,可能是一個掛起的狀態(tài),需要掛起比如一分鐘。這個場景的實現(xiàn)過程大概如下:Kong本身對插件機制可以保證在請求前和請求后,我們可以插入鉤子函數(shù)。在請求前我們的鉤子函數(shù)會在Redis的Zset里設置一個請求的狀態(tài),那么Zset的值就是該請求的唯一ID,比如它是一個雪花ID,然后分就是請求的過期時間,這個過期時間是當前時間加上請求配置里的TTL(請求的超時時間),兩者加起來就是該請求實際失效的時間。在請求進來時,我們會在Zset里記錄請求的數(shù)據(jù),表示當前的請求并發(fā)數(shù)已經(jīng)加一了。然后在執(zhí)行Redis Lua腳本的時候,它會先剔除那些已經(jīng)過期了的請求數(shù),在請求結束之后,會從Zset里移除該請求的數(shù)據(jù),也就是請求并發(fā)數(shù)減一,這是大致原理。
作者:肖晗網(wǎng)易互娛技術中心數(shù)據(jù)與平臺服務部高級開發(fā)工程師 網(wǎng)易互娛技術中心數(shù)據(jù)與平臺服務部高級開發(fā)工程師,具有多年服務端經(jīng)驗,現(xiàn)專注于CMDB、微服務、圖數(shù)據(jù)庫等方向。