做者:aCoder2013git
github.com/aCoder2013/blog/issues/35github
前言web
假設你正在開發一個電商網站,那麼這裏會涉及到不少後端的微服務,好比會員、商品、推薦服務等等。redis
那麼這裏就會遇到一個問題,APP/Browser怎麼去訪問這些後端的服務? 若是業務比較簡單的話,能夠給每一個業務都分配一個獨立的域名(https://service.api.company.com),但這種方式會有幾個問題:spring
每一個業務都會須要鑑權、限流、權限校驗等邏輯,若是每一個業務都各自爲戰,本身造輪子實現一遍,會很蛋疼,徹底能夠抽出來,放到一個統一的地方去作。數據庫
若是業務量比較簡單的話,這種方式前期不會有什麼問題,但隨着業務愈來愈複雜,好比淘寶、亞馬遜打開一個頁面可能會涉及到數百個微服務協同工做,若是每個微服務都分配一個域名的話,一方面客戶端代碼會很難維護,涉及到數百個域名,另外一方面是鏈接數的瓶頸,想象一下你打開一個APP,經過抓包發現涉及到了數百個遠程調用,這在移動端下會顯得很是低效。apache
每上線一個新的服務,都須要運維參與,申請域名、配置Nginx等,當上線、下線服務器時,一樣也須要運維參與,另外採用域名這種方式,對於環境的隔離也不太友好,調用者須要本身根據域名本身進行判斷。後端
另外還有一個問題,後端每一個微服務多是由不一樣語言編寫的、採用了不一樣的協議,好比HTTP、Dubbo、GRPC等,可是你不可能要求客戶端去適配這麼多種協議,這是一項很是有挑戰的工做,項目會變的很是複雜且很難維護。api
後期若是須要對微服務進行重構的話,也會變的很是麻煩,須要客戶端配合你一塊兒進行改造,好比商品服務,隨着業務變的愈來愈複雜,後期須要進行拆分紅多個微服務,這個時候對外提供的服務也須要拆分紅多個,同時須要客戶端配合你進行改造,很是蛋疼。緩存
API Gateway
更好的方式是採用API網關,實現一個API網關接管全部的入口流量,相似Nginx的做用,將全部用戶的請求轉發給後端的服務器,但網關作的不只僅只是簡單的轉發,也會針對流量作一些擴展,好比鑑權、限流、權限、熔斷、協議轉換、錯誤碼統1、緩存、日誌、監控、告警等,這樣將通用的邏輯抽出來,由網關統一去作,業務方也可以更專一於業務邏輯,提高迭代的效率。
經過引入API網關,客戶端只須要與API網關交互,而不用與各個業務方的接口分別通信,但多引入一個組件就多引入了一個潛在的故障點,所以要實現一個高性能、穩定的網關,也會涉及到不少點。
API註冊
業務方如何接入網關?通常來講有幾種方式。
第一種採用插件掃描業務方的API,好比Spring MVC的註解,並結合Swagger的註解,從而實現參數校驗、文檔&&SDK生成等功能,掃描完成以後,須要上報到網關的存儲服務。
手動錄入。好比接口的路徑、請求參數、響應參數、調用方式等信息,但這種方式相對來講會麻煩一些,若是參數過多的話,前期錄入會很費時費力。
配置文件導入。好比經過Swagger\OpenAPI等,好比阿里雲的網關:
協議轉換
內部的API多是由不少種不一樣的協議實現的,好比HTTP、Dubbo、GRPC等,但對於用戶來講其中不少都不是很友好,或者根本無法對外暴露,好比Dubbo服務,所以須要在網關層作一次協議轉換,將用戶的HTTP協議請求,在網關層轉換成底層對應的協議,好比HTTP -> Dubbo, 但這裏須要注意不少問題,好比參數類型,若是類型搞錯了,致使轉換出問題,而日誌又不夠詳細的話,問題會很難定位。
服務發現
網關做爲流量的入口,負責請求的轉發,但首先須要知道轉發給誰,如何尋址,這裏有幾種方式:
寫死在代碼/配置文件裏,這種方式雖然比較挫,但也能使用,好比線上仍然使用的是物理機,IP變更不會很頻繁,但擴縮容、包括應用上下線都會很麻煩,網關自身甚至須要實現一套健康監測機制。
域名。採用域名也是一種不錯的方案,對於全部的語言都適用,但對於內部的服務,走域名會很低效,另外環境隔離也不太友好,好比預發、線上一般是同一個數據庫,所以網關讀取到的多是同一個域名,這時候預發的網關調用的就是線上的服務。
註冊中心。採用註冊中心就不會有上述的這些問題,即便是在容器環境下,節點的IP變動比較頻繁,但節點列表的實時維護會由註冊中心搞定,對網關是透明的,另外應用的正常上下線、包括異常宕機等狀況,也會由註冊中心的健康檢查機制檢測到,並實時反饋給網關。而且採用註冊中心性能也沒有額外的性能損耗,採用域名的方式,額外須要走一次DNS解析、Nginx轉發等,中間多了不少跳,性能會有很大的降低,但採用註冊中心,網關是和業務方直接點對點的通信,不會有額外的損耗。
服務調用
網關因爲對接不少種不一樣的協議,所以可能須要實現不少種調用方式,好比HTTP、Dubbo等,基於性能緣由,最好都採用異步的方式,而Http、Dubbo都是支持異步的,好比apache就提供了基於NIO實現的異步HTTP客戶端。
由於網關會涉及到不少異步調用,好比攔截器、HTTP客戶端、dubbo、redis等,所以須要考慮下異步調用的方式,若是基於回調或者future的話,代碼嵌套會很深,可讀性不好,能夠參考zuul和spring cloud gateway的方案,基於響應式進行改造。
優雅下線
優雅下線也是網關須要關注的一個問題,網關底層會涉及到不少種協議,好比HTTP、Dubbo,而HTTP又能夠繼續細分,好比域名、註冊中心等,有些自身就支持優雅下線,好比Nginx自身是支持健康監測機制的,若是檢測到某一個節點已經掛掉了,就會把這個節點摘掉,對於應用正常下線,須要結合發佈系統,首先進行邏輯下線,而後對後續Nginx的健康監測請求直接返回失敗(好比直接返回500),而後等待一段時間(根據Nginx配置決定),而後再將應用實際下線掉。另外對於註冊中心的其實也相似,通常註冊中心是隻支持手動下線的,能夠在邏輯下線階段調用註冊中心的接口將節點下線掉,而有些不支持主動下線的,須要結合緩存的配置,讓應用延遲下線。另外對於其餘好比Dubbo等原理也是相似。
性能
網關做爲全部流量的入口,性能是重中之重,早期大部分網關都是基於同步阻塞模型構建的,好比Zuul 1.x。但這種同步的模型咱們都知道,每一個請求/鏈接都會佔用一個線程,而線程在JVM中是一個很重的資源,好比Tomcat默認就是200個線程,若是網關隔離沒有作好的話,當發生網絡延遲、FullGC、第三方服務慢等狀況形成上游服務延遲時,線程池很容易會被打滿,形成新的請求被拒絕,但這個時候其實線程都阻塞在IO上,系統的資源被沒有獲得充分的利用。另一點,容易受網絡、磁盤IO等延遲影響。須要謹慎設置超時時間,若是設置不當,且服務隔離作的不是很完善的話,網關很容易被一個慢接口拖垮。
而異步化的方式則徹底不一樣,一般狀況下一個CPU核啓動一個線程便可處理全部的請求、響應。一個請求的生命週期再也不固定於一個線程,而是會分紅不一樣的階段交由不一樣的線程池處理,系統的資源可以獲得更充分的利用。並且由於線程再也不被某一個鏈接獨佔,一個鏈接所佔用的系統資源也會低得多,只是一個文件描述符加上幾個監聽器等,而在阻塞模型中,每條鏈接都會獨佔一個線程,而線程是一個很是重的資源。對於上游服務的延遲狀況,也可以獲得很大的緩解,由於在阻塞模型中,慢請求會獨佔一個線程資源,而異步化以後,由於單條鏈接所佔用的資源變的很是低,系統能夠同時處理大量的請求。
若是是JVM平臺,Zuul 二、Spring Cloud gateway等都是不錯的異步網關選型,另外也能夠基於Netty、Spring Boot2.x的webflux、vert.x或者servlet3.1的異步支持進行自研。
緩存
對於一些冪等的get請求,能夠在網關層面根據業務方指定的緩存頭作一層緩存,存儲到Redis等二級緩存中,這樣一些重複的請求,能夠在網關層直接處理,而不用打到業務線,下降業務方的壓力,另外若是業務方節點掛掉,網關也可以返回自身的緩存。
限流
限流對於每一個業務組件來講,能夠說都是一個必須的組件,若是限流作很差的話,當請求量突增時,很容易致使業務方的服務掛掉,好比雙十一、雙12等大促時,接口的請求量是平時的數倍,若是沒有評估好容量,又沒有作限流的話,很容易服務整個不可用,所以須要根據業務方接口的處理能力,作好限流策略,相信你們都見過淘寶、百度搶紅包時的降級頁面。
所以必定要在接入層作好限流策略,對於非核心接口能夠直接將降級掉,保障核心服務的可用性,對於核心接口,須要根據壓測時獲得的接口容量,制定對應的限流策略。限流又分爲幾種:
單機。單機性能比較高,不涉及遠程調用,只是本地計數,對接口RT影響最小。但須要考慮下限流數的設置,好比是針對單臺網關、仍是整個網關集羣,若是是整個集羣的話,須要考慮到網關縮容、擴容時修改對應的限流數。
分佈式。分佈式的就須要一個存儲節點維護當前接口的調用數,好比redis、sentinel等,這種方式因爲涉及到遠程調用,會有些性能損耗,另外也須要考慮到存儲掛掉的問題,好比redis若是掛掉,網關須要考慮降級方案,是降級到本地限流,仍是直接將限流功能自己降級掉。
另外還有不一樣的策略:簡單計數、令牌桶等,大部分場景下其實簡單計數已經夠用了,但若是須要支持突發流量等場景時,能夠採用令牌桶等方案。還須要考慮根據什麼限流,好比是IP、接口、用戶維度、仍是請求參數中的某些值,這裏能夠採用表達式,相對比較靈活。
穩定性
穩定性是網關很是重要的一環,監控、告警須要作的很完善才能夠,好比接口調用量、響應時間、異常、錯誤碼、成功率等相關的監控告警,還有線程池相關的一些,好比活躍線程數、隊列積壓等,還有些系統層面的,好比CPU、內存、FullGC這些基本的。
網關是全部服務的入口,對於網關的穩定性的要求相對於其餘服務會更高,最好可以一直穩定的運行,儘可能少重啓,但當新增功能、或者加日誌排查問題時,不可避免的須要從新發布,所以能夠參考zuul的方式,將全部的核心功能都基於不一樣的攔截器實現,攔截器的代碼採用Groovy編寫,存儲到數據庫中,支持動態加載、編譯、運行,這樣在出了問題的時候可以第一時間定位並解決,而且若是網關須要開發新功能,只須要增長新的攔截器,並動態添加到網關便可,不須要從新發布。
熔斷降級
熔斷機制也是很是重要的一項。若某一個服務掛掉、接口響應嚴重超時等發生,則可能整個網關都被一個接口拖垮,所以須要增長熔斷降級,當發生特定異常的時候,對接口降級由網關直接返回,能夠基於Hystrix或者Resilience4j實現。
日誌
因爲全部的請求都是由網關處理的,所以日誌也須要相對比較完善,好比接口的耗時、請求方式、請求IP、請求參數、響應參數(注意脫敏)等,另外因爲可能涉及到不少微服務,所以須要提供一個統一的traceId方便關聯全部的日誌,能夠將這個traceId置於響應頭中,方便排查問題。
隔離
好比線程池、http鏈接池、redis等應用層面的隔離,另外也能夠根據業務場景,將核心業務部署帶單獨的網關集羣,與其餘非核心業務隔離開。
網關管控平臺
這塊也是很是重要的一環,須要考慮好整個流程的用戶體驗,好比接入到網關的這個流程,能不能儘可能簡化、智能,好比若是是dubbo接口,咱們能夠經過到git倉庫中獲取源碼、解析對應的類、方法,從而實現自動填充,儘可能幫用戶減小操做;另外接口通常是從測試->預發->線上,若是每次都要填寫一遍表單會很是麻煩,咱們能不能自動把這個事情作掉,另外若是網關部署到了多個可用區、甚至不一樣的國家,那這個時候,咱們還須要接口數據同步功能,否則用戶須要到每一個後臺都操做一遍,很是麻煩。
這塊我的的建議是直接參考阿里雲、aws等提供的網關服務便可,功能很是全面。
其餘
其餘還有些須要考慮到的點,好比接口mock,文檔生成、sdk代碼生成、錯誤碼統1、服務治理相關的等,這裏就不累述了。
總結
目前的網關仍是中心化的架構,全部的請求都須要走一次網關,所以當大促或者流量突增時,網關可能會成爲性能的瓶頸,並且當網關接入的大量接口的時候,作好流量評估也不是一項容易的工做,每次大促前都須要跟業務方一塊兒針對接口作壓測,評估出大體的容量,並對網關進行擴容,並且網關是全部流量的入口,全部的請求都是由網關處理,要想準確的評估出容量很複雜。能夠參考目前比較流行的ServiceMesh,採用去中心化的方案,將網關的邏輯下沉到sidecar中,sidecar和應用部署到同一個節點,並接管應用流入、流出的流量,這樣大促時,只須要對相關的業務壓測,並針對性擴容便可,另外升級也會更平滑,中心化的網關,即便灰度發佈,可是理論上全部業務方的流量都會流入到新版本的網關,若是出了問題,會影響到全部的業務,但這種去中心化的方式,能夠先針對非核心業務升級,觀察一段時間沒問題後,再全量推上線。另外ServiceMesh的方案,對於多語言支持也更友好。
【推薦閱讀】