中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站

eShopOnContainers 知(zhi)多少(shao)[9]:Ocelot gateways

引言

客(ke)戶端(duan)與(yu)微(wei)服(fu)務的(de)通(tong)信問題(ti)永(yong)遠是(shi)一個繞不(bu)開(kai)的(de)問題(ti),對(dui)于小型微(wei)服(fu)務應用,客(ke)戶端(duan)與(yu)微(wei)服(fu)務可以(yi)使用直(zhi)連的(de)方(fang)式進行(xing)通(tong)信,但對(dui)于對(dui)于大型的(de)微(wei)服(fu)務應用我們將不(bu)得不(bu)面對(dui)以(yi)下(xia)問題(ti):

  1. 如何降低客戶端到后臺的請求數量,并減少與多個微服務的無效交互?
  2. 如何處理微服務間的交叉問題,比如授權、數據轉換和動態請求派發?
  3. 客戶端如何與使用非互聯網友好協議的服務進行交互?
  4. 如何打造移動端友好的服務?

而解決這一(yi)問題的方(fang)法之一(yi)就是借助API網(wang)關,其允許我們按需(xu)組合(he)某(mou)些微(wei)服務以提供單一(yi)入(ru)口。

接(jie)下來(lai),本(ben)文就來(lai)梳理一下eShopOnContainers是如(ru)何集成Ocelot網關來(lai)進行(xing)通信的。

使用自定義的API 網關服務

Hello Ocelot

關于Ocelot,張隊在Github上貼心的整理了系列以便于我們學習。這里就簡單介紹下Ocelot,不過多展開。
Ocelot是一個開源(yuan)的輕量級的基(ji)于(yu)ASP.NET Core構建的快速(su)且可擴展的API網關,核心功(gong)能(neng)包括(kuo)路由、請求(qiu)聚合、限速(su)和負(fu)載(zai)均(jun)衡,集成(cheng)了(le)IdentityServer4以(yi)提供(gong)身份認(ren)證(zheng)和授權,基(ji)于(yu)Consul提供(gong)了(le)服務發現能(neng)力(li),借助Polly實現了(le)服務熔斷,能(neng)夠很(hen)好(hao)的和k8s和Service Fabric集成(cheng)。

Ocelot 集成

eShopOnContainers中的以下六個微服務都是通過網關API進行發布的。

引入網關層后,eShopOnContainers的整體架構如下圖所示:
引入網關層后的整體架構設計

從代碼結構來看,其(qi)基(ji)于業務(wu)(wu)邊界(jie)(Marketing和Shopping)分(fen)別為Mobile和Web端建立(li)多個網(wang)(wang)關(guan)項(xiang)目,這樣做利(li)于隔離變化(hua),降低耦合,且保證開(kai)發團隊的(de)(de)獨立(li)自主性。所以我(wo)們在設計(ji)(ji)(ji)網(wang)(wang)關(guan)時也應注意到(dao)這一點,切忌設計(ji)(ji)(ji)大一統的(de)(de)單一API網(wang)(wang)關(guan),以避免整個微服務(wu)(wu)架構體系的(de)(de)過(guo)度(du)耦合。在網(wang)(wang)關(guan)設計(ji)(ji)(ji)中應當根據(ju)業務(wu)(wu)和領域去(qu)決(jue)定API網(wang)(wang)關(guan)的(de)(de)邊界(jie),盡量設計(ji)(ji)(ji)細粒度(du)而非粗粒度(du)的(de)(de)API網(wang)(wang)關(guan)。

eShopOnContainers中ApiGateways文件下是相關的(de)網關項目(mu)。相關項目(mu)結構如(ru)下圖所(suo)示(shi)。

ApiGateways 代碼結構

從代碼結構看,有四個configuration.json文件,該(gai)文件就是ocelot的配(pei)置文件,其中主要(yao)包含兩個(ge)節點:

{
 "ReRoutes": [],
 "GlobalConfiguration": {}
}

那4個獨立的配置文件是怎樣設計成4個獨立的API網關的呢?
在eShopOnContainers中,首先基于OcelotApiGw項目構建單個Ocelot API網關Docker容器鏡像,然后在運行時,通過使用docker volume分別掛載不同路徑下的configuration.json文件來啟動不同類型的API-Gateway容器。示意圖如下:
重用Ocelot Docker鏡像啟動多個網關容器服務

docker-compse.yml中相關配置如下:

// docker-compse.yml
mobileshoppingapigw:
 image: eshop/ocelotapigw:${TAG:-latest}
 build:
 context: .
 dockerfile: src/ApiGateways/ApiGw-Base/Dockerfile

// docker-compse.override.yml
mobileshoppingapigw:
 environment:
 - ASPNETCORE_ENVIRONMENT=Development
 - IdentityUrl=//identity.api
 ports:
 - "5200:80"
 volumes:
 - ./src/ApiGateways/Mobile.Bff.Shopping/apigw:/app/configuration

通(tong)過(guo)這種方(fang)式將(jiang)API網(wang)關(guan)分成(cheng)多個(ge)API網(wang)關(guan),不(bu)僅可以(yi)同時重復使用相同的(de)Ocelot Docker鏡像,而(er)且(qie)開發(fa)團隊(dui)可以(yi)專注于團隊(dui)所屬微服(fu)務(wu)的(de)開發(fa),并通(tong)過(guo)獨立的(de)Ocelot配置文件來管理自己的(de)API網(wang)關(guan)。

而關于Ocelot的代碼集(ji)成,主要就是指定配置文件以及注冊Ocelot中間件。核心代碼如下(xia):

public void ConfigureServices(IServiceCollection services)
{
    //..
    services.AddOcelot (new ConfigurationBuilder ()
    .AddJsonFile (Path.Combine ("configuration", "configuration.json"))
    .Build ());
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
     //...
    app.UseOcelot().Wait();
}

請求聚合

在單體應用中時,進行頁面展示時,可以一次性關聯查詢所需的對象并返回,但是對于微服務應用來說,某一個頁面的展示可能需要涉及多個微服務的數據,那如何進行將多個微服務的數據進行聚合呢?首先,不可否認的是,Ocelot提供了請求聚合功能,但是就其靈活性而言,遠不能滿足我們的需求。因此,一般會選擇自定義聚合器來完成靈活的聚合功能。在eShopOnContainers中就是通過獨立ASP.NET Core Web API項目來提供明確的聚合服務。Mobile.Shopping.HttpAggregatorWeb.Shopping.HttpAggregator即是用于(yu)提供自(zi)定義的(de)請求聚合服務。

使用聚合服務的架構

下面就以Web.Shopping.HttpAggregator項目為例來講解自定義聚合的實現思路。
首先,該網關項目是基于ASP.NET Web API構建。其代碼結構如下圖所示:
Web.Shopping.HttpAggregator 自定義聚合服務代碼結構

其核心思路是自定義網關服務借助HttpClient發起請求。我們來看一下BasketService的實現代碼:

public class BasketService : IBasketService
{
    private readonly HttpClient _apiClient;
    private readonly ILogger<BasketService> _logger;
    private readonly UrlsConfig _urls;
    public BasketService(HttpClient httpClient,ILogger<BasketService> logger, IOptions<UrlsConfig> config)
    {
        _apiClient = httpClient;
        _logger = logger;
        _urls = config.Value;
    }
    public async Task<BasketData> GetById(string id)
    {
        var data = await _apiClient.GetStringAsync(_urls.Basket +  UrlsConfig.BasketOperations.GetItemById(id));
        var basket = !string.IsNullOrEmpty(data) ? JsonConvert.DeserializeObject<BasketData>(data) : null;
        return basket;
    }
}

代碼中主要是通過構造函數注入HttpClient,然后方法中借助HttpClient實例發起相應請求。那HttpClient實例是如何(he)注(zhu)冊的(de)呢(ni),我(wo)們來(lai)看下啟動類里服(fu)務(wu)注(zhu)冊邏輯。

public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
    //register delegating handlers
    services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

    //register http services  
    services.AddHttpClient<IBasketService, BasketService>()
        .AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
        .AddPolicyHandler(GetRetryPolicy())
        .AddPolicyHandler(GetCircuitBreakerPolicy());

    services.AddHttpClient<ICatalogService, CatalogService>()
        .AddPolicyHandler(GetRetryPolicy())
        .AddPolicyHandler(GetCircuitBreakerPolicy());

    services.AddHttpClient<IOrderApiClient, OrderApiClient>()
        .AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
        .AddPolicyHandler(GetRetryPolicy())
        .AddPolicyHandler(GetCircuitBreakerPolicy());
    return services;
}

從代碼(ma)中可以看到主要(yao)做(zuo)了(le)三件事:

  1. 注冊HttpClientAuthorizationDelegatingHandler負責為HttpClient構造Authorization請求頭
  2. 注冊IHttpContextAccessor用于獲取HttpContext
  3. 為三個網關服務分別注冊獨立的HttpClient,其中IBasketServieIOrderApiClient需要認證,所以注冊了HttpClientAuthorizationDelegatingHandler用于構造Authorization請求頭。另外,分別注冊了Polly的請求重試和斷路器策略。

HttpClientAuthorizationDelegatingHandler是如何構造Authorization請求頭的呢?直接看代碼(ma)實現:

public class HttpClientAuthorizationDelegatingHandler
     : DelegatingHandler
{
    private readonly IHttpContextAccessor _httpContextAccesor;
    public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccesor)
    {
        _httpContextAccesor = httpContextAccesor;
    }
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var authorizationHeader = _httpContextAccesor.HttpContext
            .Request.Headers["Authorization"];
        if (!string.IsNullOrEmpty(authorizationHeader))
        {
            request.Headers.Add("Authorization", new List<string>() { authorizationHeader });
        }
        var token = await GetToken();
        if (token != null)
        {
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        }
        return await base.SendAsync(request, cancellationToken);
    }
    async Task<string> GetToken()
    {
        const string ACCESS_TOKEN = "access_token";
        return await _httpContextAccesor.HttpContext
            .GetTokenAsync(ACCESS_TOKEN);
    }
}

代碼實現也很簡單:首先從 _httpContextAccesor.HttpContext.Request.Headers["Authorization"]中取,若沒有則從_httpContextAccesor.HttpContext.GetTokenAsync("access_token")中取,拿到訪問令牌后,添加到請求頭request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);即可。

這里你肯定有個疑問就是:為什么不是到Identity microservices去取訪問令牌,而是直接從_httpContextAccesor.HttpContext.GetTokenAsync("access_token")中取訪問令牌?

Good Question,因為對于網關項目而言,其本身也是需要認證的,在訪問網關暴露的需要認證的API時,其已經同Identity microservices協商并獲取到令牌,并將令牌內置到HttpContext中了。所(suo)以,對于同一個請求上下文,我(wo)們僅需(xu)將網關項目(mu)申請到的令牌傳遞下去即可。

Ocelot網關中如何集成認證和授權

不管是獨立的微服務還是網關,認證和授權問題都是要考慮的。Ocelot允許我們直接在網關內的進行身份驗證,如下圖所示:
網關內身份驗證

因為認(ren)證(zheng)授(shou)權作為微服務的(de)交叉(cha)問題,所(suo)以將(jiang)認(ren)證(zheng)授(shou)權作為橫切(qie)關注(zhu)點(dian)設(she)計為獨立的(de)微服務更符(fu)合關注(zhu)點(dian)分離的(de)思想。而Ocelot網關僅需(xu)簡單(dan)的(de)配置即可完(wan)成(cheng)與外部認(ren)證(zheng)授(shou)權服務的(de)集成(cheng)。

1. 配置認證選項
首先在configuration.json配置文件中為需要進行身份驗證保護API的網關設置AuthenticationProviderKey。比如:

{
  "DownstreamPathTemplate": "/api/{version}/{everything}",
  "DownstreamScheme": "http",
  "DownstreamHostAndPorts": [
    {
      "Host": "basket.api",
      "Port": 80
    }
  ],
  "UpstreamPathTemplate": "/api/{version}/b/{everything}",
  "UpstreamHttpMethod": [],
  "AuthenticationOptions": {
    "AuthenticationProviderKey": "IdentityApiKey",
    "AllowedScopes": []
  }
}

2. 注冊認證服務
當Ocelot運行時,它將根據Re-Routes節點中定義的AuthenticationOptions.AuthenticationProviderKey,去確認系統是否注冊了相對應身份驗證提供程序。如果沒有,那么Ocelot將無法啟動。如果有,則ReRoute將在執行時使用該提供程序。
OcelotApiGw的啟動配置中,就注冊了AuthenticationProviderKey:IdentityApiKey的認證服務。

public void ConfigureServices (IServiceCollection services) {
    var identityUrl = _cfg.GetValue<string> ("IdentityUrl");
    var authenticationProviderKey = "IdentityApiKey";
    //…
    services.AddAuthentication ()
        .AddJwtBearer (authenticationProviderKey, x => {
            x.Authority = identityUrl;
            x.RequireHttpsMetadata = false;
            x.TokenValidationParameters = new
            Microsoft.IdentityModel.Tokens.TokenValidationParameters () {
                ValidAudiences = new [] {
                "orders",
                "basket",
                "locations",
                "marketing",
                "mobileshoppingagg",
                "webshoppingagg"
                }
            };
        });
    //...
}

這里需要說明一點的是ValidAudiences用來指定可被允許訪問的服務。其與各個微服務啟動類中ConfigureServices() AddJwtBearer()指定的Audience相對應。比如:

// prevent from mapping "sub" claim to nameidentifier.
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear ();
var identityUrl = Configuration.GetValue<string> ("IdentityUrl");
services.AddAuthentication (options => {
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer (options => {
    options.Authority = identityUrl;
    options.RequireHttpsMetadata = false;
    options.Audience = "basket";
});

3. 按需配置申明進行鑒權
另外有一點不得不提的是,Ocelot支持在身份認證后進行基于聲明的授權。僅需在ReRoute節點下配置RouteClaimsRequirement即可:

"RouteClaimsRequirement": {
 "UserType": "employee"
}

在該示例中,當調用授權中間件時,Ocelot將查找用戶是否在令牌中是否存在UserType:employee的申明。如果不(bu)存(cun)在,則用戶將不(bu)被授權,并(bing)響應403。

最后

經過以上的講解(jie),想必你對eShopOnContainers中如何借助(zhu)API 網(wang)關(guan)模(mo)式解(jie)決客戶端與微服務的通信問題有所(suo)(suo)了解(jie),但其(qi)就是萬(wan)金(jin)油嗎?API 網(wang)關(guan)模(mo)式也有其(qi)缺(que)點(dian)所(suo)(suo)在。

  1. 網關層與內部微服務間的高度耦合。
  2. 網關層可能出現單點故障。
  3. API網關可能導致性能瓶頸。
  4. API網關如果包含復雜的自定義邏輯和數據聚合,額外增加了團隊的開發維護溝通成本。

雖然IT沒有(you)銀彈,但eShopOnContainers中網(wang)關模式的(de)應用案例至(zhi)少指明了一種解(jie)決問題的(de)思(si)路(lu)。而至(zhi)于(yu)在(zai)實(shi)戰場景(jing)中的(de)技術選(xuan)型,適合的(de)就是最好的(de)。

posted @ 2019-03-05 14:13  「圣杰」  閱讀(3200)  評論(7)    收藏  舉報