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;我们只…

模板:矩阵树定理

文章目录前言解析无向图有向图根向树叶向树code带权图code所谓矩阵树定理&#xff0c;就是用矩阵解决树问题的定理。 &#xff08;逃&#xff09; 前言 神奇科技。 之前一直没有写博客&#xff0c;觉得还是写一发比较好。 证明什么的是不可能会的 背下来背下来&#xff01; …

P2016 战略游戏

P2016 战略游戏 题意&#xff1a; 他要建立一个古城堡&#xff0c;城堡中的路形成一棵无根树。他要在这棵树的结点上放置最少数目的士兵&#xff0c;使得这些士兵能了望到所有的路。 注意&#xff0c;某个士兵在一个结点上时&#xff0c;与该结点相连的所有边将都可以被了望…

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

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

CF896E Welcome home,Chtholly/[Ynoi2018]五彩斑斓的世界(并查集+第二分块)

CF896E Welcome home,Chtholly/[Ynoi2018]五彩斑斓的世界descriptionsolutioncodedescription 五彩斑斓的世界 CF896E Welcome home,Chtholly 五彩斑斓的世界是加强版&#xff0c;所以下面的题解部分是写的加强版 solution 第二分块 查询操作中把>x>x>x的数全都…

Defuse the Bombs Gym - 102822D

Defuse the Bombs Gym - 102822D 题目&#xff1a; 给你n个数&#xff0c;现在每轮会有三个操作&#xff1a; 1.选择一个数&#xff0c;使他加一 2.所有数减一 3.当有一个数变成负数时结束操作&#xff0c;否则回到第一步 问最多能进行几次第一步&#xff1f; 题解&#xff…

P4364 [九省联考 2018] IIIDX(线段树、贪心)

解析 感觉不至于黑的题。 然而我并不会做 did_idi​ 互不相同的时候直接无脑贪心即可&#xff0c;这样55分的好成绩就到手了。&#xff08;交完发现可以骗到60&#xff09; 滚榜级的良心分了属于是。 考虑有相同时如何做。 先把值降序排序&#xff0c;然后维护一棵线段树&…

CF1491H Yuezheng Ling and Dynamic Tree(分块)

CF1491H Yuezheng Ling and Dynamic Treedescriptionsolutioncodedescription 题目链接 solution 非常清新的小分块题了 前提&#xff1a;将序列分成n\sqrt{n}n​块&#xff0c;每块有n\sqrt{n}n​个数&#xff0c;记第iii个块的左右边界为Li,RiL_i,R_iLi​,Ri​&#xff0…

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

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

Knowledge is Power Gym - 102822K

Knowledge is Power Gym - 102822K 题意&#xff1a; 给你一个数n&#xff0c;让你将n分解成一些互质的数&#xff0c;然后这些数的最大值减最小值要求最小&#xff0c;如果不行输出-1&#xff0c;否则输出最大值减最小值的最小情况 题解&#xff1a; 具体做法是通过枚举大…

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背景事件发布/订阅是一种非常强大…

CF1592E Bored Bakry(二进制+前缀异或和)

CF1592E Bored Bakrydescriptionsolutioncodedescription 题目链接 solution and\text{and}and如果第iii位为111&#xff0c;意味着区间内每个数的第iii位都是111 xor\text{xor}xor如果第iii位为111&#xff0c;意味着区间内有奇数个第iii位为111 这种涉及二进制操作的一般都…

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…

CF1580C Train Maintenance(分块)

CF1580C Train Maintenancedescriptionsolutioncodedescription 题目链接 solution 这是一种利用根号平衡时间复杂度的套路 分α\alphaα【操作参数】与n\sqrt{n}n​的关系&#xff0c;一半采取暴力&#xff0c;一半利用工具特殊处理 对于本题&#xff0c;假设第iii辆车的加…

P3746 [六省联考 2017] 组合数问题(倍增、dp)

解析 再次被“组合数问题”吊打qwq 和上一次不一样的是&#xff0c;这次更加被恶心到了。 一方面受上一个组合数问题影响&#xff0c;另外出题人也十分阴间&#xff0c;一开始还给了个组合数的公式&#xff0c;更加使我坚定的认为这是一道数学推柿子题。 然后就开始各种打表玩…

CF1473E Minimum Path(拆点+最短路)

CF1473E Minimum Pathdescriptionsolutioncodedescription 题目链接 solution 看到 ∑i1kwei\sum_{i1}^kw_{e_i}∑i1k​wei​​ 的式子&#xff0c;就应该联想到最短路 先考虑题目的弱化版&#xff0c;去掉 max,min\text{max},\text{min}max,min 的限制&#xff0c;变成一条…

2020CCPC绵阳

2020CCPC绵阳 题号题目名难度知识点AA Colorful GridBBuilding BlocksCCode a TrieDDefuse the Bombs签到二分EEscape from the IslandFFracture RayGGame of Cards银牌博弈论&#xff0c;SG函数HHide and SeekIInvaluable AssetsJJoy of Handcraft快铜线段树KKnowledge is Po…

P3747 [六省联考 2017] 相逢是问候(欧拉定理、线段树、光速幂)

解析 洛谷你恶事做尽&#xff01; 第三个tag在LOJ、bzoj等都是不需要的… 但在洛谷三只log根本过不去… 我谔谔。 如果做过 上帝与集合的正确用法 &#xff0c;那么本题就并不难了。 打个表就可以发现&#xff0c;不断取欧拉函数的上限只有log级别&#xff0c;这使得我们暴力…