eShopOnContainers 知多少[9]:Ocelot gateways
引言
客戶端與微服務的通信問題永遠是一個繞不開的問題,對于小型微服務應用,客戶端與微服務可以使用直連的方式進行通信,但對于對于大型的微服務應用我們將不得不面對以下問題:
如何降低客戶端到后臺的請求數量,并減少與多個微服務的無效交互?
如何處理微服務間的交叉問題,比如授權、數據轉換和動態(tài)請求派發(fā)?
客戶端如何與使用非互聯(lián)網友好協(xié)議的服務進行交互?
如何打造移動端友好的服務?
而解決這一問題的方法之一就是借助API網關,其允許我們按需組合某些微服務以提供單一入口。
接下來,本文就來梳理一下eShopOnContainers是如何集成Ocelot網關來進行通信的。
Hello Ocelot
關于Ocelot,張隊在Github上貼心的整理了awesome-ocelot系列以便于我們學習。這里就簡單介紹下Ocelot,不過多展開。 Ocelot是一個開源的輕量級的基于ASP.NET Core構建的快速且可擴展的API網關,核心功能包括路由、請求聚合、限速和負載均衡,集成了IdentityServer4以提供身份認證和授權,基于Consul提供了服務發(fā)現能力,借助Polly實現了服務熔斷,能夠很好的和k8s和Service Fabric集成。
Ocelot 集成
eShopOnContainers中的以下六個微服務都是通過網關API進行發(fā)布的。
引入網關層后,eShopOnContainers的整體架構如下圖所示:
從代碼結構來看,其基于業(yè)務邊界(Marketing和Shopping)分別為Mobile和Web端建立多個網關項目,這樣做利于隔離變化,降低耦合,且保證開發(fā)團隊的獨立自主性。所以我們在設計網關時也應注意到這一點,切忌設計大一統(tǒng)的單一API網關,以避免整個微服務架構體系的過度耦合。在網關設計中應當根據業(yè)務和領域去決定API網關的邊界,盡量設計細粒度而非粗粒度的API網關。
eShopOnContainers中 ApiGateways文件下是相關的網關項目。相關項目結構如下圖所示。
從代碼結構看,有四個 configuration.json文件,該文件就是ocelot的配置文件,其中主要包含兩個節(jié)點:
{
"ReRoutes": [],
"GlobalConfiguration": {}
}
那4個獨立的配置文件是怎樣設計成4個獨立的API網關的呢? 在eShopOnContainers中,首先基于 OcelotApiGw項目構建單個Ocelot API網關Docker容器鏡像,然后在運行時,通過使用 docker volume分別掛載不同路徑下的 configuration.json文件來啟動不同類型的API-Gateway容器。示意圖如下:
docker-compse.yml中相關配置如下:
docker-compse.override.ymlmobileshoppingapigw: environment: ASPNETCORE_ENVIRONMENT=Development IdentityUrl=http://identity.api ports: "5200:80" volumes: ./src/ApiGateways/Mobile.Bff.Shopping/apigw:/app/configuration
通過這種方式將API網關分成多個API網關,不僅可以同時重復使用相同的Ocelot Docker鏡像,而且開發(fā)團隊可以專注于團隊所屬微服務的開發(fā),并通過獨立的Ocelot配置文件來管理自己的API網關。
而關于Ocelot的代碼集成,主要就是指定配置文件以及注冊Ocelot中間件。核心代碼如下:
請求聚合
在單體應用中時,進行頁面展示時,可以一次性關聯(lián)查詢所需的對象并返回,但是對于微服務應用來說,某一個頁面的展示可能需要涉及多個微服務的數據,那如何進行將多個微服務的數據進行聚合呢?首先,不可否認的是,Ocelot提供了請求聚合功能,但是就其靈活性而言,遠不能滿足我們的需求。因此,一般會選擇自定義聚合器來完成靈活的聚合功能。在eShopOnContainers中就是通過獨立ASP.NET Core Web API項目來提供明確的聚合服務。 Mobile.Shopping.HttpAggregator和 Web.Shopping.HttpAggregator即是用于提供自定義的請求聚合服務。
下面就以 Web.Shopping.HttpAggregator項目為例來講解自定義聚合的實現思路。 首先,該網關項目是基于ASP.NET Web API構建。其代碼結構如下圖所示:
其核心思路是自定義網關服務借助HttpClient發(fā)起請求。我們來看一下 BasketService的實現代碼:
代碼中主要是通過構造函數注入 HttpClient,然后方法中借助 HttpClient實例發(fā)起相應請求。那 HttpClient實例是如何注冊的呢,我們來看下啟動類里服務注冊邏輯。
services.AddHttpClient<ICatalogService, CatalogService>() .AddPolicyHandler(GetRetryPolicy()) .AddPolicyHandler(GetCircuitBreakerPolicy());
services.AddHttpClient<IOrderApiClient, OrderApiClient>() .AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>() .AddPolicyHandler(GetRetryPolicy()) .AddPolicyHandler(GetCircuitBreakerPolicy()); return services;}
從代碼中可以看到主要做了三件事:
注冊?HttpClientAuthorizationDelegatingHandler負責為HttpClient構造?Authorization請求頭
注冊?IHttpContextAccessor用于獲取?HttpContext
為三個網關服務分別注冊獨立的?HttpClient,其中?IBasketServie和?IOrderApiClient需要認證,所以注冊了?HttpClientAuthorizationDelegatingHandler用于構造?Authorization請求頭。另外,分別注冊了?Polly的請求重試和斷路器策略。
那 HttpClientAuthorizationDelegatingHandler是如何構造 Authorization請求頭的呢?直接看代碼實現:
代碼實現也很簡單:首先從 _httpContextAccesor.HttpContext.Request.Headers["Authorization"]中取,若沒有則從 _httpContextAccesor.HttpContext.GetTokenAsync("access_token")中取,拿到訪問令牌后,添加到請求頭 request.Headers.Authorization=newAuthenticationHeaderValue("Bearer",token);即可。
這里你肯定有個疑問就是:為什么不是到Identity microservices去取訪問令牌,而是直接從 _httpContextAccesor.HttpContext.GetTokenAsync("access_token")中取訪問令牌?
Good Question,因為對于網關項目而言,其本身也是需要認證的,在訪問網關暴露的需要認證的API時,其已經同Identity microservices協(xié)商并獲取到令牌,并將令牌內置到 HttpContext中了。所以,對于同一個請求上下文,我們僅需將網關項目申請到的令牌傳遞下去即可。
Ocelot網關中如何集成認證和授權
不管是獨立的微服務還是網關,認證和授權問題都是要考慮的。Ocelot允許我們直接在網關內的進行身份驗證,如下圖所示:
因為認證授權作為微服務的交叉問題,所以將認證授權作為橫切關注點設計為獨立的微服務更符合關注點分離的思想。而Ocelot網關僅需簡單的配置即可完成與外部認證授權服務的集成。
1. 配置認證選項
首先在 configuration.json配置文件中為需要進行身份驗證保護API的網關設置 AuthenticationProviderKey。比如:
2. 注冊認證服務
當Ocelot運行時,它將根據Re-Routes節(jié)點中定義的 AuthenticationOptions.AuthenticationProviderKey,去確認系統(tǒng)是否注冊了相對應身份驗證提供程序。如果沒有,那么Ocelot將無法啟動。如果有,則ReRoute將在執(zhí)行時使用該提供程序。 在 OcelotApiGw的啟動配置中,就注冊了 AuthenticationProviderKey:IdentityApiKey的認證服務。
這里需要說明一點的是 ValidAudiences用來指定可被允許訪問的服務。其與各個微服務啟動類中 ConfigureServices()內 AddJwtBearer()指定的 Audience相對應。比如:
3. 按需配置申明進行鑒權
另外有一點不得不提的是,Ocelot支持在身份認證后進行基于聲明的授權。僅需在 ReRoute節(jié)點下配置 RouteClaimsRequirement即可:
"RouteClaimsRequirement": {
"UserType": "employee"
}
在該示例中,當調用授權中間件時,Ocelot將查找用戶是否在令牌中是否存在 UserType:employee的申明。如果不存在,則用戶將不被授權,并響應403。
最后
經過以上的講解,想必你對eShopOnContainers中如何借助API 網關模式解決客戶端與微服務的通信問題有所了解,但其就是萬金油嗎?API 網關模式也有其缺點所在。
網關層與內部微服務間的高度耦合。
網關層可能出現單點故障。
API網關可能導致性能瓶頸。
API網關如果包含復雜的自定義邏輯和數據聚合,額外增加了團隊的開發(fā)維護溝通成本。
雖然IT沒有銀彈,但eShopOnContainers中網關模式的應用案例至少指明了一種解決問題的思路。而至于在實戰(zhàn)場景中的技術選型,適合的就是最好的。
原文地址:http://www.cnblogs.com/sheng-jie/p/10476436.html
.NET社區(qū)新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com 
總結
以上是生活随笔為你收集整理的eShopOnContainers 知多少[9]:Ocelot gateways的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: Asp.NETCore轻松学系列阅读指引
- 下一篇: .NET/C# 获取一个正在运行的进程的
