在上篇.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的其他特性:限流熔斷、負載均衡 .Net微服務實踐(四)[網關]:Ocelot限流熔斷、緩存以及負載均衡