Ocelot(二)- 請求聚合

来源:https://www.cnblogs.com/markjiang7m2/archive/2019/05/15/10865511.html
-Advertisement-
Play Games

Ocelot(二) 請求聚合與負載均衡 作者:markjiang7m2 原文地址: 源碼地址:https://gitee.com/Sevenm2/OcelotDemo 在上一篇Ocelot的文章中,我已經給大家介紹了何為Ocelot以及如何簡單使用它的路由功能,如果你還沒有不瞭解Ocelot為何物, ...


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

作者:markjiang7m2
原文地址:https://www.cnblogs.com/markjiang7m2/p/10865511.html
源碼地址:https://gitee.com/Sevenm2/OcelotDemo

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

開篇題外話:在上一篇文章的案例中,我直接使用API返回伺服器的埠和介面的路徑,我感覺這樣舉例過於偏技術化,比較沉悶,然後我想到了之前參加PMP課程培訓時候,我們的培訓講師——孫志斌老師引用小王老李的模型給我們講述項目管理的各種實戰,可謂是生動形象,而且所舉的例子也非常貼近我們的日常工作,通俗易懂,因此,我也嘗試使用類似的人物形象進行案例的講解。首先,本文將會引入兩個人物WillingJack。Willing是一名資深專家,工作多年,而Jack則是.NET新手。

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

案例二 請求聚合

我們在案例一路由中已經知道,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屬性。AggregatesReRoutes是同級的,而且也是一個數組,這代表著我們可以聲明多個聚合路由,而在我們聲明的這一組聚合路由中的屬性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_002_aggrjack

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

可以看到,在返回結果中同時包含了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_004_aggrerror

直接通過聚合路由訪問
Ocelot_005_aggrleadererror

前面我說到返回結果好像有哪裡不太對,那到底是哪裡出錯了呢?我來將返回的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_006_aggrleaderjson

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

  • 僅支持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>();

運行,訪問進階請求聚合的Urlhttp://localhost:4727/aggrLeaderAdvanced,得到如下結果:
Ocelot_007_aggrleaderadvanced

也許大家已經留意到,我在處理返回結果是,並沒有像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;
}

Startup.cs

services.AddSingleton<LeaderAdvancedDependency>();

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

Ocelot除了支持Singleton的聚合器以外,還支持Transient的聚合器,大家可以按需使用。
Startup.cs

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的返回結果

Ocelot_008_balance8001

而當我們再次請求http://localhost:4727/ocelot/5,得到的是埠8002的返回結果

Ocelot_009_balance8002

再次請求則又是8001的返回結果,如此輪詢下去。
但需要註意的是,當我嘗試將8002埠服務停止時

Ocelot_010_balanceiis

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

Ocelot_011_balanceerror

根據官網文檔的說明

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

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

在本案例中,我就不再展開演示另外3種演算法了,其中NoLoadBalancer會與服務發現的案例再進行深入探討。

總結

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

dotNet框架學苑


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 加Q[965546358]獲取資源 第1章 課程導學 第2章 小程式開發入門 從幾個方面介紹小程式開發相關的內容,包括小程式開發者賬號註冊、小程式開發流程、小程式開發規範、小程式常用的API,例如網路請求、本地緩存等API,以及小程式組件等等的知識點。 第3章 深入Django視圖層 分層次介紹Dj ...
  • 樹 概念:樹是一些節點的集合,一棵樹由稱作根(root)的節點 r 以及0個或多個非空的(子)樹組成,這些子樹中每一棵的根都被來自根 r 的一條有向的邊(edge)連接。每一棵子樹的根叫做根 r 的兒子(child),r 是每一棵子樹的根的父親(parent)。一棵樹是N個 節點和N-1條邊的集合, ...
  • 我最近寫到了一個項目中用到了樹形圖,不得不說這個樹形圖是真的扯淡; 我用到的是layui中的樹形圖,再展示數據過程中遇到了很多的問題,廢話不多說,直接貼代碼。 一、調用排序介面,對數據進行排序。 二、生成樹結構 ...
  • 說明 操作系統:Windows 10 Python 版本:3.7x 虛擬環境管理器:virtualenv 代碼編輯器:VS Code 實驗 環境初始化 實驗示例 Hello World 程式運行效果如下圖所示: 此時,我們可以通過 Swagger UI 或者 curl 來請求我們上面創建的 一個 和 ...
  • 在C#中字元串類型String是由一系列的單個字元組合而成,其實可以通過字元串String對象ToCharArray()方法來將字元串中的元素逐一存在數據類型為Char的一維數組中。 例如將字元str = "ABCDEFG"分割為到一維數組可用下列語句: 通過上述語句可得到以下結果: 備註:原文轉載 ...
  • 針對公網上線的網站系統,很多網站的功能變數名稱會同時含有帶www和不帶www的功能變數名稱解析記錄,如果需要同時解析帶www和不帶www的功能變數名稱信息,則需要在相應的功能變數名稱解析平臺(如阿裡雲功能變數名稱解析平臺、騰訊雲功能變數名稱解析平臺)設置不帶www的主功能變數名稱以及帶www的功能變數名稱解析記錄。同時在Web伺服器如IIS中配置對應的網站,II ...
  • 在IIS Web伺服器的網站配置的過程中,有時候需要一個網站配置對應多個功能變數名稱記錄,例如不帶www的主功能變數名稱以及帶www的功能變數名稱解析記錄對應同一個網站文件,此時最簡單的配置方法就是將一個網站綁定多個主機功能變數名稱,比新建多個網站然後對應一個網站文件簡單很多,並且後續修改網站相關配置的時候只需要修改一個網站即可。 ...
  • C#開發過程中針對字元串String類型的操作是常見操作,有時候業務需要判斷某個字元串是否以特定字元開頭或者特定字元結束,此時就可使用StartsWith方法來判斷目標字元串是否以特定字元串開頭,通過EndWith方法可判斷是否以某字元串結尾。 StartWith函數調用的格式為: strA.Sta ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...