有赞API网关实践
https://tech.youzan.com/api-gateway-in-practice/
一、API網(wǎng)關(guān)簡介
隨著移動互聯(lián)網(wǎng)的興起、開放合作思維的盛行,不同終端和第三方開發(fā)者都需要大量的接入企業(yè)核心業(yè)務(wù)能力,此時各業(yè)務(wù)系統(tǒng)將會面臨同一系列的問題,例如:如何讓調(diào)用方快速接入、如何讓業(yè)務(wù)方安全地對外開放能力,如何應(yīng)對和控制業(yè)務(wù)洪峰調(diào)用等等。于是就誕生了一個隔離企業(yè)內(nèi)部業(yè)務(wù)系統(tǒng)和外部系統(tǒng)調(diào)用的屏障 - API網(wǎng)關(guān),它負責在上層抽象出各業(yè)務(wù)系統(tǒng)需要的通用功能,例如:鑒權(quán)、限流、ACL、降級等。另外隨著近年來微服務(wù)的流行,API網(wǎng)關(guān)已經(jīng)成為一個微服務(wù)架構(gòu)中的標配組件。
二、有贊API網(wǎng)關(guān)簡介
有贊API網(wǎng)關(guān)目前承載著微商城、零售、微小店、餐飲、美業(yè)、AppSDK、部分PC、三方開發(fā)者等多個業(yè)務(wù)的調(diào)用,每天有著億級別的流量。
有贊后端服務(wù)最開始是由PHP搭建,隨著整個技術(shù)體系的升級,后面逐步從PHP遷移到Java體系。在API網(wǎng)關(guān)設(shè)計之初主要支持Dubbo、Http兩種協(xié)議。遷移過程中,我們發(fā)現(xiàn)部分服務(wù)需要通過RPC方式調(diào)用PHP服務(wù),于是我們(公司)基于Dubbo開發(fā)了一個新的框架Nova,兼容Dubbo調(diào)用,同時支持調(diào)用PHP服務(wù)。于是網(wǎng)關(guān)也支持了新的Nova協(xié)議,這樣就有Dubbo、Http、Nova三種協(xié)議。
隨著業(yè)務(wù)的不斷發(fā)展,業(yè)務(wù)服務(wù)化速度加快,網(wǎng)關(guān)面臨各類新的需求。例如回調(diào)類型的API接入,這種API不需要鑒權(quán),只需要一個限流服務(wù),路由到后端服務(wù)即可;另外還有參數(shù)、返回值的轉(zhuǎn)換需求也不斷到來,這期間我們快速迭代滿足新的需求。而在這個過程中我們也走了很多彎路,例如API的規(guī)范,在最開始規(guī)范意識比較籠統(tǒng),導致返回值在對外暴露時出現(xiàn)了不統(tǒng)一的情況,后續(xù)做SDK自動化的時候比較棘手,經(jīng)過不斷的約束開發(fā)者,最終做到了統(tǒng)一。
三、架構(gòu)與設(shè)計
1. 網(wǎng)關(guān)架構(gòu)
部署架構(gòu)圖網(wǎng)關(guān)的調(diào)用方主要包括微商城、微小店、零售等App應(yīng)用,以及三方開發(fā)者和部分PC業(yè)務(wù)。通過LVS做負載均衡,后端Tengine實現(xiàn)反向代理,網(wǎng)關(guān)應(yīng)用調(diào)用到實際的業(yè)務(wù)集群
應(yīng)用架構(gòu)圖網(wǎng)關(guān)核心由Pipe鏈構(gòu)成,每個Pipe負責一塊功能,同時使用緩存、異步等特性提升并發(fā)及性能
線程模型圖網(wǎng)關(guān)采用Jetty部署,調(diào)用采用Http協(xié)議,請求由容器線程池處理(容器開啟了Servlet3.0異步,提升了較大的吞吐量),之后分發(fā)到應(yīng)用線程池異步處理。應(yīng)用線程池在設(shè)計之初考慮不同的任務(wù)執(zhí)行可能會出現(xiàn)耗時不一的情況,所以將任務(wù)分別拆分到不同的線程池,以提高不同類型任務(wù)的并發(fā)度,如圖分為CommonGroup, ExecutionGroup, ResultGroup
CommonGroup執(zhí)行通用任務(wù),ExecutionGroup執(zhí)行多協(xié)議路由及調(diào)用任務(wù),ResultGroup執(zhí)行結(jié)果處理任務(wù)(包含異常)
網(wǎng)關(guān)業(yè)務(wù)生態(tài)圖網(wǎng)關(guān)生態(tài)主要包含控制臺、網(wǎng)關(guān)核心、網(wǎng)關(guān)統(tǒng)計與監(jiān)控
控制臺主要對API生命周期進行管理,以及ACL、流量管控等功能;
網(wǎng)關(guān)核心主要處理API調(diào)用,包含鑒權(quán)、限流、路由、協(xié)議轉(zhuǎn)換等功能;
統(tǒng)計與監(jiān)控模塊主要完成API調(diào)用的統(tǒng)計以及對店鋪、三方的一些報表統(tǒng)計,同時提供監(jiān)控功能和報警功能
2. 網(wǎng)關(guān)核心設(shè)計
2.1 異步
我們使用Jetty容器來部署應(yīng)用,并開啟Servlet3.0的異步特性,由于網(wǎng)關(guān)業(yè)務(wù)本身就是調(diào)用大量業(yè)務(wù)接口,因此IO操作會比較頻繁,使用該特性能較大提升網(wǎng)關(guān)整體并發(fā)能力及吞吐量。另外我們在內(nèi)部處理開啟多組線程池進行異步處理,以異步回調(diào)的方式通知任務(wù)完成,進一步提升并發(fā)量
2.2 二級緩存
為了進一步提升網(wǎng)關(guān)的性能,我們增加了一層分布式緩存(借用Codis實現(xiàn)),將一些不經(jīng)常變更的API元數(shù)據(jù)緩存下來,這樣不僅減少了應(yīng)用和DB的交互次數(shù),還加快了讀取效率。我們同時考慮到Codis在極端情況下存在不穩(wěn)定因素,因此我們在本地再次做了本地緩存,這樣的讀取可以從ms級別降低到ns級別。為了實現(xiàn)多臺機器的本地緩存一致性,我們使用了ZK監(jiān)聽節(jié)點變化來更新各機器本地緩存
2.3 鏈式處理
在設(shè)計網(wǎng)關(guān)的時候,我們采用責任鏈模式來實現(xiàn)網(wǎng)關(guān)的核心處理流程,將每個處理邏輯看成一個Pipe,每個Pipe按照預先設(shè)定的順序先后執(zhí)行,與開源的Zuul 1.x類似,我們也采用了PRPE模式(Pre、Routing、Post、Error),在我們這里Pre分為PrePipe、RateLimitPipe、AuthPipe、AclPipe、FlowSepPipe,這些Pipe對數(shù)據(jù)進行預處理、限流、鑒權(quán)、訪問控制、分流,并將過濾后的Context向下傳遞;Routing分為DubboPipe、HttpPipe,這些Pipe分別處理Dubbo協(xié)議、Http協(xié)議路由及調(diào)用;Post為ResultPipe,處理正常返回值以及統(tǒng)計打點,Error為ErrorPipe,處理異常場景
2.4 線程池隔離
Jetty容器線程池(QTP)負責接收Http請求,之后交由應(yīng)用線程池CommonGroup,ExecutionGroup, ResultGroup,通用的操作將會被放到CommonGroup線程池執(zhí)行,執(zhí)行真實調(diào)用的被放到ExecutionGroup,結(jié)果處理放到ResultGroup。這樣部分Pipe之間線程隔離,通常前置Pipe處理都比較快,所以共享線程池即可,真實調(diào)用通常比較耗時,因此我們放到獨立的線程池,同時結(jié)果處理也存在一些運算,因此也放到獨立線程池
2.5 平滑限流
最早我們采用了簡單的分布式緩存(Codis)計數(shù)實現(xiàn)限流,以IP、API維度構(gòu)建Key進行累加,這種限流方式實現(xiàn)簡單,但是不能做到連續(xù)時間段內(nèi)平滑限流。例如針對某個API每分鐘限流100次,第1秒發(fā)起20次,第二秒發(fā)起30次,第3秒發(fā)起40次,這樣的限流波動比較大,因此我們決定將其改進。經(jīng)過調(diào)研我們最終選擇了令牌桶限流,令牌桶限流相比于漏桶限流能適應(yīng)閑置較長時段后的尖峰調(diào)用,同時消除了簡單計數(shù)器限流帶來的短時間內(nèi)流量不均的問題。目前網(wǎng)關(guān)支持IP、店鋪、API、應(yīng)用ID和三方ID等多個維度的限流,也支持各維度的自由組合限流,可以很容易擴展出新的維度
2.6 熔斷降級
由于我們經(jīng)常遇到調(diào)用后端接口超時,或者異常的情況,后端服務(wù)無法立即恢復,這種情況下再將請求發(fā)到后端已沒有意義。于是我們使用Hystrix進行熔斷降級處理。Hystrix支持線程池和信號量2種模式的隔離方案,網(wǎng)關(guān)的業(yè)務(wù)場景是多API和API分組,每個API都可能路由到不同后端服務(wù),如果我們對API或者API分組做線程池隔離,就會產(chǎn)生大量的線程,所以我們選擇了信號量做隔離。我們?yōu)槊總€API提供一個降級配置,用戶可以選擇自己配置的API在達到多少錯誤率時進行熔斷降級。
引入Hystrix后,Hystrix會對每個API做統(tǒng)計,包括總量、正確率、QPS等指標,同時會產(chǎn)生大量事件,當API很多的時候,這些指標和事件會占用大量內(nèi)存,導致更加頻繁的YoungGC,這對應(yīng)用性能產(chǎn)生了一定的影響,不過整體的收益還是不錯的
另外有贊內(nèi)部也開發(fā)了一個基于Hystrix的服務(wù)熔斷平臺(Tesla),平臺在可視化、易用性、擴展性上面均有較大程度的提升;后續(xù)網(wǎng)關(guān)會考慮熔斷模塊的實現(xiàn)基于服務(wù)熔斷平臺,以提供更好的服務(wù)
2.7 分流
有贊內(nèi)部存在多種協(xié)議類型的后端服務(wù),最原始的服務(wù)是PHP開發(fā),后面逐漸遷移到Java,很早一部分API是由PHP暴露的,后續(xù)為了能做灰度遷移到Java,我們做了分流,將老的PHP接口的流量按照一定的比例分發(fā)到新的Java接口上
3. 控制臺
除了核心功能的調(diào)用外,網(wǎng)關(guān)還需要支持內(nèi)部用戶(下稱業(yè)務(wù)方)快速配置接口暴露給開發(fā)者。 控制臺主要職責包括:快速配置API、一站式測試API、一鍵發(fā)布API,自動化文檔生成,自動化SDK生成
- 快速配置API:這塊我們主要是按照對外、對內(nèi)來進行配置,業(yè)務(wù)方將自己要對外公開的名稱、參數(shù)編輯好,再通過對內(nèi)映射將對外參數(shù)映射到內(nèi)部服務(wù)的接口里面
- 一站式測試API:API配置完成后,為了能讓業(yè)務(wù)方快速測試,我們做了一站式獲取鑒權(quán)值,參數(shù)值自動保存,做到一站式測試
- 一鍵發(fā)布API:在完成配置和測試后,API就可以直接發(fā)布,這個時候選擇對應(yīng)環(huán)境的注冊中心或者服務(wù)域名即可
- 自動化文檔生成:我們針對文檔這塊做了文檔中心,對內(nèi)部用戶,他們只需要到平臺來搜索即可,對外部用戶,可以在有贊云官網(wǎng)查看或者在控制臺直接導出pdf文件給用戶
- 自動化SDK生成:對于開發(fā)者來說,接入一個平臺必然少不了SDK,我們針對多語言做了自動化SDK生成,當用戶的接口發(fā)布成功后,我們會監(jiān)聽到有新的接口,這時會觸發(fā)自動編譯(Java)SDK的模塊,將新接口打包成新版本的壓縮包,供開發(fā)者使用;如果編譯失敗(Java)則不會替換老的壓縮包,我們會發(fā)送報警給相應(yīng)的開發(fā)者,讓其調(diào)整不規(guī)范的地方
4. 數(shù)據(jù)統(tǒng)計
為了讓業(yè)務(wù)方能在上線后了解自己的接口的運行狀況,我們做了API相關(guān)的統(tǒng)計。我們通過在核心模塊里面打日志,利用rsyslog采集數(shù)據(jù)到Kafka,然后從Kafka消費進行統(tǒng)計,之后回流到數(shù)據(jù)庫供在線查詢
除此之外,我們?yōu)槊總€商家做了他們授權(quán)的服務(wù)商調(diào)用接口的統(tǒng)計。這塊功能的實現(xiàn),我們通過Storm從Kafka實時消費,并實時統(tǒng)計落HBase,每天凌晨將前一天的數(shù)據(jù)同步到Hive進行統(tǒng)計并回流到數(shù)據(jù)庫
5. 報警監(jiān)控
業(yè)務(wù)方API上線后,除了查看統(tǒng)計外,當API出問題時,還需要及時發(fā)現(xiàn)。我們針對這塊做了API報警功能。用戶在平臺配置自己的API的報警,這里我們主要支持基于錯誤數(shù)或RT維度的報警。
我們實時地從Kafka消費API調(diào)用日志,如果發(fā)現(xiàn)某個API的RT或者錯誤次數(shù)超過配置的報警閾值,則會立即觸發(fā)報警
四、實踐總結(jié)
1. 規(guī)范
在網(wǎng)關(guān)上暴露的API很多,如何讓這些API按照統(tǒng)一的標準對外暴露,讓開發(fā)者能夠低門檻快速接入是網(wǎng)關(guān)需要思考的問題
網(wǎng)關(guān)規(guī)范主要是對API的命名、入?yún)?#xff08;公用入?yún)ⅰI(yè)務(wù)入?yún)?#xff09;、內(nèi)部服務(wù)返回值、錯誤碼(公用錯誤碼、業(yè)務(wù)錯誤碼)、出參(公用出參、業(yè)務(wù)出參),進行規(guī)范
在我們的實踐過程中,總結(jié)了以下規(guī)范:
- 命名規(guī)范:youzan.[業(yè)務(wù)線(可選)].[應(yīng)用名].[動作].[版本],例如:youzan.item.create.3.0.0
- 入?yún)⒁?guī)范:要求全部小寫,組合單詞以下劃線分隔,例如:title, item_id;入?yún)⑷绻且粋€結(jié)構(gòu)體,要求以json字符串傳入,并且json中的key必須小寫并且以下劃線分隔
- 出參規(guī)范:要求全部小寫,組合單詞以下劃線分隔,例如:page_num, total_count;如果參數(shù)為結(jié)構(gòu)體,結(jié)構(gòu)體里面的key必須小寫且以下劃線分隔
- 錯誤碼規(guī)范:我們做了統(tǒng)一的錯誤碼,例如系統(tǒng)級錯誤碼51xxx,業(yè)務(wù)錯誤碼50000,詳情信息由msg顯示;業(yè)務(wù)級錯誤碼由業(yè)務(wù)方自行定義,同時約束每個業(yè)務(wù)方的錯誤碼范圍
- 服務(wù)返回值規(guī)范:針對不同的業(yè)務(wù)方,每個API可能會有不同的業(yè)務(wù)錯誤,我們需要將這部分業(yè)務(wù)級錯誤展示給開發(fā)者,因此我們約定返回值需要按照一個POJO類型(包含code, msg, data)來返回,對于code為200,我們認為正常返回,否則認為是業(yè)務(wù)錯誤,將返回值包裝為錯誤結(jié)果
2. 發(fā)布
- 我們將API劃分到3個環(huán)境,分別為測試環(huán)境、預發(fā)環(huán)境、生產(chǎn)環(huán)境。API的創(chuàng)建、編輯必須在測試環(huán)境進行,測試完成后,可以將API發(fā)布到預發(fā)環(huán)境,之后再從預發(fā)環(huán)境發(fā)布到生產(chǎn)環(huán)境,這樣可以保持三個環(huán)境的API數(shù)據(jù)一致。好處是:一方面可以讓測試開發(fā)能在測試環(huán)境進行自動化驗證,另一方面可以防止用戶直接編輯線上接口引發(fā)故障
3. 工具化
- 對于內(nèi)部用戶經(jīng)常可能需要排查問題,例如OAuth Token里面帶的參數(shù),需要經(jīng)常查詢,我們提供工具化的控制臺,能讓用戶方便查詢,從而減少答疑量
- 我們上線后也曾經(jīng)出現(xiàn)過緩存不一致的情況,為了能快速排查問題,我們做了緩存管理工具,能在圖形化界面上查看本地緩存以及Codis的緩存,可以進行對比找出差異
- 為了更好的排查線上問題,我們接入了有贊對比引擎(Replay)平臺,該平臺能將線上的流量引到預發(fā),幫助開發(fā)者更快定位問題
五、踩過的坑
-
Meta區(qū)Full GC導致服務(wù)無法響應(yīng)
現(xiàn)象:應(yīng)用hung死,調(diào)用接口返回503,無法服務(wù)
排查過程:現(xiàn)場dump了內(nèi)存,GC記錄,以及線程運行快照。首先看了GC發(fā)現(xiàn)是Full GC,但是不清楚是哪里發(fā)生的,看線程運行快照也沒發(fā)現(xiàn)什么問題。于是在本地用HeapAnalysis分析,堆區(qū)沒看出什么問題,大對象都是應(yīng)該占用的;于是查看方法區(qū),通過ClassLoader Analysis發(fā)現(xiàn)Fastjson相關(guān)的類較多,因此懷疑是class泄露,進一步通過MAT的OQL語法分析,發(fā)現(xiàn)是Fastjson在序列化Jetty容器的HttpServletRequest時,為了加快速度于是創(chuàng)建新的類時拋了異常,導致動態(tài)創(chuàng)建的類在方法區(qū)堆積從而引發(fā)Full GC,后續(xù)我們也向Fastjson提了相關(guān)bug
解決方案:將序列化HttpServletRequest的代碼移除
-
偽死循環(huán)導致CPU 100%
現(xiàn)象:在有贊雙11全鏈路壓測期間,某個業(yè)務(wù)調(diào)用API,導致我們的應(yīng)用CPU幾乎接近100%
排查過程:經(jīng)過日志分析,發(fā)現(xiàn)該接口存在大量超時,但是從代碼沒看出特別有問題的地方。于是我們將接口在QA環(huán)境模擬調(diào)用,用VisualVM連上去,通過抽樣器抽樣CPU,發(fā)現(xiàn)某個方法消耗CPU較高,因此我們迅速定位到源碼,發(fā)現(xiàn)這段代碼主要是執(zhí)行輪詢?nèi)蝿?wù)是否完成,如果完成則調(diào)用完成回調(diào),如果未完成繼續(xù)放到隊列。再結(jié)合之前的環(huán)境觀察發(fā)現(xiàn)大量超時的任務(wù)被放到隊列,導致任務(wù)被取出后,任務(wù)仍然是未完成狀態(tài),這樣會將任務(wù)放回隊列,這樣其實構(gòu)成了一個死循環(huán)
解決方案:將主動輪詢改為異步通知,我們這里是Dubbo調(diào)用,Dubbo調(diào)用返回的Future實際是一個FutureAdapter,可以獲取到里面的ResponseFuture(DefaultFuture),這個類型的Future支持設(shè)置Callback,任務(wù)完成時會通知到設(shè)置的回調(diào)
六、未來展望
七、結(jié)語
有贊網(wǎng)關(guān)目前歸屬有贊共享技術(shù)-基礎(chǔ)服務(wù)中心團隊開發(fā)和維護;
該團隊目前主要分為商品中心、庫存中心、物流中心、消息溝通平臺、云生態(tài)5個小組;
商品/庫存/物流中心:通過不斷抽象上層業(yè)務(wù),完成通用的模型建設(shè);為上層業(yè)務(wù)方提供高可用的服務(wù),并快速響應(yīng)多變的業(yè)務(wù)需求;針對秒殺、洪峰調(diào)用、及上層業(yè)務(wù)多變等需求,三個小組還齊力開發(fā)和持續(xù)完善著 對比引擎、服務(wù)熔斷、熱點探測等三個通用系統(tǒng);
消息溝通平臺:提供幾乎一切消息溝通相關(guān)的能力及一套幫助商家與用戶聯(lián)系的多客服系統(tǒng),每天承載著上億次調(diào)用(短信、apppush、語音、微信、微博、多客服、郵件等通道);
云生態(tài):承擔著核心網(wǎng)關(guān)的建設(shè)和發(fā)展(上面的網(wǎng)關(guān)應(yīng)用系統(tǒng))、三方推送系統(tǒng)、有贊云后臺、商業(yè)化訂購以及App Engine的預研和開發(fā)
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/articles/9238890.html
總結(jié)
- 上一篇: Lambda架构在有赞广告平台的应用与演
- 下一篇: 在Mybatis-spring上基于注解