eShopOnContainers 知多少[9]:Ocelot gateways

引言

客户端与微服务的通信问题永远是一个绕不开的问题,对于小型微服务应用,客户端与微服务可以使用直连的方式进行通信,但对于对于大型的微服务应用我们将不得不面对以下问题:

  1. 如何降低客户端到后台的请求数量,并减少与多个微服务的无效交互?

  2. 如何处理微服务间的交叉问题,比如授权、数据转换和动态请求派发?

  3. 客户端如何与使用非互联网友好协议的服务进行交互?

  4. 如何打造移动端友好的服务?

而解决这一问题的方法之一就是借助API网关,其允许我们按需组合某些微服务以提供单一入口。

接下来,本文就来梳理一下eShopOnContainers是如何集成Ocelot网关来进行通信的。

640?wx_fmt=png

Hello Ocelot

关于Ocelot,张队在Github上贴心的整理了awesome-ocelot系列以便于我们学习。这里就简单介绍下Ocelot,不过多展开。 Ocelot是一个开源的轻量级的基于ASP.NET Core构建的快速且可扩展的API网关,核心功能包括路由、请求聚合、限速和负载均衡,集成了IdentityServer4以提供身份认证和授权,基于Consul提供了服务发现能力,借助Polly实现了服务熔断,能够很好的和k8s和Service Fabric集成。

Ocelot 集成

eShopOnContainers中的以下六个微服务都是通过网关API进行发布的。640?wx_fmt=png

引入网关层后,eShopOnContainers的整体架构如下图所示:640?wx_fmt=png

从代码结构来看,其基于业务边界(Marketing和Shopping)分别为Mobile和Web端建立多个网关项目,这样做利于隔离变化,降低耦合,且保证开发团队的独立自主性。所以我们在设计网关时也应注意到这一点,切忌设计大一统的单一API网关,以避免整个微服务架构体系的过度耦合。在网关设计中应当根据业务和领域去决定API网关的边界,尽量设计细粒度而非粗粒度的API网关。

eShopOnContainers中 ApiGateways文件下是相关的网关项目。相关项目结构如下图所示。

640?wx_fmt=png

从代码结构看,有四个 configuration.json文件,该文件就是ocelot的配置文件,其中主要包含两个节点:

  1. {

  2. "ReRoutes": [],

  3. "GlobalConfiguration": {}

  4. }

那4个独立的配置文件是怎样设计成4个独立的API网关的呢? 在eShopOnContainers中,首先基于 OcelotApiGw项目构建单个Ocelot API网关Docker容器镜像,然后在运行时,通过使用 docker volume分别挂载不同路径下的 configuration.json文件来启动不同类型的API-Gateway容器。示意图如下:640?wx_fmt=png

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

// docker-compse.ymlmobileshoppingapigw: image: eshop/ocelotapigw:${TAG:-latest} build: context: . dockerfile: src/ApiGateways/ApiGw-Base/Dockerfile
// 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镜像,而且开发团队可以专注于团队所属微服务的开发,并通过独立的Ocelot配置文件来管理自己的API网关。

而关于Ocelot的代码集成,主要就是指定配置文件以及注册Ocelot中间件。核心代码如下:

publicvoidConfigureServices(IServiceCollection services){    //..    services.AddOcelot(newConfigurationBuilder().AddJsonFile(Path.Combine("configuration","configuration.json")).Build());}publicvoidConfigure(IApplicationBuilder app,IHostingEnvironment env){    //...    app.UseOcelot().Wait();}

请求聚合

在单体应用中时,进行页面展示时,可以一次性关联查询所需的对象并返回,但是对于微服务应用来说,某一个页面的展示可能需要涉及多个微服务的数据,那如何进行将多个微服务的数据进行聚合呢?首先,不可否认的是,Ocelot提供了请求聚合功能,但是就其灵活性而言,远不能满足我们的需求。因此,一般会选择自定义聚合器来完成灵活的聚合功能。在eShopOnContainers中就是通过独立ASP.NET Core Web API项目来提供明确的聚合服务。 Mobile.Shopping.HttpAggregatorWeb.Shopping.HttpAggregator即是用于提供自定义的请求聚合服务。

640?wx_fmt=png

下面就以 Web.Shopping.HttpAggregator项目为例来讲解自定义聚合的实现思路。 首先,该网关项目是基于ASP.NET Web API构建。其代码结构如下图所示:640?wx_fmt=png

其核心思路是自定义网关服务借助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实例是如何注册的呢,我们来看下启动类里服务注册逻辑。

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;}

从代码中可以看到主要做了三件事:

  1. 注册 HttpClientAuthorizationDelegatingHandler负责为HttpClient构造 Authorization请求头

  2. 注册 IHttpContextAccessor用于获取 HttpContext

  3. 为三个网关服务分别注册独立的 HttpClient,其中 IBasketServie和 IOrderApiClient需要认证,所以注册了 HttpClientAuthorizationDelegatingHandler用于构造 Authorization请求头。另外,分别注册了 Polly的请求重试和断路器策略。

HttpClientAuthorizationDelegatingHandler是如何构造 Authorization请求头的呢?直接看代码实现:

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=newAuthenticationHeaderValue("Bearer",token);即可。

这里你肯定有个疑问就是:为什么不是到Identity microservices去取访问令牌,而是直接从 _httpContextAccesor.HttpContext.GetTokenAsync("access_token")中取访问令牌?

Good Question,因为对于网关项目而言,其本身也是需要认证的,在访问网关暴露的需要认证的API时,其已经同Identity microservices协商并获取到令牌,并将令牌内置到 HttpContext中了。所以,对于同一个请求上下文,我们仅需将网关项目申请到的令牌传递下去即可。

Ocelot网关中如何集成认证和授权

不管是独立的微服务还是网关,认证和授权问题都是要考虑的。Ocelot允许我们直接在网关内的进行身份验证,如下图所示:640?wx_fmt=png

因为认证授权作为微服务的交叉问题,所以将认证授权作为横切关注点设计为独立的微服务更符合关注点分离的思想。而Ocelot网关仅需简单的配置即可完成与外部认证授权服务的集成。

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的启动配置中,就注册了 AuthenticationProviderKeyIdentityApiKey的认证服务。

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即可:

  1. "RouteClaimsRequirement": {

  2. "UserType": "employee"

  3. }

在该示例中,当调用授权中间件时,Ocelot将查找用户是否在令牌中是否存在 UserType:employee的申明。如果不存在,则用户将不被授权,并响应403。

最后

经过以上的讲解,想必你对eShopOnContainers中如何借助API 网关模式解决客户端与微服务的通信问题有所了解,但其就是万金油吗?API 网关模式也有其缺点所在。

  1. 网关层与内部微服务间的高度耦合。

  2. 网关层可能出现单点故障。

  3. API网关可能导致性能瓶颈。

  4. API网关如果包含复杂的自定义逻辑和数据聚合,额外增加了团队的开发维护沟通成本。

虽然IT没有银弹,但eShopOnContainers中网关模式的应用案例至少指明了一种解决问题的思路。而至于在实战场景中的技术选型,适合的就是最好的。

原文地址:http://www.cnblogs.com/sheng-jie/p/10476436.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com
640?wx_fmt=jpeg


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/316923.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Acwing 1072. 树的最长路径

Acwing 1072. 树的最长路径 题意&#xff1a; 每个边有权值&#xff0c;求树的直径 题解&#xff1a; 两遍dfs可以求&#xff0c;这里用树形dp的方法&#xff0c;我们将1作为根节点来看这棵树 我们可以将点看作是钉子&#xff0c;边就是挂在钉子上的绳子&#xff0c;我们只…

.NET/C# 获取一个正在运行的进程的命令行参数

在自己的进程内部&#xff0c;我们可以通过 Main 函数传入的参数&#xff0c;也可以通过 Environment.GetCommandLineArgs 来获取命令行参数。但是&#xff0c;可以通过什么方式来获取另一个运行着的程序的命令行参数呢&#xff1f;进程内部获取传入参数的方法&#xff0c;可以…

听说,霸都.NET技术社区准备搞线下聚会了?

.NET Core实战项目交流群日常交流嗨&#xff0c;你听说了没有&#xff1f;霸都.NET技术社区准备搞线下聚会了&#xff01;啥时候的事情啊&#xff1f;最近才知道的消息啊&#xff01;那你是从哪里知道的消息呢&#xff1f;.NET Core项目实战交流群&#xff08;637326624&#x…

P4383 [八省联考 2018] 林克卡特树(wqs二分、树形dp)

解析 它还真的不难。 乐。 这题没做出来有些谔谔。 外层wqs二分显而易见&#xff0c;里面不知道为啥我总觉得这个题可以贪心。 然后一直试图在原树直径上下功夫&#xff0c;一筹莫展。 看到题解“dp”两个字这题也就做完了… 就相当于要把一棵树分成若干条无交链&#xff0c;每…

ASP.NET Core中实现单体程序的事件发布/订阅 - LamondLu - 博客园

标题&#xff1a;ASP.NET Core中实现单体程序的事件发布/订阅作者&#xff1a;Lamond Lu地址&#xff1a;https://www.cnblogs.com/lwqlun/p/10468058.html项目源代码&#xff1a;https://github.com/lamondlu/EventHandlerInSingleApplication背景事件发布/订阅是一种非常强大…

Joy of Handcraft Gym - 102822J(线段树或差分)

Joy of Handcraft Gym - 102822J 题意&#xff1a; 每个灯有亮的周期和亮度&#xff0c;问1~m这段时间灯光最亮是多少 题解&#xff1a; 线段树维护区间最大值 根据灯的周期向这段区间加亮度k&#xff0c;然后利用线段树维护区间最大值 但是这样会超时&#xff0c;加个小优…

.NET Core 使用 HttpClient SSL 请求出错的解决办法

问题使用 HTTP Client 请求 HTTPS 的 API 时出现 The certificate cannot be verified up to a trusted certification authority 异常&#xff0c;并且证书已经传入。下面就是问题代码&#xff1a;public class Program{public static void Main(string[] args){var url &quo…

微软4年后重登市值第一,纳德拉如何做到的?

他用4年多时间将微软的市值提高了5000亿美元&#xff0c;超越苹果再次成为全球市值最高的上市公司。译 | 达达萨提亚纳德拉&#xff08;Satya Nadella&#xff09;2014年刚刚执掌微软时&#xff0c;微软当时是一个日渐没落的帝国。但在他领导的4年多时间里&#xff0c;微软百花…

【.NET Core项目实战-统一认证平台】第十五章 网关篇-使用二级缓存提升性能

首先说声抱歉&#xff0c;可能是因为假期综合症&#xff08;其实就是因为懒哈&#xff09;的原因&#xff0c;已经很长时间没更新博客了&#xff0c;现在也调整的差不多了&#xff0c;准备还是以每周1-2篇的进度来更新博客&#xff0c;并完成本项目所有功能。言归正传&#xff…

NET Core微服务之路:简单谈谈对ELK,Splunk,Exceptionless统一日志收集中心的心得体会...

前言日志&#xff0c;一直以来都是开发人员和运维人员最关心的问题。开发人员可通过日志记录来协助问题定位&#xff0c;运维人员可通过日志发现系统隐患&#xff0c;故障等定位问题。如果你的系统中没有日志&#xff0c;就像一个断了线的风筝&#xff0c;你永远不知道它会的落…

.NET Core 中的 Generic Host快速使用指南

本文以自己在工作中学习和使用.net core generic-host 作一个总结。前言在创建的ASPNETCORE项目中&#xff0c;我们可以在Main()中看见&#xff0c;我们通过IWebHostBuild创建了一个IWebHost&#xff0c;而微软提供了WebHost.CreateDefaultBuilder(args)来帮助我们更轻松得创建…

微软一顿操作猛如虎,PowerShell 排名直线上升

近日&#xff0c;TIOBE 发布了 2019 年 3 月编程语言排行榜&#xff0c;PowerShell 首次进入到了榜单的 Top 50 中&#xff0c;排在第 45 位。PowerShell 是运行在 Windows 操作系统上实现对系统以及应用程序进行管理自动化的命令行脚本环境。&#xff08;PowerShell 排在了 TI…

AcWing 201. 可见的点

AcWing 201. 可见的点 题意&#xff1a; 题解&#xff1a; 我们先说结论:坐标(i,j)&#xff0c;如果i和j互质&#xff0c;说明该坐标为可见 为什么&#xff1f; 我们想想什么样的坐标可见&#xff0c;什么样的会被挡住。光线是一个直线&#xff0c;在同一个直线上的点会被第一…

ocelot 自定义认证和授权

Intro最近又重新启动了网关项目&#xff0c;服务越来越多&#xff0c;每个服务都有一个地址&#xff0c;这无论是对于前端还是后端开发调试都是比较麻烦的&#xff0c;前端需要定义很多 baseUrl&#xff0c;而后端需要没有代码调试的时候需要对每个服务的地址都收藏着或者记在哪…

CF765F Souvenirs(势能线段树)

CF765F Souvenirsproblemsolutioncodeproblem 题目链接 solution 这个势能线段树简直是太巧妙了&#xff01;&#xff01;&#xff01;( ఠൠఠ )&#xff89; 将询问按右端点升序离线下来。 对于每一个右端点 rrr&#xff0c;维护 ansimin⁡{∣ai−aj∣,j∈[i,r]}ans_i\m…

AcWing 220. 最大公约数

AcWing 220. 最大公约数 题意&#xff1a; 题解&#xff1a; 题目就变成了AcWing 201. 可见的点 当然有微调&#xff0c;因为可见的点里面是从0开始&#xff0c;本题从1开始&#xff0c;所以本题中phi[1]认为是0 AcWing 201. 可见的点的题解 代码&#xff1a; #include<b…

欧拉函数(简单介绍+例题)

Acwing视频讲解 欧拉函数&#xff1a;正整数n&#xff0c;欧拉函数是小于n的正整数中与n互质的数的数目 Np1a1 * p1a2 * p1a3 * …* p1ak 如果pj是i的最小质因子 红色区域一样 经推导得&#xff1a;phi[i * pj] phi[i] * pj 如果pj不是i的最小质因子 经推导&#xff1a;phi[…

程序员过关斩将--你的面向接口编程一定对吗?

菜菜哥&#xff0c;出大事啦怎么了&#xff0c;你和男票分手了&#xff1f;很正常&#xff0c;谁让你男票是产经经理呢不是啦&#xff0c;是我做的一个小游戏&#xff0c;需求又变了&#xff0c;程序我快改不动了说来让我欢乐一下&#xff1f;菜菜哥&#xff0c;咱两还能不能好…

Codeforces:779(div2)

前言 solve 4 rnk247 占了罚时的便宜。 CF不占罚时便宜就会被罚时占便宜 感觉这场似乎都是性质题&#xff0c;一眼看出性质就秒了&#xff0c;看不出就很难做出来了。 C似乎卡了很多人。但我做起来还好。 D2做不出来有些懊恼。 E是妙题。 题目 A 水题&#xff0c;保证male…

我们为什么要搞长沙.NET技术社区(4)

我们为什么要搞长沙.NET技术社区&#xff08;4&#xff09;邹溪源&#xff0c;2019年3月7日Ps:文中的.NET 包括且不限定于传统.NET Framework技术和.NET Core技术。1. 楔子昨天&#xff08;2019年3月6日&#xff09;晚餐时间&#xff0c;有幸得到长沙技术圈资深.NET开发者出生…