上一次我們通過一張架構圖(.Net Core with 微服務 - 架構圖)來講述了微服務的結構,分層等內容。從現在開始我們開始慢慢搭建一個最簡單的微服務架構。這次我們先用幾個簡單的 web api 項目以及 ocelot 網關項目來演示下網關是如何配置,如何工作的。
Ocelot 網關
Ocelot 是使用 asp.net core 開發的一個 api 網關項目。它功能豐富,集成了路由、限流、緩存、聚合等功能。它使用 .net 編寫,本質上就是一堆 asp.net core 的中間件,所以它天生對 .net 友好。這些中間件攔截外部的請求,根據路由配置轉發到對應的內部服務上,再把內部的返回結果對外暴露。
搭建項目結構
新建一個解決方案,新建幾個項目。
- api_gateway API網關
- hotel_base 酒店基本信息服務
- member_center 會員中心服務
- ordering 訂單服務
安裝 Ocelot
在API網關項目上使用nuget安裝Ocelot的類庫。Ocelot本質上就是一堆 asp.net Core 的 middleware。所以我們需要在UseOcelot擴展方法在注冊這些中間件。
Install-Package Ocelot
public static void Main(string[] args)
{
new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
config
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddJsonFile("routes.json")
.AddEnvironmentVariables();
})
.ConfigureServices(s => {
s.AddOcelot();
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConsole();
})
.UseIISIntegration()
.Configure(app =>
{
app.UseOcelot().Wait();
})
.Build()
.Run();
}
}
在 main 函數內注冊Ocelot的中間件,服務,使用AddJsonFile指定路由的配置文件。
路由
Ocelot最基本的功能就是反向代理。代理的配置通過一個json文件來配置。下面讓我們來簡單的演示下如何配置。
以下是通過網關代理訪問酒店服務的酒店列表的配置示例。
{
//獲取酒店列表
"UpstreamPathTemplate": "/api/hotel",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamPathTemplate": "/hotel",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
//hotel service
"Host": "localhost",
"Port": 6003
}
]
}
配置主要是分為Upstream跟Downstream兩部分。Upstream其實就是指代ocelot網關本身。Downstream代表真正的服務。
- UpstreamPathTemplate 網關匹配的路徑
- UpstreamHttpMethod 網關匹配的請求方法
- DownstreamPathTemplate 服務匹配的路徑
- DownstreamScheme 服務的Scheme,http、https
- DownstreamHostAndPorts 服務的主機地址跟端口
上面的配置描述的意思是:把對網關的/api/hotel的GET請求轉發到主機http://localhost:6003/hotel接口上。
路由參數
Ocelot的path模板可以使用{param}模式來匹配參數,然后傳遞到下游服務器上。
{
//獲取單個酒店
"UpstreamPathTemplate": "/api/hotel/{hotel_id}",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamPathTemplate": "/hotel/{hotel_id}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
//hotel service
"Host": "localhost",
"Port": 6003
}
],
"Key": "hotel_base_info",
}
使用{hotel_id}匹配hotelId參數。
{
//獲取酒店房間列表
"UpstreamPathTemplate": "/api/hotel_rooms/{hotel_id}",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamPathTemplate": "/room/hotel_rooms/{hotel_id}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
//hotel service
"Host": "localhost",
"Port": 6003
}
],
"Key": "hotel_rooms"
}
使用{hotel_id}匹配hotelId參數。
{
//獲取查詢訂單
"UpstreamPathTemplate": "/api/order/query?day={day}",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamPathTemplate": "/order/get_orders?day={day}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
//order service
"Host": "localhost",
"Port": 6001
}
]
}
在QueryString上使用{day}匹配參數。
限流
Ocelot支持對請求的限流操作。
"RateLimitOptions": {
"EnableRateLimiting": true,
"Period": "1s",
"PeriodTimespan": 1,
"Limit": 1
}
在路由配置節點添加RateLimitOptions節點。
- EnableRateLimiting = true 開啟限流
- Period = 1s 限流的時間區間為1s
- PeriodTimespan = 1 限流后重置時間
- Limit = 1 限制請求的數量
上面的配置的意思是1秒內限制一次請求,1秒后重置這個限制。
緩存
Ocelot可以對請求的響應值提供緩存服務。
//緩存5s
"FileCacheOptions": { "TtlSeconds": 5 }
在路由配置節點上配置FileCacheOptions字段,TtlSeconds代表需要緩存的時間,單位是秒。
聚合
上一回我們講微服務架構的時候說到“聚合服務層”,我們說這一層的主要功能是對請求進行聚合適配跟裁剪。其實ocelot已經提供了簡單的api聚合功能。如果聚合的需求比較簡單,那么可以使用ocelot直接實現。
簡單聚合
簡單聚合可以通過配置把幾個請求的聚合成一個請求,一次性返回幾個請求的響應。響應通過json格式被包裝返回。
"Aggregates": [
{
//聚合 查詢酒店信息跟酒店房間列表
"RouteKeys": [
"hotel_base_info",
"hotel_rooms"
],
"UpstreamPathTemplate": "/api/hotel_detail/{hotel_id}"
},
]
RouteKeys 代表需要聚合的請求的鍵值。
使用代碼聚合
上面我們直接通過配置實現了api之間聚合請求。這種聚合比較簡單,會把聚合的幾個請求的響應值原封不動的返回回來。有的時候我們需要對返回值做一些轉換或者裁剪,比如同一個api我們對移動端的響應可能需要裁剪掉部分字段。這種需求在ocelot內我們可以使用代碼來完成。
這里不太推薦這種聚合方式,這會造成網關跟下游服務的強耦合關系。
這里我們演示下如何把獲取酒店信息跟酒店房間列表的返回值進行裁剪,並返回一個新的響應。
public class HotelDetailInfoForMobileAggregator : IDefinedAggregator
{
public async Task<DownstreamResponse> Aggregate(List<HttpContext> responses)
{
dynamic hotelInfo = new ExpandoObject();
List<dynamic> rooms = new List<dynamic>();
foreach (var context in responses)
{
if ((context.Items["DownstreamRoute"] as dynamic).Key == "hotel_base_info")
{
var respContent = await context.Items.DownstreamResponse().Content.ReadAsStringAsync();
hotelInfo = JsonConvert.DeserializeObject<dynamic>(respContent);
}
if ((context.Items["DownstreamRoute"] as dynamic).Key == "hotel_rooms")
{
var respContent = await context.Items.DownstreamResponse().Content.ReadAsStringAsync();
rooms = JsonConvert.DeserializeObject<List<dynamic>>(respContent);
}
}
dynamic newResponse = new ExpandoObject();
newResponse.hotel = new {
hotelInfo.id,
hotelInfo.name
};
newResponse.rooms = rooms.Select(x => new {
x.id,
x.no
});
var stringContent = new StringContent(JsonConvert.SerializeObject(newResponse));
return new DownstreamResponse(
stringContent,
System.Net.HttpStatusCode.OK,
responses.SelectMany(x => x.Items.DownstreamResponse().Headers).ToList(),
"OK");
}
}
每一個聚合都需要繼承IDefinedAggregator這個接口然后實現Aggregate方法。在這個方法內對每個請求的響應值進行裁剪,然后重新組合。
{
//聚合 查詢酒店信息跟酒店房間列表 移動端 裁剪
"RouteKeys": [
"hotel_base_info",
"hotel_rooms"
],
"UpstreamPathTemplate": "/api/m/hotel_detail/{hotel_id}",
"Aggregator": "HotelDetailInfoForMobileAggregator"
}
在配置文件的Aggregates內添加一個配置節點在“Aggregator”字段上指定Aggregator的類名。
.ConfigureServices(s => {
s.AddOcelot()
.AddTransientDefinedAggregator<HotelDetailInfoForMobileAggregator>();
})
同時在ConfigureServices方法內配置HotelDetailInfoForMobileAggregator的依賴注入。
總結
本次我們通過幾個最簡單的web api項目,演示了如何使用 ocelot 網關進行反向代理,限流,聚合等常用功能。可以看到 ocelot 的配置使用還是比較簡單的。因為是 .net 代碼編寫,所以對.net 開發者比較友好,我們可以直接使用 .net 代碼來編寫一些功能,比如直接使用代碼來聚合請求的結果。
相關文章
NET Core with 微服務 - 什么是微服務
.Net Core with 微服務 - 架構圖
演示代碼
https://github.com/kklldog/myhotel_microservice