引言

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

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

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

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

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

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

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

Hello Ocelot

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

Ocelot 集成

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

引入网关层后,eShopOnContainers的整体架构如下图所示:

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

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

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

  1. {

  2. "ReRoutes": [],

  3. "GlobalConfiguration": {}

  4. }

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

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即是用于提供自定义的请求聚合服务。

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

其核心思路是自定义网关服务借助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允许我们直接在网关内的进行身份验证,如下图所示:

因为认证授权作为微服务的交叉问题,所以将认证授权作为横切关注点设计为独立的微服务更符合关注点分离的思想。而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的启动配置中,就注册了 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即可:

  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

eShopOnContainers 知多少[9]:Ocelot gateways相关推荐

  1. eShopOnContainers 知多少[12]:Envoy gateways

    1. 引言 在最新的eShopOnContainers  3.0 中Ocelot 网关被Envoy Proxy 替换.下面就来简要带大家了解下Envoy,并尝试梳理下为什么要使用Envoy替代Ocel ...

  2. eShopOnContainers 知多少[8]:Ordering microservice

    1. 引言 Ordering microservice(订单微服务)就是处理订单的了,它与前面讲到的几个微服务相比要复杂的多.主要涉及以下业务逻辑: 订单的创建.取消.支付.发货 库存的扣减 2. 架 ...

  3. eShopOnContainers 知多少[5]:EventBus With RabbitMQ

    1. 引言 事件总线这个概念对你来说可能很陌生,但提到观察者(发布-订阅)模式,你也许就很熟悉.事件总线是对发布-订阅模式的一种实现.它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需 ...

  4. eShopOnContainers 知多少[3]:Identity microservice

    首先感谢晓晨Master和EdisonChou的审稿!也感谢正在阅读的您! 引言 通常,服务所公开的资源和 API 必须仅限受信任的特定用户和客户端访问.那进行 API 级别信任决策的第一步就是身份认 ...

  5. eShopOnContainers 知多少[2]:Run起来

    环境准备 Win10(开启Hyper-V) .NET Core SDK Docker for Windows VS2017 or VS Code Git SQL Server Management S ...

  6. eShopOnContainers 知多少[11]:服务间通信之gRPC

    1. 引言 最近翻看最新3.0 eShopOncontainers源码,发现其在架构选型中补充了 gRPC 进行服务间通信.那就索性也写一篇,作为系列的补充. 2. gRPC 老规矩,先来理一下gRP ...

  7. eShopOnContainers 知多少[10]:部署到 K8S | AKS

    1. 引言 断断续续,感觉这个系列又要半途而废了.趁着假期,赶紧再更一篇,介绍下如何将eShopOnContainers部署到K8S上,进而实现大家常说的微服务上云. 2. 先了解下 Helm 读过我 ...

  8. eShopOnContainers 知多少[6]:持久化事件日志

    1. 引言 事件总线解决了微服务间如何基于集成事件进行异步通信的问题.然而只有事件总线正常运行,微服务之间基于事件的通信才得以运转. 而现实情况是,总有这样或那样的问题,导致事件总线不稳定或不可用,比 ...

  9. eShopOnContainers 知多少[4]:Catalog microservice

    引言 Catalog microservice(目录微服务)维护着所有产品信息,包括库存.价格.所以该微服务的核心业务为: 产品信息的维护 库存的更新 价格的维护 架构模式 如上图所示,本微服务采用简 ...

最新文章

  1. Mrtg搭建流量监控服务器
  2. 【docker】常用docker命令,及一些坑
  3. leetcode 144 --- 二叉树前序遍历
  4. 高并发负载均衡——网络协议原理
  5. 集群IPtables转发与防火墙
  6. 微信第一个“小程序”亮相:不是APP胜似APP!
  7. 吴恩达深度学习 —— 2.5 导数
  8. linux 进程内存分布,linux C++ 的内存分布情况
  9. 6-C/C++实现数据结构链表相关操作
  10. 解决 vs2010问题 error MSB8008: 指定的平台工具集(v110)未安装或无效
  11. [Ext JS 4] 实战之 ComboBox 和 DateField (消失之解决办法)
  12. LCD1602的四线驱动
  13. 搜索引擎的排序技术综述
  14. 一文读懂nginx gzip
  15. 用户登录,前后端如何交互判断是否登录超时!
  16. selenium网页截图总结
  17. alert获取输入框内容_Alert弹出框处理
  18. JS JQuery实现简单的鼠标移动动画效果
  19. TSP之动态规划找最优解
  20. antd design 引用样式不生效问题

热门文章

  1. 禁用删除键退回历史记录_如何在Windows 8中删除或禁用搜索超级按钮历史记录
  2. 数据同步云端本地_如何从云端删除Windows 8的同步数据
  3. android+notepad教程,Android Sample学习——NotePad
  4. [置顶] C#中通过调用webService获取上网IP地址的区域的方法
  5. 计算机与操作系统基础小结
  6. JS多个对象添加到一个对象中
  7. java dateTime + long
  8. LateUpdate、Late、FixedUpdate的意义
  9. 解决iPhone网络软件在睡眠情况断线问题
  10. [reship]某类领导