天天看點

Ocelot(二)- 請求聚合與負載均衡

在上一篇Ocelot的文章中,我已經給大家介紹了何為Ocelot以及如何簡單使用它的路由功能,如果你還沒有不了解Ocelot為何物,可以檢視我的系列文章 Ocelot - .Net Core開源網關。在這篇文章中,我将會繼續給大家介紹Ocelot的功能:請求聚合與負載均衡。

Ocelot(二)- 請求聚合與負載均衡

作者:markjiang7m2

原文位址:https://www.cnblogs.com/markjiang7m2/p/10865511.html

源碼位址:https://gitee.com/Sevenm2/OcelotDemo

開篇題外話:在上一篇文章的案例中,我直接使用API傳回伺服器的端口和接口的路徑,我感覺這樣舉例過于偏技術化,比較沉悶,然後我想到了之前參加PMP課程教育訓練時候,我們的教育訓練講師——孫志斌老師引用

小王

老李

的模型給我們講述項目管理的各種實戰,可謂是生動形象,而且所舉的例子也非常貼近我們的日常工作,通俗易懂,是以,我也嘗試使用類似的人物形象進行案例的講解。首先,本文将會引入兩個人物

Willing

Jack

。Willing是一名資深專家,工作多年,而Jack則是.NET新手。

本文中涉及案例的完整代碼都可以從我的代碼倉庫進行下載下傳。

  • 倉庫位址:https://gitee.com/Sevenm2/OcelotDemo

案例二 請求聚合

我們在案例一路由中已經知道,Ocelot可以定義多組路由,然後根據優先級對上遊服務發出的請求進行不同的轉發處理,每個路由轉發都比對唯一的一個下遊服務API接口。然而,有時候,上遊服務想要獲得來自兩個API接口傳回的結果。Ocelot允許我們在配置檔案中聲明聚合路由

Aggregates

,進而實作這樣的效果。

舉個例子,有一天我的老闆(使用者)讓我(上遊服務)去了解清楚Willing和Jack兩位同僚對工作安排有什麼意見(請求),當然了,我可以先跑去問Jack,然後再跑到Willing那裡了解情況,可是這樣我就要跑兩趟,這樣不劃算啊,于是,我去找了他們的上司(聚合)說我老闆想要了解他們兩個的意見,他們上司一個電話打過去,Willing和Jack就都一起過來了,我也就很快完成了老闆交代的任務。

在這個過程中,我是可以單獨通路Willing或者Jack的,是以,他們是在

ReRoutes

中聲明的兩組普通的路由,而他們的上司是在

Aggregates

中聲明的一組聚合路由。剛剛我們的舉例當中,通路不同的人需要到達不同的地方,是以在聲明路由時,也需要注意它們的

UpstreamPathTemplate

都是不一樣的。

下面是具體的路由配置:

"ReRoutes": [
{
    "DownstreamPathTemplate": "/api/ocelot/aggrWilling",
    "DownstreamScheme": "http",
    "DownstreamHostAndPorts": [
    {
        "Host": "localhost",
        "Port": 8001
    }
    ],
    "UpstreamPathTemplate": "/ocelot/aggrWilling",
    "UpstreamHttpMethod": [ "Get" ],
    "Key": "aggr_willing",
    "Priority": 2
},
{
    "DownstreamPathTemplate": "/api/ocelot/aggrJack",
    "DownstreamScheme": "http",
    "DownstreamHostAndPorts": [
    {
        "Host": "localhost",
        "Port": 8001
    }
    ],
    "UpstreamPathTemplate": "/ocelot/aggrJack",
    "UpstreamHttpMethod": [ "Get" ],
    "Key": "aggr_jack",
    "Priority": 2
}
],
"Aggregates": [
{
    "ReRouteKeys": [
    "aggr_willing",
    "aggr_jack"
    ],
    "UpstreamPathTemplate": "/aggrLeader"
}
]
           

大家可以注意到,在

ReRoutes

中聲明的兩組路由相比案例一不同的是,多加了一個

Key

屬性。

Aggregates

ReRoutes

是同級的,而且也是一個數組,這代表着我們可以聲明多個聚合路由,而在我們聲明的這一組聚合路由中的屬性

ReRouteKeys

,它包含的元素就是我們真正需要響應的路由的

Key

屬性值。

當然,我們的下遊服務也相應添加兩個API接口。

// GET api/ocelot/aggrWilling
[HttpGet("aggrWilling")]
public async Task<IActionResult> AggrWilling(int id)
{
    var result = await Task.Run(() =>
    {
        return $"我是Willing,還是多加工資最實際, path: {HttpContext.Request.Path}";
    });
    return Ok(result);
}
// GET api/ocelot/aggrJack
[HttpGet("aggrJack")]
public async Task<IActionResult> AggrJack(int id)
{
    var result = await Task.Run(() =>
    {
        return $"我是Jack,我非常珍惜現在的工作機會, path: {HttpContext.Request.Path}";
    });
    return Ok(result);
}
           

下面我們一起來看看執行的結果。

我們按照案例一,先單獨來問問Jack。

Ocelot(二)- 請求聚合與負載均衡

然後再看看直接通過聚合路由通路

Ocelot(二)- 請求聚合與負載均衡

可以看到,在傳回結果中同時包含了Willing和Jack的結果,并且是以

json

串的格式傳回,以路由的

Key

屬性值作為傳回json的屬性。

(傳回的結果好像哪裡不太對,不知道你是否發現了,但暫時先不要着急,我在後面會為大家揭曉)

需要注意的是,Ocelot僅支援

GET

方式的請求聚合。Ocelot總是以

application/json

的格式傳回一個聚合請求的,當下遊服務是傳回404狀态碼,在傳回結果中,其對應的值則為空值,即使聚合路由中所有的下遊服務都傳回404狀态碼,聚合路由的傳回結果也不會是404狀态碼。

我們在不添加任何API接口的情況下,聲明一組下遊服務不存在的路由,并将它添加到聚合路由當中。

"ReRoutes": [
...,
{
    "DownstreamPathTemplate": "/api/ocelot/aggrError/1",
    "DownstreamScheme": "http",
    "DownstreamHostAndPorts": [
    {
        "Host": "localhost",
        "Port": 8001
    }
    ],
    "UpstreamPathTemplate": "/ocelot/aggrError/1",
    "UpstreamHttpMethod": [ "Get" ],
    "Key": "aggr_error",
    "Priority": 2
}
],
"Aggregates": [
{
    "ReRouteKeys": [
    "aggr_willing",
    "aggr_jack",
    "aggr_error"
    ],
    "UpstreamPathTemplate": "/aggrLeader"
}
]
           

測試結果如下:

直接請求aggr_error

Ocelot(二)- 請求聚合與負載均衡

直接通過聚合路由通路

Ocelot(二)- 請求聚合與負載均衡

前面我說到傳回結果好像有哪裡不太對,那到底是哪裡出錯了呢?我來将傳回的json串進行格式化一下。

{
    "aggr_willing":我是Willing,還是多加工資最實際, path: /api/ocelot/aggrWilling,
    "aggr_jack":我是Jack,我非常珍惜現在的工作機會, path: /api/ocelot/aggrJack,
    "aggr_error":
}
           

我們會發現這并不是一個正确的json串,那到底為什麼會這樣呢?既然Ocelot是開源的,那我們就來深挖一下源碼到底是怎麼處理聚合請求傳回結果的。

Ocelot Github:https://github.com/ThreeMammals/Ocelot

找到位于

Ocelot.Middleware.Multiplexer

中的一個類

SimpleJsonResponseAggregator

,靜态方法

MapAggregateContent

var content = await contexts[0].DownstreamResponse.Content.ReadAsStringAsync();
contentBuilder.Append($"\"{responseKeys[k]}\":{content}");
           

因為我的下遊服務傳回結果是一個字元串,然後被Ocelot直接拼接到傳回結果中,進而得到我們上面看到的結果。

是以,在我看來,當我們使用Ocelot的聚合路由功能時,下遊服務的傳回結果必須要保證是一個json串,這樣才能最終被正确識别。

我把下遊服務改一改,添加一個類,然後将API傳回結果格式更改為這個類型。

public class ResponseResult
{
    public string Comment { get; set; }
}
           
// GET api/ocelot/aggrWilling
[HttpGet("aggrWilling")]
public async Task<IActionResult> AggrWilling(int id)
{
    var result = await Task.Run(() =>
    {
        ResponseResult response = new ResponseResult()
        { Comment = $"我是Willing,還是多加工資最實際, path: {HttpContext.Request.Path}" };
        return response;
        //return $"我是Willing,還是多加工資最實際, path: {HttpContext.Request.Path}";
    });
    return Ok(result);
}
// GET api/ocelot/aggrJack
[HttpGet("aggrJack")]
public async Task<IActionResult> AggrJack(int id)
{
    var result = await Task.Run(() =>
    {
        ResponseResult response = new ResponseResult()
        { Comment = $"我是Jack,我非常珍惜現在的工作機會, path: {HttpContext.Request.Path}" };
        return response;
        //return $"我是Jack,我非常珍惜現在的工作機會, path: {HttpContext.Request.Path}";
    });
    return Ok(result);
}
           

運作看執行結果

Ocelot(二)- 請求聚合與負載均衡

簡單總結為以下三點注意:

  • 僅支援

    GET

    方式
  • 下遊服務傳回類型要求為application/json
  • 傳回内容類型為application/json,不會傳回404請求

進階請求聚合

在上一個案例中,我已經可以通過Willing和Jack的上司得到我想要的結果,但在這個過程中,他們的上司(聚合)都隻是在幫我獲得結果,沒有對得到的結果做任何的幹預。那如果上司想着,既然老闆想要了解情況,自己當然也要幹點活,讓老闆知道在這個過程中自己也是有出力的,這就涉及到進階的請求聚合了。

在網上搜了一下關于進階請求聚合的資料,好像沒有怎麼見到有相關執行個體的Demo,最全面的資料來自于官網文檔說明,也許是在實際應用中這個功能不怎麼被運用?或是我打開的方式不對?原因暫時未知,知道的朋友們可以在留言區給我說一下。那麼我在這裡就用執行個體給大家介紹一下。

Ocelot支援在獲得下遊服務傳回結果後,通過一個聚合器對傳回結果進行再一步的加工處理,目前支援内容,頭和狀态代碼的修改。我們來看配置檔案

"Aggregates": [
{
    "ReRouteKeys": [
    "aggr_willing",
    "aggr_jack",
    "aggr_error"
    ],
    "UpstreamPathTemplate": "/aggrLeaderAdvanced",
    "Aggregator": "LeaderAdvancedAggregator"
}
]
           

因為是請求聚合的進階,是以

ReRoutes

路由不需要任何更改。

Aggregates

中一組配置增加了屬性

Aggregator

,表示當獲得傳回結果,由聚合器

LeaderAdvancedAggregator

進行處理。

然後我在Ocelot項目中添加聚合器

LeaderAdvancedAggregator

,要實作這個聚合器,就必須實作來自

Ocelot.Middleware.Multiplexer

提供的接口

IDefinedAggregator

public class LeaderAdvancedAggregator : IDefinedAggregator
{
    public async Task<DownstreamResponse> Aggregate(List<DownstreamResponse> responses)
    {
        List<string> results = new List<string>();
        var contentBuilder = new StringBuilder();

        contentBuilder.Append("{");

        foreach (var down in responses)
        {
            string content = await down.Content.ReadAsStringAsync();
            results.Add($"\"{Guid.NewGuid()}\":{content}");
        }
        //來自leader的聲音
        results.Add($"\"{Guid.NewGuid()}\":{{comment:\"我是leader,我組織了他們兩個進行調查\"}}");

        contentBuilder.Append(string.Join(",", results));
        contentBuilder.Append("}");

        var stringContent = new StringContent(contentBuilder.ToString())
        {
            Headers = { ContentType = new MediaTypeHeaderValue("application/json") }
        };
        
        var headers = responses.SelectMany(x => x.Headers).ToList();
        return new DownstreamResponse(stringContent, HttpStatusCode.OK, headers, "some reason");
    }
}
           

當下遊服務傳回結果後,Ocelot就會調用聚合器的

Aggregate

方法,是以,我們的處理代碼就寫在這個方法中。

之後,我們就需要将聚合器在容器中進行注冊

Startup.cs

services
    .AddOcelot()
    .AddSingletonDefinedAggregator<LeaderAdvancedAggregator>();
           

運作,通路進階請求聚合的Url

http://localhost:4727/aggrLeaderAdvanced

,得到如下結果:

Ocelot(二)- 請求聚合與負載均衡

也許大家已經留意到,我在處理傳回結果是,并沒有像Ocelot内部傳回結果一樣使用路由的

Key

作為屬性,而是使用了Guid。其實這也是我在做Demo時候的一處疑惑,我似乎無法像Ocelot内部一樣處理。

在這個

Aggregate

方法中提供的參數類型隻有

List<DownstreamResponse>

,但

DownstreamResponse

中并沒有關于

ReRouteKeys

的資訊。我檢視了Ocelot的源碼,

ReRouteKeys

隻存在于

DownstreamReRoute

中,但我無法通過

DownstreamResponse

擷取到

DownstreamReRoute

希望有知道的朋友能在留言區告訴我一下,感謝。

另外,這個聚合器也能像一般服務一樣,可以使用依賴注入的方式添加依賴。我也嘗試在案例中添加了一個依賴

LeaderAdvancedDependency

。如何使用依賴注入,我這裡就不細說了,大家可以搜尋 .net core依賴注入的相關資料。

LeaderAdvancedAggregator.cs

public LeaderAdvancedDependency _dependency;

public LeaderAdvancedAggregator(LeaderAdvancedDependency dependency)
{
    _dependency = dependency;
}
           
services.AddSingleton<LeaderAdvancedDependency>();
           

這樣,我們就可以在聚合器中使用依賴了。

Ocelot除了支援

Singleton

的聚合器以外,還支援

Transient

的聚合器,大家可以按需使用。

services
    .AddOcelot()
    .AddTransientDefinedAggregator<LeaderAdvancedAggregator>();
           

案例三 負載均衡

在前面的案例中,我們全部的路由配置中都是一組路由配置一個下遊服務位址,也就意味着,當上遊服務請求一個Url,Ocelot就必定轉發給某一個固定的下遊服務,但這樣對于一個系統來說,這是不安全的,因為有可能某一個下遊服務阻塞,甚至挂掉了,那就可能導緻整個服務癱瘓了,對于目前快速運轉的網際網路時代,這是不允許的。

Ocelot能夠通過可用的下遊服務對每個路由進行負載平衡。我們來看看具體的路由配置

{
    "DownstreamPathTemplate": "/api/ocelot/{postId}",
    "DownstreamScheme": "http",
    "DownstreamHostAndPorts": [
    {
        "Host": "localhost",
        "Port": 8001
    },
    {
        "Host": "localhost",
        "Port": 8002
    }
    ],
    "UpstreamPathTemplate": "/ocelot/{postId}",
    "UpstreamHttpMethod": [ "Get" ],
    "LoadBalancerOptions": {
    "Type": "RoundRobin"
    }
}
           

LeadConnection

負載均衡器算法共有4種:

  • LeastConnection 把新請求發送到現有請求最少的服務上
  • RoundRobin 輪詢可用的服務并發送請求
  • NoLoadBalancer 不負載均衡,總是發往第一個可用的下遊服務
  • CookieStickySessions 使用cookie關聯所有相關的請求到制定的服務

為了能快速驗證負載均衡器的有效性,我們這個案例中采用了

RoundRobin

輪詢算法。然後下遊服務還是用了案例一中建立的基本服務,在IIS中部署兩套同樣的下遊服務,分别占用端口8001和8002。

當我們第一次請求

http://localhost:4727/ocelot/5

,得到的是端口8001的傳回結果

而當我們再次請求

http://localhost:4727/ocelot/5

,得到的是端口8002的傳回結果

Ocelot(二)- 請求聚合與負載均衡

再次請求則又是8001的傳回結果,如此輪詢下去。

但需要注意的是,當我嘗試将8002端口服務停止時

我得到了這樣的結果:第一次請求得到8001的傳回結果,第二次請求得到的則是500的狀态碼

Ocelot(二)- 請求聚合與負載均衡

根據官網文檔的說明

RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot’s.
           

的确說的是輪詢可用的服務,似乎與我的測試結果不相符。不知道是我的測試環境出了問題,還是我某個環節配置錯誤,亦或是這個算法真的沒有避開不可用的服務。希望有知道的朋友在留言區給我解惑,感謝。

在本案例中,我就不再展開示範另外3種算法了,其中

NoLoadBalancer

會與服務發現的案例再進行深入探讨。

總結

本來今天是想給大家寫多兩個功能案例的,奈何這個進階的資料實在不多,當然也有我自己一方面實力不足的原因,導緻花了很長的時間進行消化。在本文中介紹了Ocelot的請求聚合與負載均衡,其中請求聚合在使用的過程中還是有幾點需要注意的,負載均衡則需要大家按需選擇适合自己系統的算法。後續還會有Ocelot的系列文章,希望大家持續關注。

作者: markjiang7m2 出處:

https://www.cnblogs.com/markjiang7m2/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

Ocelot(二)- 請求聚合與負載均衡