.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