有贊API網關實踐

轉載:https://tech.youzan.com/api-gateway-in-practice/

有贊API網關實踐

一、API網關簡介

隨着移動互聯網的興起、開放合作思維的盛行,不同終端和第三方開發者都需要大量的接入企業核心業務能力,此時各業務系統將會面臨同一系列的問題,例如:如何讓調用方快速接入、如何讓業務方安全地對外開放能力,如何應對和控制業務洪峯調用等等。於是就誕生了一個隔離企業內部業務系統和外部系統調用的屏障 - API網關,它負責在上層抽象出各業務系統需要的通用功能,例如:鑑權、限流、ACL、降級等。另外隨着近年來微服務的流行,API網關已經成爲一個微服務架構中的標配組件。

二、有贊API網關簡介

有贊API網關目前承載着微商城、零售、微小店、餐飲、美業、AppSDK、部分PC、三方開發者等多個業務的調用,每天有着億級別的流量。

有贊後端服務最開始是由PHP搭建,隨着整個技術體系的升級,後面逐步從PHP遷移到Java體系。在API網關設計之初主要支持Dubbo、Http兩種協議。遷移過程中,我們發現部分服務需要通過RPC方式調用PHP服務,於是我們(公司)基於Dubbo開發了一個新的框架Nova,兼容Dubbo調用,同時支持調用PHP服務。於是網關也支持了新的Nova協議,這樣就有Dubbo、Http、Nova三種協議。

隨着業務的不斷髮展,業務服務化速度加快,網關面臨各類新的需求。例如回調類型的API接入,這種API不需要鑑權,只需要一個限流服務,路由到後端服務即可;另外還有參數、返回值的轉換需求也不斷到來,這期間我們快速迭代滿足新的需求。而在這個過程中我們也走了很多彎路,例如API的規範,在最開始規範意識比較籠統,導致返回值在對外暴露時出現了不統一的情況,後續做SDK自動化的時候比較棘手,經過不斷的約束開發者,最終做到了統一。

三、架構與設計

1. 網關架構

部署架構圖

網關的調用方主要包括微商城、微小店、零售等App應用,以及三方開發者和部分PC業務。通過LVS做負載均衡,後端Tengine實現反向代理,網關應用調用到實際的業務集羣

應用架構圖

網關核心由Pipe鏈構成,每個Pipe負責一塊功能,同時使用緩存、異步等特性提升併發及性能

線程模型圖

網關採用Jetty部署,調用採用Http協議,請求由容器線程池處理(容器開啓了Servlet3.0異步,提升了較大的吞吐量),之後分發到應用線程池異步處理。應用線程池在設計之初考慮不同的任務執行可能會出現耗時不一的情況,所以將任務分別拆分到不同的線程池,以提高不同類型任務的併發度,如圖分爲CommonGroup, ExecutionGroup, ResultGroup

CommonGroup執行通用任務,ExecutionGroup執行多協議路由及調用任務,ResultGroup執行結果處理任務(包含異常)

網關業務生態圖

網關生態主要包含控制檯、網關核心、網關統計與監控
控制檯主要對API生命週期進行管理,以及ACL、流量管控等功能;
網關核心主要處理API調用,包含鑑權、限流、路由、協議轉換等功能;
統計與監控模塊主要完成API調用的統計以及對店鋪、三方的一些報表統計,同時提供監控功能和報警功能


2. 網關核心設計

2.1 異步

我們使用Jetty容器來部署應用,並開啓Servlet3.0的異步特性,由於網關業務本身就是調用大量業務接口,因此IO操作會比較頻繁,使用該特性能較大提升網關整體併發能力及吞吐量。另外我們在內部處理開啓多組線程池進行異步處理,以異步回調的方式通知任務完成,進一步提升併發量

image

2.2 二級緩存

爲了進一步提升網關的性能,我們增加了一層分佈式緩存(借用Codis實現),將一些不經常變更的API元數據緩存下來,這樣不僅減少了應用和DB的交互次數,還加快了讀取效率。我們同時考慮到Codis在極端情況下存在不穩定因素,因此我們在本地再次做了本地緩存,這樣的讀取可以從ms級別降低到ns級別。爲了實現多臺機器的本地緩存一致性,我們使用了ZK監聽節點變化來更新各機器本地緩存

image

2.3 鏈式處理

在設計網關的時候,我們採用責任鏈模式來實現網關的核心處理流程,將每個處理邏輯看成一個Pipe,每個Pipe按照預先設定的順序先後執行,與開源的Zuul 1.x類似,我們也採用了PRPE模式(Pre、Routing、Post、Error),在我們這裏Pre分爲PrePipe、RateLimitPipe、AuthPipe、AclPipe、FlowSepPipe,這些Pipe對數據進行預處理、限流、鑑權、訪問控制、分流,並將過濾後的Context向下傳遞;Routing分爲DubboPipe、HttpPipe,這些Pipe分別處理Dubbo協議、Http協議路由及調用;Post爲ResultPipe,處理正常返回值以及統計打點,Error爲ErrorPipe,處理異常場景

image

2.4 線程池隔離

Jetty容器線程池(QTP)負責接收Http請求,之後交由應用線程池CommonGroup,ExecutionGroup, ResultGroup,通用的操作將會被放到CommonGroup線程池執行,執行真實調用的被放到ExecutionGroup,結果處理放到ResultGroup。這樣部分Pipe之間線程隔離,通常前置Pipe處理都比較快,所以共享線程池即可,真實調用通常比較耗時,因此我們放到獨立的線程池,同時結果處理也存在一些運算,因此也放到獨立線程池

image

2.5 平滑限流

最早我們採用了簡單的分佈式緩存(Codis)計數實現限流,以IP、API維度構建Key進行累加,這種限流方式實現簡單,但是不能做到連續時間段內平滑限流。例如針對某個API每分鐘限流100次,第1秒發起20次,第二秒發起30次,第3秒發起40次,這樣的限流波動比較大,因此我們決定將其改進。經過調研我們最終選擇了令牌桶限流,令牌桶限流相比於漏桶限流能適應閒置較長時段後的尖峯調用,同時消除了簡單計數器限流帶來的短時間內流量不均的問題。目前網關支持IP、店鋪、API、應用ID和三方ID等多個維度的限流,也支持各維度的自由組合限流,可以很容易擴展出新的維度

image

2.6 熔斷降級

由於我們經常遇到調用後端接口超時,或者異常的情況,後端服務無法立即恢復,這種情況下再將請求發到後端已沒有意義。於是我們使用Hystrix進行熔斷降級處理。Hystrix支持線程池和信號量2種模式的隔離方案,網關的業務場景是多API和API分組,每個API都可能路由到不同後端服務,如果我們對API或者API分組做線程池隔離,就會產生大量的線程,所以我們選擇了信號量做隔離。我們爲每個API提供一個降級配置,用戶可以選擇自己配置的API在達到多少錯誤率時進行熔斷降級。
引入Hystrix後,Hystrix會對每個API做統計,包括總量、正確率、QPS等指標,同時會產生大量事件,當API很多的時候,這些指標和事件會佔用大量內存,導致更加頻繁的YoungGC,這對應用性能產生了一定的影響,不過整體的收益還是不錯的

另外有贊內部也開發了一個基於Hystrix的服務熔斷平臺(Tesla),平臺在可視化、易用性、擴展性上面均有較大程度的提升;後續網關會考慮熔斷模塊的實現基於服務熔斷平臺,以提供更好的服務

image

2.7 分流

有贊內部存在多種協議類型的後端服務,最原始的服務是PHP開發,後面逐漸遷移到Java,很早一部分API是由PHP暴露的,後續爲了能做灰度遷移到Java,我們做了分流,將老的PHP接口的流量按照一定的比例分發到新的Java接口上

3. 控制檯

除了核心功能的調用外,網關還需要支持內部用戶(下稱業務方)快速配置接口暴露給開發者。 控制檯主要職責包括:快速配置API、一站式測試API、一鍵發佈API,自動化文檔生成,自動化SDK生成

  • 快速配置API:這塊我們主要是按照對外、對內來進行配置,業務方將自己要對外公開的名稱、參數編輯好,再通過對內映射將對外參數映射到內部服務的接口裏面

image

  • 一站式測試API:API配置完成後,爲了能讓業務方快速測試,我們做了一站式獲取鑑權值,參數值自動保存,做到一站式測試

image

  • 一鍵發佈API:在完成配置和測試後,API就可以直接發佈,這個時候選擇對應環境的註冊中心或者服務域名即可

image

  • 自動化文檔生成:我們針對文檔這塊做了文檔中心,對內部用戶,他們只需要到平臺來搜索即可,對外部用戶,可以在有贊雲官網查看或者在控制檯直接導出pdf文件給用戶

image

  • 自動化SDK生成:對於開發者來說,接入一個平臺必然少不了SDK,我們針對多語言做了自動化SDK生成,當用戶的接口發佈成功後,我們會監聽到有新的接口,這時會觸發自動編譯(Java)SDK的模塊,將新接口打包成新版本的壓縮包,供開發者使用;如果編譯失敗(Java)則不會替換老的壓縮包,我們會發送報警給相應的開發者,讓其調整不規範的地方

image

4. 數據統計

爲了讓業務方能在上線後瞭解自己的接口的運行狀況,我們做了API相關的統計。我們通過在覈心模塊裏面打日誌,利用rsyslog採集數據到Kafka,然後從Kafka消費進行統計,之後迴流到數據庫供在線查詢

除此之外,我們爲每個商家做了他們授權的服務商調用接口的統計。這塊功能的實現,我們通過Storm從Kafka實時消費,並實時統計落HBase,每天凌晨將前一天的數據同步到Hive進行統計並回流到數據庫

image

5. 報警監控

業務方API上線後,除了查看統計外,當API出問題時,還需要及時發現。我們針對這塊做了API報警功能。用戶在平臺配置自己的API的報警,這裏我們主要支持基於錯誤數或RT維度的報警。
我們實時地從Kafka消費API調用日誌,如果發現某個API的RT或者錯誤次數超過配置的報警閾值,則會立即觸發報警

image

四、實踐總結

1. 規範

在網關上暴露的API很多,如何讓這些API按照統一的標準對外暴露,讓開發者能夠低門檻快速接入是網關需要思考的問題

網關規範主要是對API的命名、入參(公用入參、業務入參)、內部服務返回值、錯誤碼(公用錯誤碼、業務錯誤碼)、出參(公用出參、業務出參),進行規範

在我們的實踐過程中,總結了以下規範:

  • 命名規範:youzan.[業務線(可選)].[應用名].[動作].[版本],例如:youzan.item.create.3.0.0
  • 入參規範:要求全部小寫,組合單詞以下劃線分隔,例如:title, item_id;入參如果是一個結構體,要求以json字符串傳入,並且json中的key必須小寫並且以下劃線分隔
  • 出參規範:要求全部小寫,組合單詞以下劃線分隔,例如:page_num, total_count;如果參數爲結構體,結構體裏面的key必須小寫且以下劃線分隔
  • 錯誤碼規範:我們做了統一的錯誤碼,例如系統級錯誤碼51xxx,業務錯誤碼50000,詳情信息由msg顯示;業務級錯誤碼由業務方自行定義,同時約束每個業務方的錯誤碼範圍
  • 服務返回值規範:針對不同的業務方,每個API可能會有不同的業務錯誤,我們需要將這部分業務級錯誤展示給開發者,因此我們約定返回值需要按照一個POJO類型(包含code, msg, data)來返回,對於code爲200,我們認爲正常返回,否則認爲是業務錯誤,將返回值包裝爲錯誤結果

2. 發佈

  • 我們將API劃分到3個環境,分別爲測試環境、預發環境、生產環境。API的創建、編輯必須在測試環境進行,測試完成後,可以將API發佈到預發環境,之後再從預發環境發佈到生產環境,這樣可以保持三個環境的API數據一致。好處是:一方面可以讓測試開發能在測試環境進行自動化驗證,另一方面可以防止用戶直接編輯線上接口引發故障

3. 工具化

  • 對於內部用戶經常可能需要排查問題,例如OAuth Token裏面帶的參數,需要經常查詢,我們提供工具化的控制檯,能讓用戶方便查詢,從而減少答疑量
  • 我們上線後也曾經出現過緩存不一致的情況,爲了能快速排查問題,我們做了緩存管理工具,能在圖形化界面上查看本地緩存以及Codis的緩存,可以進行對比找出差異
  • 爲了更好的排查線上問題,我們接入了有贊對比引擎(Replay)平臺,該平臺能將線上的流量引到預發,幫助開發者更快定位問題

五、踩過的坑

  • Meta區Full GC導致服務無法響應

    現象:應用hung死,調用接口返回503,無法服務

    排查過程:現場dump了內存,GC記錄,以及線程運行快照。首先看了GC發現是Full GC,但是不清楚是哪裏發生的,看線程運行快照也沒發現什麼問題。於是在本地用HeapAnalysis分析,堆區沒看出什麼問題,大對象都是應該佔用的;於是查看方法區,通過ClassLoader Analysis發現Fastjson相關的類較多,因此懷疑是class泄露,進一步通過MAT的OQL語法分析,發現是Fastjson在序列化Jetty容器的HttpServletRequest時,爲了加快速度於是創建新的類時拋了異常,導致動態創建的類在方法區堆積從而引發Full GC,後續我們也向Fastjson提了相關bug

    解決方案:將序列化HttpServletRequest的代碼移除

  • 僞死循環導致CPU 100%

    現象:在有贊雙11全鏈路壓測期間,某個業務調用API,導致我們的應用CPU幾乎接近100%

    排查過程:經過日誌分析,發現該接口存在大量超時,但是從代碼沒看出特別有問題的地方。於是我們將接口在QA環境模擬調用,用VisualVM連上去,通過抽樣器抽樣CPU,發現某個方法消耗CPU較高,因此我們迅速定位到源碼,發現這段代碼主要是執行輪詢任務是否完成,如果完成則調用完成回調,如果未完成繼續放到隊列。再結合之前的環境觀察發現大量超時的任務被放到隊列,導致任務被取出後,任務仍然是未完成狀態,這樣會將任務放回隊列,這樣其實構成了一個死循環

    解決方案:將主動輪詢改爲異步通知,我們這裏是Dubbo調用,Dubbo調用返回的Future實際是一個FutureAdapter,可以獲取到裏面的ResponseFuture(DefaultFuture),這個類型的Future支持設置Callback,任務完成時會通知到設置的回調

六、未來展望

  1. 業務級資源組隔離。隨着業務的不斷髮展,當業務線較多時,可以將重要的業務分配到更優質的資源組(例如:機器性能、線程池的大小),將一般業務放到普通資源組,這樣可以更好的服務不同的業務場景
  2. 更高併發的線程池/IO的優化。隨着業務的發展,未來可能會出現更高的併發,需要更精良的線程及IO模型
  3. 更多的協議支持。以後技術的發展,Http2可能會蓬勃發展,這時需要接入Http2的協議

七、結語

有贊網關目前歸屬有贊共享技術-基礎服務中心團隊開發和維護;
該團隊目前主要分爲商品中心、庫存中心、物流中心、消息溝通平臺、雲生態5個小組;
商品/庫存/物流中心:通過不斷抽象上層業務,完成通用的模型建設;爲上層業務方提供高可用的服務,並快速響應多變的業務需求;針對秒殺、洪峯調用、及上層業務多變等需求,三個小組還齊力開發和持續完善着 對比引擎、服務熔斷、熱點探測等三個通用系統;
消息溝通平臺:提供幾乎一切消息溝通相關的能力及一套幫助商家與用戶聯繫的多客服系統,每天承載着上億次調用(短信、apppush、語音、微信、微博、多客服、郵件等通道);
雲生態:承擔着核心網關的建設和發展(上面的網關應用系統)、三方推送系統、有贊雲後臺、商業化訂購以及App Engine的預研和開發;



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