.Net微服务实践(三):Ocelot配置路由和请求聚合 .Net微服务实践(三):Ocelot配置路由和请求聚合

.Net微服务实践(三):Ocelot配置路由和请求聚合

在上篇.Net微服务实践(二):Ocelot介绍和快速开始中我们介绍了Ocelot,创建了一个Ocelot Hello World程序,接下来,我们会介绍Oclot的主要特性路由和另外一个特性请求聚合。这些特性都是通过配置来实现的。

配置

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

Ocelot的配置文件包含两个节点: ReRoutes和GlobalConfiguration

  • ReRoutes - 告诉Ocelot如何处理上游的请求
  • GlobalConfiguration - 全局配置,此节点的配置允许覆盖ReRoutes里面的配置,你可以在这里进行通用的一些配置信息

Ocelot的完整配置项如下

{
          "DownstreamPathTemplate": "/",
          "UpstreamPathTemplate": "/",
          "UpstreamHttpMethod": [ "Get" ], "DownstreamHttpMethod": "", "DownstreamHttpVersion": "", "AddHeadersToRequest": {}, "AddClaimsToRequest": {}, "RouteClaimsRequirement": {}, "AddQueriesToRequest": {}, "RequestIdKey": "", "FileCacheOptions": { "TtlSeconds": 0, "Region": "" }, "ReRouteIsCaseSensitive": false, "ServiceName": "", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 51876, } ], "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 0, "DurationOfBreak": 0, "TimeoutValue": 0 }, "LoadBalancer": "", "RateLimitOptions": { "ClientWhitelist": [], "EnableRateLimiting": false, "Period": "", "PeriodTimespan": 0, "Limit": 0 }, "AuthenticationOptions": { "AuthenticationProviderKey": "", "AllowedScopes": [] }, "HttpHandlerOptions": { "AllowAutoRedirect": true, "UseCookieContainer": true, "UseTracing": true, "MaxConnectionsPerServer": 100 }, "DangerousAcceptAnyServerCertificateValidator": false } 

完整配置项中的每一项具体含义和作用接下来会一一介绍,大的配置项的主要含义如下:

  • Downstream - 下游服务配置
  • UpStream - 上游服务配置
  • Aggregates - 服务聚合配置
  • ServiceName, LoadBalancer, UseServiceDiscovery - 配置服务发现
  • AuthenticationOptions - 配置服务认证
  • RouteClaimsRequirement - 配置Claims鉴权
  • RateLimitOptions - 限流配置
  • FileCacheOptions - 缓存配置
  • QosOptions - 服务质量与熔断
  • DownstreamHeaderTransform - 头信息转发

路由

基本配置

在上一篇的hello world程序中使用的就是基本配置

{
  "ReRoutes": [
    {
      "DownstreamPathTemplate": "/api/orders",
      "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5001 } ], "UpstreamPathTemplate": "/api/orders", "UpstreamHttpMethod": [ "Get" ] } ], "GlobalConfiguration": { "BaseUrl": "http://localhost:5000" } } 
  • BaseUrl - Ocelot的服务运行地址,要特别注意一下BaseUrl是我们外部暴露的Url,比如我们的Ocelot运行在http://localhost:5000,但是前面有一个 nginx绑定了域名http://api.demo.com,那这里我们的BaseUrl就是 http://api.demo.com
  • UpstreamPathTemplate、UpstreamHttpMethod - 配置上游服务器请求URL
  • DownstreamPathTemplate、DownstreamScheme、DownstreamHostAndPorts - 配置下游服务器请求URL

在基本配置的示例中:要实现的功能就是将 http://localhost:5000/api/orders GET 请求路由到 http://localhost:5001/api/orders GET

占位符

在Ocelot中,可以以{something}的形式将变量的占位符添加到模板中。占位符变量需要同时出现在DownstreamPathTemplate和UpstreamPathTemplate属性中。请求时Ocelot将尝试请求时进行替换

{
      "DownstreamPathTemplate": "/api/{everything}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5002 } ], "UpstreamPathTemplate": "/api/{everything}", "UpstreamHttpMethod": [ "Get" ] } 

示例说明:所有http://localhost:5000/api/XXXXXX的请求都会路由到http://localhost:5002/api/XXXXXX

例如http://localhost:5000/api/products 路由到 http://localhost:5002/api/products

例如http://localhost:5000/api/products/1 路由到 http://localhost:5002/api/products/1

验证

修改配置,运行示例程序, 访问http://localhost:5000/api/products,返回了产品数据

注意:在添加Ocelot.json文件时 .AddJsonFile("Ocelot.json",false,true), 第三个参数是指定文件发生变化时,是否重新加载,示例程序中是true. 所以我们只要修改运行目录下的配置文件,不用重新运行示例程序。

万能模板

既然占位符可以做通用匹配,自然而然就有一种配置可以匹配所有请求

{
      "DownstreamPathTemplate": "/{url}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5002 } ], "UpstreamPathTemplate": "/{url}", "UpstreamHttpMethod": [ "Get" ] } 

示例说明: 转发所有的请求到http://localhost:5002

验证

修改配置,运行示例程序, 访问http://localhost:5000/api/products,返回了产品数据

优先级

如果一个上游请求有多个路由配置都能匹配,到底该使用哪个路由呢? 路由可以配置优先级(Priority), 0最小,路由会使用优先级高的(说明:如果多个匹配路由优先级一样,则按顺序使用第一个)

  • 在product-api中添加一个category api
[ApiController]
public class CategoryController : ControllerBase { // GET: api/Product [Route("api/categories")] [HttpGet] public IEnumerable<string> Get() { return new string[] { "电子产品", "医护用品" }; } } 
  • 修改Ocelot.json配置文件如下
{
  "DownstreamPathTemplate": "/api/products",
  "DownstreamScheme": "http",
  "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5002 } ], "UpstreamPathTemplate": "/api/products", "UpstreamHttpMethod": [ "Get" ], "Priority": 0 }, { "DownstreamPathTemplate": "/api/categories", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5002 } ], "UpstreamPathTemplate": "/api/{everything}", "UpstreamHttpMethod": [ "Get" ], "Priority": 1 } 

如果这时访问http://localhost:5000/api/products, 大家猜一下,是返回产品数据还是类别数据?

验证

修改配置,运行示例程序, 访问http://localhost:5000/api/products,返回了类别数据, 因为类别路由的优先级是1, 优先级更高

查询参数

  • 在order-api中添加一个订单明细的api\
[Route("api/orders/{id}")]
[HttpGet]
public string Get(int id) { string order = string.Empty; switch(id) { case 1: order = "刘明的订单"; break; case 2: order = "王天的订单"; break; default: order = "没有找到订单"; break; } return order; } 
  • 修改Ocelot.json配置如下
{
  "DownstreamPathTemplate": "/api/orders/{id}",
  "DownstreamScheme": "http",
  "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5001 } ], "UpstreamPathTemplate": "/api/orders?id={id}", "UpstreamHttpMethod": [ "Get" ] } 

我们期望的结果是,当访问http://localhost:5000/api/orders?id=1 (下游服务实际没这个接口)时 路由到http://localhost:5001/api/orders/1返回订单明细

验证

修改配置,运行示例程序, 访问http://localhost:5000/api/orders?id=1,返回了订单明细数据

请求聚合

有一种场景,前端一个页面,调用了多个API,要同时开多个连接几次调用才能全部所需要的数据,为了减少不必要的请求和开销,Ocelot也支持请求聚合

默认聚合

  • 修改配置文件,在ReRoutes 添加如下配置
{
  "DownstreamPathTemplate": "/api/orders",
  "DownstreamScheme": "http",
  "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5001 } ], "UpstreamPathTemplate": "/api/orders", "UpstreamHttpMethod": [ "Get" ], "Key": "Orders" }, { "DownstreamPathTemplate": "/api/products", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5002 } ], "UpstreamPathTemplate": "/api/products", "UpstreamHttpMethod": [ "Get" ], "Priority": 0, "Key": "Products" } 

大家注意一下,这和之前的配置有什么区别? 区别就是再每一个路由配置下多了一个 Key, Key的值可以任意定义(但建议还是按业务含义定义)

  • 在Ocelot.json中添加如下配置
"Aggregates": [
    {
      "ReRouteKeys": [
        "Orders",
        "Products"
      ],
      "UpstreamPathTemplate": "/api/aggregates" } ] 

注意Aggregates配置是和在ReRoutes配置平级的

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

示例说明: 当访问http://localhost:5000/api/aggregates, 会同时返回订单数据和产品数据

运行示例进行验证

既然是多个请求聚合,那么问题来了:

  • 如果其中一个服务宕机,会怎么样?

    我们停止订单服务,再次当访问http://localhost:5000/api/aggregates, 结果返回500
  • 如果其中一个服务不是宕机,而是返回500,会怎么样?

    我们修改order-api代码,在其中抛出异常
// GET: api/Product
[Route("api/orders")]
[HttpGet]
public IEnumerable<string> Get() { throw new Exception("获取所有订单出错"); } 

再次运行示例,访问http://localhost:5000/api/aggregates,Response是200, 但是body中Products节点是正常的产品数据,Orders节点里面的数据是异常信息

自定义聚合

如果默认的聚合返回的结果数据结构不是我们想要的,想要修改怎么办?答案是使用自定义聚合

  • 在ocelot-gateway中, 添加一个自动以聚合器FakeDefinedAggregator, 必须实现IDefinedAggregator接口。这个聚合器的功能很简单,就是将两个聚合请求的结果,用逗号拼接起来返回
public class FakeDefinedAggregator : IDefinedAggregator
{
    public FakeDefinedAggregator(FakeDepdendency dep) { } public async Task<DownstreamResponse> Aggregate(List<DownstreamContext> responses) { var one = await responses[0].DownstreamResponse.Content.ReadAsStringAsync(); var two = await responses[1].DownstreamResponse.Content.ReadAsStringAsync(); var merge = $"{one}, {two}"; var headers = responses.SelectMany(x => x.DownstreamResponse.Headers).ToList(); return new DownstreamResponse(new StringContent(merge), HttpStatusCode.OK, headers, "some reason"); } } 
  • 注入自定义聚合器
services.AddOcelot()
        .AddSingletonDefinedAggregator<FakeDefinedAggregator>();
  • 在Ocelot.json中修改配置,指定自定义聚合器
"Aggregates": [
    {
      "ReRouteKeys": [
        "Orders",
        "Products"
      ],
      "UpstreamPathTemplate": "/api/aggregates", "Aggregator": "FakeDefinedAggregator" } ], 

与之前的配置相比,多了如下的配置,就是指定自定义聚合器的

"Aggregator": "FakeDefinedAggregator"

验证

修改配置,运行示例程序, 访问http://localhost:5000/api/aggregate, 验证返回结果

最后

本篇我们介绍了Ocelot配置,只要特性路由,以及请求聚合。接下里我们会介绍Ocelot的其他特性:限流熔断、负载均衡

示例代码下载地址: https://github.com/lcyhjx/ocelot-demo/tree/master

在上篇.Net微服务实践(二):Ocelot介绍和快速开始中我们介绍了Ocelot,创建了一个Ocelot Hello World程序,接下来,我们会介绍Oclot的主要特性路由和另外一个特性请求聚合。这些特性都是通过配置来实现的。

配置

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

Ocelot的配置文件包含两个节点: ReRoutes和GlobalConfiguration

  • ReRoutes - 告诉Ocelot如何处理上游的请求
  • GlobalConfiguration - 全局配置,此节点的配置允许覆盖ReRoutes里面的配置,你可以在这里进行通用的一些配置信息

Ocelot的完整配置项如下

{
          "DownstreamPathTemplate": "/",
          "UpstreamPathTemplate": "/",
          "UpstreamHttpMethod": [ "Get" ], "DownstreamHttpMethod": "", "DownstreamHttpVersion": "", "AddHeadersToRequest": {}, "AddClaimsToRequest": {}, "RouteClaimsRequirement": {}, "AddQueriesToRequest": {}, "RequestIdKey": "", "FileCacheOptions": { "TtlSeconds": 0, "Region": "" }, "ReRouteIsCaseSensitive": false, "ServiceName": "", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 51876, } ], "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 0, "DurationOfBreak": 0, "TimeoutValue": 0 }, "LoadBalancer": "", "RateLimitOptions": { "ClientWhitelist": [], "EnableRateLimiting": false, "Period": "", "PeriodTimespan": 0, "Limit": 0 }, "AuthenticationOptions": { "AuthenticationProviderKey": "", "AllowedScopes": [] }, "HttpHandlerOptions": { "AllowAutoRedirect": true, "UseCookieContainer": true, "UseTracing": true, "MaxConnectionsPerServer": 100 }, "DangerousAcceptAnyServerCertificateValidator": false } 

完整配置项中的每一项具体含义和作用接下来会一一介绍,大的配置项的主要含义如下:

  • Downstream - 下游服务配置
  • UpStream - 上游服务配置
  • Aggregates - 服务聚合配置
  • ServiceName, LoadBalancer, UseServiceDiscovery - 配置服务发现
  • AuthenticationOptions - 配置服务认证
  • RouteClaimsRequirement - 配置Claims鉴权
  • RateLimitOptions - 限流配置
  • FileCacheOptions - 缓存配置
  • QosOptions - 服务质量与熔断
  • DownstreamHeaderTransform - 头信息转发

路由

基本配置

在上一篇的hello world程序中使用的就是基本配置

{
  "ReRoutes": [
    {
      "DownstreamPathTemplate": "/api/orders",
      "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5001 } ], "UpstreamPathTemplate": "/api/orders", "UpstreamHttpMethod": [ "Get" ] } ], "GlobalConfiguration": { "BaseUrl": "http://localhost:5000" } } 
  • BaseUrl - Ocelot的服务运行地址,要特别注意一下BaseUrl是我们外部暴露的Url,比如我们的Ocelot运行在http://localhost:5000,但是前面有一个 nginx绑定了域名http://api.demo.com,那这里我们的BaseUrl就是 http://api.demo.com
  • UpstreamPathTemplate、UpstreamHttpMethod - 配置上游服务器请求URL
  • DownstreamPathTemplate、DownstreamScheme、DownstreamHostAndPorts - 配置下游服务器请求URL

在基本配置的示例中:要实现的功能就是将 http://localhost:5000/api/orders GET 请求路由到 http://localhost:5001/api/orders GET

占位符

在Ocelot中,可以以{something}的形式将变量的占位符添加到模板中。占位符变量需要同时出现在DownstreamPathTemplate和UpstreamPathTemplate属性中。请求时Ocelot将尝试请求时进行替换

{
      "DownstreamPathTemplate": "/api/{everything}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5002 } ], "UpstreamPathTemplate": "/api/{everything}", "UpstreamHttpMethod": [ "Get" ] } 

示例说明:所有http://localhost:5000/api/XXXXXX的请求都会路由到http://localhost:5002/api/XXXXXX

例如http://localhost:5000/api/products 路由到 http://localhost:5002/api/products

例如http://localhost:5000/api/products/1 路由到 http://localhost:5002/api/products/1

验证

修改配置,运行示例程序, 访问http://localhost:5000/api/products,返回了产品数据

注意:在添加Ocelot.json文件时 .AddJsonFile("Ocelot.json",false,true), 第三个参数是指定文件发生变化时,是否重新加载,示例程序中是true. 所以我们只要修改运行目录下的配置文件,不用重新运行示例程序。

万能模板

既然占位符可以做通用匹配,自然而然就有一种配置可以匹配所有请求

{
      "DownstreamPathTemplate": "/{url}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5002 } ], "UpstreamPathTemplate": "/{url}", "UpstreamHttpMethod": [ "Get" ] } 

示例说明: 转发所有的请求到http://localhost:5002

验证

修改配置,运行示例程序, 访问http://localhost:5000/api/products,返回了产品数据

优先级

如果一个上游请求有多个路由配置都能匹配,到底该使用哪个路由呢? 路由可以配置优先级(Priority), 0最小,路由会使用优先级高的(说明:如果多个匹配路由优先级一样,则按顺序使用第一个)

  • 在product-api中添加一个category api
[ApiController]
public class CategoryController : ControllerBase { // GET: api/Product [Route("api/categories")] [HttpGet] public IEnumerable<string> Get() { return new string[] { "电子产品", "医护用品" }; } } 
  • 修改Ocelot.json配置文件如下
{
  "DownstreamPathTemplate": "/api/products",
  "DownstreamScheme": "http",
  "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5002 } ], "UpstreamPathTemplate": "/api/products", "UpstreamHttpMethod": [ "Get" ], "Priority": 0 }, { "DownstreamPathTemplate": "/api/categories", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5002 } ], "UpstreamPathTemplate": "/api/{everything}", "UpstreamHttpMethod": [ "Get" ], "Priority": 1 } 

如果这时访问http://localhost:5000/api/products, 大家猜一下,是返回产品数据还是类别数据?

验证

修改配置,运行示例程序, 访问http://localhost:5000/api/products,返回了类别数据, 因为类别路由的优先级是1, 优先级更高

查询参数

  • 在order-api中添加一个订单明细的api\
[Route("api/orders/{id}")]
[HttpGet]
public string Get(int id) { string order = string.Empty; switch(id) { case 1: order = "刘明的订单"; break; case 2: order = "王天的订单"; break; default: order = "没有找到订单"; break; } return order; } 
  • 修改Ocelot.json配置如下
{
  "DownstreamPathTemplate": "/api/orders/{id}",
  "DownstreamScheme": "http",
  "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5001 } ], "UpstreamPathTemplate": "/api/orders?id={id}", "UpstreamHttpMethod": [ "Get" ] } 

我们期望的结果是,当访问http://localhost:5000/api/orders?id=1 (下游服务实际没这个接口)时 路由到http://localhost:5001/api/orders/1返回订单明细

验证

修改配置,运行示例程序, 访问http://localhost:5000/api/orders?id=1,返回了订单明细数据

请求聚合

有一种场景,前端一个页面,调用了多个API,要同时开多个连接几次调用才能全部所需要的数据,为了减少不必要的请求和开销,Ocelot也支持请求聚合

默认聚合

  • 修改配置文件,在ReRoutes 添加如下配置
{
  "DownstreamPathTemplate": "/api/orders",
  "DownstreamScheme": "http",
  "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5001 } ], "UpstreamPathTemplate": "/api/orders", "UpstreamHttpMethod": [ "Get" ], "Key": "Orders" }, { "DownstreamPathTemplate": "/api/products", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5002 } ], "UpstreamPathTemplate": "/api/products", "UpstreamHttpMethod": [ "Get" ], "Priority": 0, "Key": "Products" } 

大家注意一下,这和之前的配置有什么区别? 区别就是再每一个路由配置下多了一个 Key, Key的值可以任意定义(但建议还是按业务含义定义)

  • 在Ocelot.json中添加如下配置
"Aggregates": [
    {
      "ReRouteKeys": [
        "Orders",
        "Products"
      ],
      "UpstreamPathTemplate": "/api/aggregates" } ] 

注意Aggregates配置是和在ReRoutes配置平级的

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

示例说明: 当访问http://localhost:5000/api/aggregates, 会同时返回订单数据和产品数据

运行示例进行验证

既然是多个请求聚合,那么问题来了:

  • 如果其中一个服务宕机,会怎么样?

    我们停止订单服务,再次当访问http://localhost:5000/api/aggregates, 结果返回500
  • 如果其中一个服务不是宕机,而是返回500,会怎么样?

    我们修改order-api代码,在其中抛出异常
// GET: api/Product
[Route("api/orders")]
[HttpGet]
public IEnumerable<string> Get() { throw new Exception("获取所有订单出错"); } 

再次运行示例,访问http://localhost:5000/api/aggregates,Response是200, 但是body中Products节点是正常的产品数据,Orders节点里面的数据是异常信息

自定义聚合

如果默认的聚合返回的结果数据结构不是我们想要的,想要修改怎么办?答案是使用自定义聚合

  • 在ocelot-gateway中, 添加一个自动以聚合器FakeDefinedAggregator, 必须实现IDefinedAggregator接口。这个聚合器的功能很简单,就是将两个聚合请求的结果,用逗号拼接起来返回
public class FakeDefinedAggregator : IDefinedAggregator
{
    public FakeDefinedAggregator(FakeDepdendency dep) { } public async Task<DownstreamResponse> Aggregate(List<DownstreamContext> responses) { var one = await responses[0].DownstreamResponse.Content.ReadAsStringAsync(); var two = await responses[1].DownstreamResponse.Content.ReadAsStringAsync(); var merge = $"{one}, {two}"; var headers = responses.SelectMany(x => x.DownstreamResponse.Headers).ToList(); return new DownstreamResponse(new StringContent(merge), HttpStatusCode.OK, headers, "some reason"); } } 
  • 注入自定义聚合器
services.AddOcelot()
        .AddSingletonDefinedAggregator<FakeDefinedAggregator>();
  • 在Ocelot.json中修改配置,指定自定义聚合器
"Aggregates": [
    {
      "ReRouteKeys": [
        "Orders",
        "Products"
      ],
      "UpstreamPathTemplate": "/api/aggregates", "Aggregator": "FakeDefinedAggregator" } ], 

与之前的配置相比,多了如下的配置,就是指定自定义聚合器的

"Aggregator": "FakeDefinedAggregator"

验证

修改配置,运行示例程序, 访问http://localhost:5000/api/aggregate, 验证返回结果

最后

本篇我们介绍了Ocelot配置,只要特性路由,以及请求聚合。接下里我们会介绍Ocelot的其他特性:限流熔断、负载均衡

示例代码下载地址: https://github.com/lcyhjx/ocelot-demo/tree/master

猜你喜欢

转载自www.cnblogs.com/lihaijia/p/12654792.html