上一次我們通過一張架構圖(.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

關注我的公眾號一起玩轉技術