.Net微服務實踐(四)[閘道器]:Ocelot限流熔斷、快取以及負載均衡
- 限流
- 熔斷
- 快取
- Header轉化
- HTTP方法轉換
- 負載均衡
- 注入/重寫中介軟體
- 後臺管理
- 最後
在上篇.Net微服務實踐(三)[閘道器]:Ocelot配置路由和請求聚合中我們介紹了Ocelot的配置,主要特性路由以及服務聚合。接下來,我們會介紹Ocelot的限流、熔斷、快取以及負載均衡。
限流
我們先來看限流的配置
Reroute節點中的配置如下:
{ "DownstreamPathTemplate": "/api/orders", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5001 } ], "UpstreamPathTemplate": "/api/orders", "UpstreamHttpMethod": [ "Get" ], "RateLimitOptions": { "ClientWhitelist": [], "EnableRateLimiting": true, "Period": "10m", "PeriodTimespan": 3, "Limit": 1 } }
GlobalConfiguration中的配置如下:
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5000",
//限流
"RateLimitOptions": {
"QuotaExceededMessage": "您的請求量超過了配額1/10分鐘",
"HttpStatusCode": 999
}
}
配置說明
在Reroute和GlobalConfiguration節點中添加了RateLimitOptions節點
- ClientWhitelist - 白名單,也就是不受限流控制的客戶端
- EnableRateLimiting - 是否開啟限流
- Period & Limit - 在一段時間內允許的請求次數
- PeriodTimespan - 客戶端的重試間隔數,也就是客戶端間隔多長時間可以重試
- QuotaExceededMessage - 限流以後的提示資訊
- HttpStatusCode - 超出配額時,返回的http狀態碼
示例說明
客戶端在10分鐘之內只允許請求一次http://localhost:5000/api/orders,在請求之後3秒鐘之後可以重試
驗證
修改配置,執行示例程式,
訪問http://localhost:5000/api/orders,第一次可以正常獲取返回結果,再次訪時,顯示"您的請求量超過了配額1/10分鐘, 並且response狀態碼是999
PeriodTimespan的驗證
修改Period為1s, 修改PeriodTimespan為10,這樣當前的配置是1秒中允許一個請求,10秒後才能重試。 再次執行示例程式。
訪問http://localhost:5000/api/orders,第一次可以正常獲取返回結果, 等待兩秒,再次訪問,大家想一下,這個時候,會不會返回正常結果(已經過了兩秒)。這時還是返回999,為什麼? 因為儘管配額上是允許的,但是因為配置是客戶端10秒以後才能重試,而這時只等待了2秒,所以還是返回999.
熔斷
Ocelot的熔斷使用了Polly來實現,在OcelotGateway專案新增Polly包
注入Polly
services
.AddOcelot()
.AddPolly();
修改配置
{
"DownstreamPathTemplate": "/api/orders",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5001
}
],
"UpstreamPathTemplate": "/api/orders",
"UpstreamHttpMethod": [ "Get" ],
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 2,
"DurationOfBreak": 5000,
"TimeoutValue": 2000
}
}
配置說明
在Reroute節點中添加了QoSOptions節點
- ExceptionsAllowedBeforeBreaking - 在熔斷之前允許的異常次數
- DurationOfBreak - 熔斷時長 ,單位毫秒
- TimeoutValue - 請求超時設定, 單位毫秒
示例說明
當訪問http://localhost:5000/api/orders出現2次異常後,服務熔斷5秒,如果服務響應超過2秒,也觸發熔斷條件
驗證
-
場景一:服務宕機
修改配置,只啟動閘道器,不啟動oder api,訪問http://localhost:5000/api/orders,第一次有響應耗時,返回500,第二次也有響應耗時,返回500. 第三次則快速返回503 Service Unavalible, 服務熔斷了。 -
場景二:超時
修改配置
修改api/orders程式碼,等待3秒
// GET: api/orders
[Route("api/orders")]
[HttpGet]
public IEnumerable<string> Get()
{
Task.Delay(3000).Wait();
return new string[] { "劉明的訂單", "王天的訂單" };
}
啟動閘道器,啟動order-api,訪問http://localhost:5000/api/orders,返回503
- 場景三:服務正常響應,但是服務500 內部錯誤
修改配置
修改api/orders程式碼,丟擲異常
// GET: api/orders
[Route("api/orders")]
[HttpGet]
public IEnumerable<string> Get()
{
throw new Exception("獲取所有訂單出錯");
}
啟動閘道器,啟動order-api,訪問http://localhost:5000/api/orders, 不觸發熔斷
快取
快取使用了CacheManageer來實現,新增CacheManager包
Install-Package Ocelot.Cache.CacheManager
注入快取元件
services.AddOcelot()
.AddCacheManager(x =>
{
x.WithDictionaryHandle();
});
Ocelot.json配置檔案修改
{
"DownstreamPathTemplate": "/api/orders",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5001
}
],
"UpstreamPathTemplate": "/api/orders",
"UpstreamHttpMethod": [ "Get" ],
"FileCacheOptions": {
"TtlSeconds": 60,
"Region": "orders"
}
}
快取是根據 downstream service 的URL來快取的
配置說明
在Reroute節點中添加了FileCacheOptions節點
- TtlSeconds - 快取有效期,單位是秒
- Region - 快取分割槽, 可以通過呼叫後臺Api 來清空一個region下的快取
示例說明
當訪問http://localhost:5000/api/orders後,結果會快取60秒,在快取有效期內即使原始的order api的返回結果發生變化,通過閘道器請求時,還是會返回快取的結果。
驗證
- 修改配置,只啟動閘道器,啟動oder api,訪問http://localhost:5000/api/orders,返回的結果如下
"劉明的訂單", "王天的訂單"
- 修改api/orders, 重新啟動api/orders
[Route("api/orders")]
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "帥的訂單", "我的訂單" };
}
- 再次訪問http://localhost:5000/api/orders, 這個時候是會返回什麼結果呢?, 驗證顯示還是返回
"劉明的訂單", "王天的訂單"
因為結果被快取了
- 等待2分鐘,再訪問http://localhost:5000/api/orders,這個時候是會返回什麼結果呢?, 驗證顯示返回了新的結果
"帥的訂單", "我的訂單"
因為快取有效期已經過了
Header轉化
Ocelot允許在上游服務的request和下游服務的response的header中新增、替換資訊
配置如下:
{
"DownstreamPathTemplate": "/api/shopping-carts",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5001
}
],
"DownstreamHeaderTransform": {
"devops": "rdc"
},
"UpstreamPathTemplate": "/api/shopping-carts",
"UpstreamHttpMethod": [ "Get" ],
"UpstreamHeaderTransform": {
"lakin": "rdc",
"CI": "msbuild, jenkins",
"Location": "http://localhost:5001, {BaseUrl}"
}
}
配置說明
在Reroute節點中添加了DownstreamHeaderTransform節點和UpstreamHeaderTransform節點
"DownstreamHeaderTransform": {
"devops": "rdc"
}
說明:在下游服務的response中新增一個header, key是devops, value是rdc
"UpstreamHeaderTransform": {
"lakin": "rdc",
"CI": "msbuild, jenkins",
"Location": "http://localhost:5001, {BaseUrl}"
}
- 新增header資訊
在上游服務的request中新增一個header, key是lakin, value是rdc - 替換header資訊
在上游服務的request中, 將key是CI的header, 其值由msbuild替換為jenkins - 替換時使用placeholder
在上游服務的request中, 將key是Location的header, 其值由http://localhost:5001替換為{BaseUrl} placehokder
示例說明
當訪問http://localhost:5000/api/orders後,結果會快取60秒,在快取有效期內即使原始的order api的返回結果發生變化,通過閘道器請求時,還是會返回快取的結果。
驗證
- 修改Order Service, 新增一個ShoppingCart api, 程式碼如下
// GET: api/shopping-carts
[Route("api/shopping-carts")]
[HttpGet]
public IEnumerable<string> Get()
{
Console.WriteLine($"開始列印header資訊");
foreach (var item in this.Request.Headers)
{
Console.WriteLine($"{item.Key} - {item.Value}");
}
Console.WriteLine($"列印header資訊完成");
return new string[] { "洗髮水", "無人機" };
}
- 啟動閘道器、啟動Order Service
- 使用Postman呼叫http://localhost:5000/api/shopping-carts, 在request中新增兩個header項
"CI": "msbuild",
"Location": "http://localhost:5001"
- 發起請求
- 在Order Service的控制檯,可以看到如下輸出, 新增一個header項,替換兩個header項的值
開始列印header資訊CI
lakin - rdc
CI - jenkins
Location - http://localhost:5000
列印header資訊完成
- 檢查Postman中response的header資訊,會發現添加了如下的header項
devops - rdc
HTTP方法轉換
Ocelot允許在路由時轉化HTTP方法
{
"DownstreamPathTemplate": "/api/shopping-carts",
"DownstreamScheme": "http",
"DownstreamHttpMethod": "POST",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5001
}
],
"UpstreamPathTemplate": "/api/shopping-carts",
"UpstreamHttpMethod": [ "Get" ]
}
示例說明
上述示例中,將GET /api/shopping-carts 路由到 POST /api/shopping-carts, 將GET轉換成了POST
適用場景:例如有些已經存在的的API,因為某些歷史原因都是用POST,在通過閘道器對外提供服務時,就可以按照標準API進行轉換
驗證
- 當前GET /api/shopping-carts返回如下結果
"洗髮水", "無人機"
- 我們在ShoppingCartController中新增一個新的POST API
[Route("api/shopping-carts")]
[HttpPost]
public string Post()
{
return "新增商品到購物車成功";
}
- 執行閘道器和Order Service
- 訪問GET http://localhost:5000/api/shopping-carts, 返回的結果如下,已經將GET轉換成了POST
新增商品到購物車成功
負載均衡
Ocelot內建了負載均衡,我們先來看配置
{
"DownstreamPathTemplate": "/api/orders",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5001
},
{
"Host": "localhost",
"Port": 6001
}
],
"UpstreamPathTemplate": "/api/orders",
"UpstreamHttpMethod": [ "Get" ],
"LoadBalancerOptions": {
"Type": "RoundRobin"
}
}
配置說明
在DownstreamHostAndPorts指指定多個服務地址
在Reroute節點中新增LoadBalancerOptions,這是負載均衡的配置節點,其中Type屬性指定了負載均衡的演算法, 它有如下幾個值:
- LeastConnection – 將請求發往最空閒的那個伺服器
- RoundRobin – 輪流傳送
- NoLoadBalance – 總是發往第一個請求(如果配置了服務發現,則總是發往發現的第一個服務地址)
驗證
- 啟動閘道器
- 修改api/orders的程式碼,並啟動Order Service
[Route("api/orders")]
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "劉明的訂單", "王天的訂單" };
}
- 拷貝一份Order Service的副本,修改api/orders的程式碼,修改副本的啟動埠為6001, 並啟動Order Service
[Route("api/orders")]
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "帥的訂單", "我的訂單" };
}
- 在Postman中呼叫GET http://localhost:5000/api/orders
第一次的結果是
"劉明的訂單", "王天的訂單"
第二次的結果是
"帥的訂單", "我的訂單"
第三次的結果是
"劉明的訂單", "王天的訂單"
注入/重寫中介軟體
Ocelot本身是一組中介軟體,它也提供了方式來注入和重寫其中的某些中介軟體:
- PreErrorResponderMiddleware - 在所有Ocelot中介軟體之前執行, 允許使用者在Ocelot管道執行之前和執行之後提供任何行為。
- PreAuthenticationMiddleware - 提供預身份認證邏輯,在身份認證中介軟體之前執行
- AuthenticationMiddleware - 覆寫Ocelot中介軟體提供的身份認證邏輯
- PreAuthorisationMiddleware - 提供預授權邏輯,在授權中介軟體之前執行
- AuthorisationMiddleware - 覆寫Ocelot中介軟體提供的授權邏輯
- PreQueryStringBuilderMiddleware - 在QueryString轉換之前,提供預處理邏輯
下面是注入PreErrorResponderMiddleware中介軟體的程式碼示例:
//注入中介軟體
var configuration = new OcelotPipelineConfiguration
{
PreErrorResponderMiddleware = async (ctx, next) =>
{
ctx.HttpContext.Request.Headers.Add("myreq", "ocelot-request");
await next.Invoke();
}
};
app.UseOcelot(configuration).Wait();
注意: Ocelot也是一組中介軟體,所以可以在Ocelot中介軟體之前,按常規方式新增任何中介軟體, 但是不能在Ocelot中介軟體之後新增,因為Ocelot沒有呼叫 next
後臺管理
Ocelot提供了一組後臺管理的API, 從前三篇文章可以看出,Ocelot主要也就是配置檔案的管理,所以API主要也就是管理配置
- 獲取管理後臺的token
POST {adminPath}/connect/token - 獲取配置
GET {adminPath}/configuration - 建立/修改配置
POST {adminPath}/configuration - 刪除快取
DELETE {adminPath}/outputcache/{region}
最後
本篇我們介紹了Ocelot的限流、熔斷、快取、負載均衡以及其他一些特性。到目前為止,Ocelot的基本配置和功能都已經介紹完了。接下里我們會結合consul來介紹服務發現,以及Ocelot和Consul的整合。
示例程式碼下載地址: https://github.com/lcyhjx/ocelot-demo/tree/mas