1. 程式人生 > >Ocelot簡易教程(四)之請求聚合以及服務發現

Ocelot簡易教程(四)之請求聚合以及服務發現

上篇文章給大家講解了Ocelot的一些特性並對路由進行了詳細的介紹,今天呢就大家一起來學習下Ocelot的請求聚合以及服務發現功能。希望能對大家有所幫助。

請求聚合

Ocelot允許你宣告聚合路由,這樣你可以把多個正常的ReRoutes打包並對映到一個物件來對客戶端的請求進行響應。比如,你請求訂單資訊,訂單中又包含商品資訊,這裡就設計到兩個微服務,一個是商品服務,一個是訂單服務。如果不運用聚合路由的話,對於一個訂單資訊,客戶端可能需要請求兩次服務端。實際上這會造成服務端額外的開銷。這時候有了聚合路由後,你只需要請求一次聚合路由,然後聚合路由會合並訂單跟商品的結果都一個物件中,並把這個物件響應給客戶端。使用Ocelot的此特性可以讓你很容易的實現前後端分離的架構。
為了實現Ocelot的請求功能,你需要在ocelot.json中進行如下的配置。這裡我們指定了了兩個正常的ReRoutes,然後給每個ReRoute設定一個Key屬性。最後我們再Aggregates節點中的ReRouteKeys屬性中加入我們剛剛指定的兩個Key從而組成了兩個ReRoutes的聚合。當然我們還需要設定UpstreamPathTemplate匹配上游的使用者請求,它的工作方式與正常的ReRoute類似。

注意:不要把Aggregates中UpstreamPathTemplate設定的跟ReRoutes中的UpstreamPathTemplate設定成一樣。

在開始例項前先把我們的ocelot Nuget包升級到最新的12.0.0版本,當然你也可以不進行升級。這裡需要注意一下,如果你升級到12.0.0的版本的話,那麼你config.AddOcelot()的用法會發生改變,需要傳入引數config.AddOcelot(hostingContext.HostingEnvironment)

1.為了演示的需要這裡我們新增一個類庫專案,分別新建兩個類,一個是商品Good類,一個是訂單Order類(這裡只是為了演示的需要,所以程式碼很簡陋)如下所示:

 public class Goods
    {
        public int Id { get; set; }
        public string Content { get; set; }
    }

public class Orders
    {
        public int Id { get; set; }
        public string Content { get; set; }
    }
  1. 接下來我們給OrderApi以及GoodApi分別新建一個控制器,並返回相應的實體。如下所示:

    //GoodApi專案中
        [Route("api/[controller]")]
        [ApiController]
        public class GoodController : ControllerBase
        {
            // GET api/Good/5
            [HttpGet("{id}")]
            public ActionResult<string> Get(int id)
            {
                var item = new Goods
                {
                    Id = id,
                    Content = $"{id}的關聯的商品明細",
                };
                return JsonConvert.SerializeObject(item);
            }
        }
      //OrderApi專案中  
    [Route("api/[controller]")]
        [ApiController]
        public class OrderController : ControllerBase
        {
            // GET api/Order/5
            [HttpGet("{id}")]
            public ActionResult<string> Get(int id)
            {
                var item = new Orders {
                    Id=id,
                    Content=$"{id}的訂單明細",
                };
                return JsonConvert.SerializeObject(item);
            }
        }
  2. 接下來我們分別在ocelot.good.json以及ocelot.order.json中新增一個路由,並給出Keys.如下所示:

    這裡注意,跟上篇文章中的路由不同的是,這裡多了一個Key屬性。

    //ocelot.good.json
    {
          "DownstreamPathTemplate": "/api/Good/{id}",
          "DownstreamScheme": "http",
          "DownstreamHostAndPorts": [
            {
              "Host": "localhost",
              "Port": 1001
            }
          ],
          "UpstreamPathTemplate": "/good/{id}",
          "UpstreamHttpMethod": [ "Get", "Post" ],
          "Key": "Good",
          "Priority": 2
        }
    //ocelot.order.json
    {
          "DownstreamPathTemplate": "/api/Order/{id}",
          "DownstreamScheme": "http",
          "DownstreamHostAndPorts": [
            {
              "Host": "localhost",
              "Port": 1002
            }
          ],
          "UpstreamPathTemplate": "/order/{id}",
          "UpstreamHttpMethod": [ "Get", "Post" ],
          "Key": "Order",
          "Priority": 2
        }
  3. 在ocelot.all.json中加入聚合配置,如下所示:

      "Aggregates": [
        {
          "ReRouteKeys": [
            "Good",
            "Order"
          ],
          "UpstreamPathTemplate": "/GetOrderDetail/{id}"
        }
      ]

    注意:這裡AggregatesReRoutes同級,ReRouteKeys中填寫的陣列就是上面步驟3中設定的Key屬性對應的值。

  4. 1537715634581
    格式化後代碼如下:

    {
        "Good":{
            "Id":1,
            "Content":"1的關聯的商品明細"
        },
        "Order":{
            "Id":1,
            "Content":"1的訂單明細"
        }
    }
  5. 眼尖的朋友可能已經猜到了。聚合路由返回的內容就是json串。json串由ReRouteKeys組成,每個Key的內容就是具體下游響應的內容了!例項程式碼已經同步更新到Github上,地址:https://github.com/yilezhu/OcelotDemo

    Ocelot將始終使用聚合請求返回內容型別application/json。還有需要注意的是聚合請求不會返回404請求。如果兩個下游都返回404狀態碼的話,這裡聚合後的響應也不會返回404,只會返回空的json串,拿上面的例項,如果兩個下游都返回404的話,那麼他的響應程式碼類似下面這樣:

    {
       "Good": ,
       "Order":
    }

    如果下游服務返回404,則聚合將僅為該下游服務返回任何內容。即使所有下游都返回404,它也不會將聚合響應更改為404。

服務發現

Ocelot允許您指定服務發現提供程式,並將使用它來查詢Ocelot將請求轉發到的下游服務的主機和埠。目前,這僅在GlobalConfiguration部分中受支援,這意味著相同的服務發現提供程式將用於為ReRoute級別指定ServiceName的所有ReRoutes。

Consul

在使用Consul前你首先要做的就是安裝在Ocelot中提供Consul支援的NuGet包
Install-Package Ocelot.Provider.Consul
然後將下面的內容新增在ConfigureServices方法中

services.AddOcelot()//注入Ocelot服務
                    .AddConsul(); 

GlobalConfiguration中需要加入以下內容。如果您未指定主機和埠,則將使用Consul預設值。

"ServiceDiscoveryProvider": {
    "Host": "localhost",
    "Port": 8500,
    "Type": "Consul"
}

注意:如果你採用AddOcelot()這種方式來自動載入ocelot配置檔案的方式,那麼你需要新建一個ocelot.global.json檔案,然後加入上面的配置:如下所示:

{
"GlobalConfiguration": {
  "ServiceDiscoveryProvider": {
    "Host": "localhost",
    "Port": 8500,
    "Type": "Consul"
  }
}
}
然後重新執行dotnet run命令會自動合併配置資訊到Ocelot.json中,生成的對應內容如下:
​```C#
"ServiceDiscoveryProvider": {
      "Host": "localhost",
      "Port": 8500,
      "Type": "Consul",
      "Token": null,
      "ConfigurationKey": null,
      "PollingInterval": 0
    }

這個上篇文章中已經進行了相關的介紹。

為了告訴Ocelot ReRoute是為其主機和埠使用服務發現提供程式,您必須在下游請求時新增要使用的ServiceName和負載均衡器。目前,Ocelot可以使用RoundRobin和LeastConnection演算法。如果未指定負載均衡器,則Ocelot將不會對請求進行負載均衡。

{
    "DownstreamPathTemplate": "/api/posts/{postId}",
    "DownstreamScheme": "https",
    "UpstreamPathTemplate": "/posts/{postId}",
    "UpstreamHttpMethod": [ "Put" ],
    "ServiceName": "product",
    "LoadBalancerOptions": {
        "Type": "LeastConnection"
    },
}

設定此項後,Ocelot將從服務發現提供程式中查詢下游主機和埠,並跨任何可用服務進行負載平衡請求。

動態路由

作者的想法是在使用服務發現提供程式時啟用動態路由。在此模式下,Ocelot將使用上游路徑的第一個段來與服務發現提供程式一起查詢下游服務。

例如,使用https://api.yilezhu.cn/product/products 等網址呼叫ocelot 。Ocelot將採用產品路徑的第一部分product,並將其用作在Consul中查詢服務的Key。如果consul返回一個服務,Ocelot將使用從consul返回的主機和埠以及剩餘路徑段組合後的Url來進行請求的響應。,如:http:// hostfromconsul:portfromconsul/products。Ocelot將正常向下游URL轉發查詢字串。即query

要啟用動態路由,您需要在配置中保留0個ReRoutes。目前您無法混合動態和配置ReRoutes。除此之外,您還需要指定上面概述的Service Discovery提供程式詳細資訊和下游http / https方案作為DownstreamScheme。

除此之外,您還可以設定RateLimitOptions,QoSOptions,LoadBalancerOptions和HttpHandlerOptions,DownstreamScheme(您可能希望在https上呼叫Ocelot,但可以通過http與私有服務進行通訊),這些將應用於所有動態ReRoutes。

配置可能看起來像:

{
    "ReRoutes": [],
    "Aggregates": [],
    "GlobalConfiguration": {
        "RequestIdKey": null,
        "ServiceDiscoveryProvider": {
            "Host": "localhost",
            "Port": 8500,
            "Type": "Consul",
            "Token": null,
            "ConfigurationKey": null
        },
        "RateLimitOptions": {
            "ClientIdHeader": "ClientId",
            "QuotaExceededMessage": null,
            "RateLimitCounterPrefix": "ocelot",
            "DisableRateLimitHeaders": false,
            "HttpStatusCode": 429
        },
        "QoSOptions": {
            "ExceptionsAllowedBeforeBreaking": 0,
            "DurationOfBreak": 0,
            "TimeoutValue": 0
        },
        "BaseUrl": null,
            "LoadBalancerOptions": {
            "Type": "LeastConnection",
            "Key": null,
            "Expiry": 0
        },
        "DownstreamScheme": "http",
        "HttpHandlerOptions": {
            "AllowAutoRedirect": false,
            "UseCookieContainer": false,
            "UseTracing": false
        }
    }
}

Ocelot還允許您設定DynamicReRoutes,允許您為每個下游服務設定速率限制規則。如果您有一個產品和搜尋服務,並且您希望對另一個進行速率限制,則此功能非常有用。這方面的一個例子如下。

{
    "DynamicReRoutes": [
        {
        "ServiceName": "product",
        "RateLimitRule": {
                "ClientWhitelist": [],
                "EnableRateLimiting": true,
                "Period": "1s",
                "PeriodTimespan": 1000.0,
                "Limit": 3
            }
        }
    ],
    "GlobalConfiguration": {
        "RequestIdKey": null,
        "ServiceDiscoveryProvider": {
            "Host": "localhost",
            "Port": 8523,
            "Type": "Consul"
        },
        "RateLimitOptions": {
            "ClientIdHeader": "ClientId",
            "QuotaExceededMessage": "",
            "RateLimitCounterPrefix": "",
            "DisableRateLimitHeaders": false,
            "HttpStatusCode": 428
        }
        "DownstreamScheme": "http",
    }
}

此配置意味著如果您在/product/上進入Ocelot請求,則動態路由將啟動,並且ocelot將使用針對DynamicReRoutes部分中的產品服務的速率限制設定。

GitHub地址

Ocelot簡易教程目錄