eShopOnContainers 知多少[9]:Ocelot gateways
引言
客户端与微服务的通信问题永远是一个绕不开的问题,对于小型微服务应用,客户端与微服务可以使用直连的方式进行通信,但对于对于大型的微服务应用我们将不得不面对以下问题:
- 如何降低客户端到后台的请求数量,并减少与多个微服务的无效交互?
- 如何处理微服务间的交叉问题,比如授权、数据转换和动态请求派发?
- 客户端如何与使用非互联网友好协议的服务进行交互?
- 如何打造移动端友好的服务?
而解决这一问题的方法之一就是借助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的配置文件,其中主要包含两个节点:
{
"ReRoutes": [],
"GlobalConfiguration": {}
}
那4个独立的配置文件是怎样设计成4个独立的API网关的呢?
在eShopOnContainers中,首先基于OcelotApiGw
项目构建单个Ocelot API网关Docker容器镜像,然后在运行时,通过使用docker volume
分别挂载不同路径下的configuration.json
文件来启动不同类型的API-Gateway容器。示意图如下:
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=http://identity.api
ports:
- "5200:80"
volumes:
- ./src/ApiGateways/Mobile.Bff.Shopping/apigw:/app/configuration
通过这种方式将API网关分成多个API网关,不仅可以同时重复使用相同的Ocelot Docker镜像,而且开发团队可以专注于团队所属微服务的开发,并通过独立的Ocelot配置文件来管理自己的API网关。
而关于Ocelot的代码集成,主要就是指定配置文件以及注册Ocelot中间件。核心代码如下:
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.HttpAggregator
和Web.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;
}
从代码中可以看到主要做了三件事:
- 注册
HttpClientAuthorizationDelegatingHandler
负责为HttpClient构造Authorization
请求头 - 注册
IHttpContextAccessor
用于获取HttpContext
- 为三个网关服务分别注册独立的
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 = new AuthenticationHeaderValue("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
即可:
"RouteClaimsRequirement": {
"UserType": "employee"
}
在该示例中,当调用授权中间件时,Ocelot将查找用户是否在令牌中是否存在UserType:employee
的申明。如果不存在,则用户将不被授权,并响应403。
最后
经过以上的讲解,想必你对eShopOnContainers中如何借助API 网关模式解决客户端与微服务的通信问题有所了解,但其就是万金油吗?API 网关模式也有其缺点所在。
- 网关层与内部微服务间的高度耦合。
- 网关层可能出现单点故障。
- API网关可能导致性能瓶颈。
- API网关如果包含复杂的自定义逻辑和数据聚合,额外增加了团队的开发维护沟通成本。
虽然IT没有银弹,但eShopOnContainers中网关模式的应用案例至少指明了一种解决问题的思路。而至于在实战场景中的技术选型,适合的就是最好的。
eShopOnContainers 知多少[9]:Ocelot gateways的更多相关文章
- eShopOnContainers 知多少[12]:Envoy gateways
1. 引言 在最新的eShopOnContainers 3.0 中Ocelot 网关被Envoy Proxy 替换.下面就来简要带大家了解下Envoy,并尝试梳理下为什么要使用Envoy替代Ocelo ...
- eShopOnContainers 知多少[1]:总体概览
引言 在微服务大行其道的今天,Java阵营的Spring Boot.Spring Cloud.Dubbo微服务框架可谓是风水水起,也不得不感慨Java的生态圈的火爆.反观国内.NET阵营,微服务却不愠 ...
- eShopOnContainers 知多少[8]:Ordering microservice
1. 引言 Ordering microservice(订单微服务)就是处理订单的了,它与前面讲到的几个微服务相比要复杂的多.主要涉及以下业务逻辑: 订单的创建.取消.支付.发货 库存的扣减 2. 架 ...
- eShopOnContainers 知多少[5]:EventBus With RabbitMQ
1. 引言 事件总线这个概念对你来说可能很陌生,但提到观察者(发布-订阅)模式,你也许就很熟悉.事件总线是对发布-订阅模式的一种实现.它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需 ...
- eShopOnContainers 知多少[3]:Identity microservice
首先感谢晓晨Master和EdisonChou的审稿!也感谢正在阅读的您! 引言 通常,服务所公开的资源和 API 必须仅限受信任的特定用户和客户端访问.那进行 API 级别信任决策的第一步就是身份认 ...
- eShopOnContainers 知多少[10]:部署到 K8S | AKS
1. 引言 断断续续,感觉这个系列又要半途而废了.趁着假期,赶紧再更一篇,介绍下如何将eShopOnContainers部署到K8S上,进而实现大家常说的微服务上云. 2. 先了解下 Helm 读过我 ...
- eShopOnContainers 知多少[6]:持久化事件日志
1. 引言 事件总线解决了微服务间如何基于集成事件进行异步通信的问题.然而只有事件总线正常运行,微服务之间基于事件的通信才得以运转. 而现实情况是,总有这样或那样的问题,导致事件总线不稳定或不可用,比 ...
- eShopOnContainers 知多少[4]:Catalog microservice
引言 Catalog microservice(目录微服务)维护着所有产品信息,包括库存.价格.所以该微服务的核心业务为: 产品信息的维护 库存的更新 价格的维护 架构模式 如上图所示,本微服务采用简 ...
- eShopOnContainers 知多少[2]:Run起来
环境准备 Win10(开启Hyper-V) .NET Core SDK Docker for Windows VS2017 or VS Code Git SQL Server Management S ...
随机推荐
- 《Servlet与JSP核心编程》读书笔记
这本书实际是我进入JavaWeb开发的入门书籍,而且是日常碰到一些技术问题需要确认时的参考书,前一段时间在解决一个他人的问题时,我突然发现我的第一遍阅读对这本书的内容的理解还不够透彻,所以又开始N多年 ...
- CSS样式渐变代码,兼容IE8
background: -webkit-linear-gradient(top,#ffffff,#f5f5f5); background: -moz-linear-gradient(top,#ffff ...
- 对cordova插件配置文件plugin.xml的理解
1.配置文件表头包括了插件id,是用于唯一标识插件的.同时插件配置了一个插件名称. 2.这个文件从工作机制,也就是js代码一直到native的java插件代码工作分成两个流程.第一个流程是从代码到插件 ...
- Android Zxing 转换竖屏扫描且提高识别率
最近的一个Android需要用到扫码功能,用的是Zxing开源库.Zxing的集成就不说了,但是Zxing默认的是横屏扫码,在实际生产中并不适用,需要改为竖屏扫描. 转竖屏步骤: 1>. And ...
- 压力测试工具ab - Apache HTTP server benchmarking tool
搞互联网开发,压力测试必不可少.压力测试的工具很多,我用过ab和JMeter,今天主要讲ab的用法. 1.ab是什么 ab is a tool for benchmarking your Apache ...
- Spring中的Lookup(方法注入)
在使用Spring时,可能会遇到这种情况:一个单例的Bean依赖另一个非单例的Bean.如果简单的使用自动装配来注入依赖,就可能会出现一些问题,如下所示: 单例的Class A @Component ...
- SQL Server 2008更改数据库保存路径
本文由荒原之梦原创,原文链接:http://zhaokaifeng.com/?p=641 操作环境: WindowsXP 数据库: Microsoft SQL Server 2008 操作步骤: 选中 ...
- Loadrunner三种post格式的请求
Loadrunner三种post格式的请求 web_custom_request intweb_custom_request(const char *RequestName, <List of ...
- jQuery.on() 函数详解 【转载】
注意事项 1:on()为指定元素的一个或多个事件绑定事件处理函数.(可传递参数) 2:从jQuery 1.7开始,on()函数提供了绑定事件处理程序所需的所有功能,用于统一取代以前的bind(). d ...
- java 泛型详解(普通泛型、 通配符、 泛型接口,泛型数组,泛型方法,泛型嵌套)
JDK1.5 令我们期待很久,可是当他发布的时候却更换版本号为5.0.这说明Java已经有大幅度的变化.本文将讲解JDK5.0支持的新功能-----Java的泛型. 1.Java泛型 其实Java ...