Ocelot(三)- 服務發現

Ocelot(三)- 服務發現

做者:markjiang7m2
原文地址:http://www.javashuo.com/article/p-fpwgldxy-ek.html
源碼地址:https://gitee.com/Sevenm2/OcelotDemojavascript

本文是我關於Ocelot系列文章的第三篇,主要是給你們介紹Ocelot的另外一功能。與其說是給你們介紹,不如說是咱們一塊兒來共同探討,由於我也是在一邊學習實踐的過程當中,順便把學習的過程記錄下來罷了。
正如本文要介紹的服務發現,在Ocelot中本該是一個較小的功能,但也許你們也注意到,這篇文章距離個人上一篇文章也有一個星期了。主要是由於Ocelot的服務發現支持提供程序Consul,而我對Consul並不怎麼了解,所以花了比較長的時間去倒弄Consul。由於這個是關於Ocelot的系列文章,因此我暫時也不打算在本文中詳細介紹Consul的功能以及搭建過程了,可能會在完成Ocelot系列文章後,再整理一篇關於Consul的文章。css

關於更多的Ocelot功能介紹,能夠查看個人系列文章html

本文中涉及案例的完整代碼均可以從個人代碼倉庫進行下載。java

Ocelot接口更新:進階請求聚合

好了,也許你們有疑問,爲何在這裏又會重提請求聚合的內容?
在上一篇文章Ocelot(二)- 請求聚合與負載均衡中,我曾說到進階請求聚合中,因爲Aggregate方法中提供的參數類型只有List<DownstreamResponse>,但DownstreamResponse中並無關於ReRouteKeys的信息,因此處理返回結果時,並無像Ocelot內部返回結果同樣使用路由的Key做爲屬性。
而後,今天我注意到了Ocelot有新版本發佈,因而我作了更新,從13.5.0更新到了13.5.1,而後竟然是有意外驚喜。
接口方法Aggregate更新以下:
13.5.0node

public interface IDefinedAggregator {
    Task<DownstreamResponse> Aggregate(List<DownstreamResponse> responses);
}

13.5.1git

public interface IDefinedAggregator {
    Task<DownstreamResponse> Aggregate(List<DownstreamContext> responses);
}

參數類型從List<DownstreamResponse>更改成List<DownstreamContext>。咱們再來看看DownstreamContextgithub

public class DownstreamContext
{
    public DownstreamContext(HttpContext httpContext);

    public List<PlaceholderNameAndValue> TemplatePlaceholderNameAndValues { get; set; }
    public HttpContext HttpContext { get; }
    public DownstreamReRoute DownstreamReRoute { get; set; }
    public DownstreamRequest DownstreamRequest { get; set; }
    public DownstreamResponse DownstreamResponse { get; set; }
    public List<Error> Errors { get; }
    public IInternalConfiguration Configuration { get; set; }
    public bool IsError { get; }
}

事實上,若是你有看過Ocelot內部處理請求聚合部分的代碼,就會發現它使用的就是DownstreamContext,而現在Ocelot已經將這些路由,配置,請求,響應,錯誤等信息都開放出來了。哈哈,固然,GitHub上面的realease note,人家主要是爲了讓開發者可以捕獲處理下游服務發生的錯誤,更多信息能夠查看issue#892issue#890docker

既然如此,那我就按照它內部的輸出結果來一遍,固然我這裏沒有嚴格按照官方處理過程,只是簡單的輸出。shell

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

    contentBuilder.Append("{");

    foreach (var down in responses)
    {
        string content = await down.DownstreamResponse.Content.ReadAsStringAsync();
        results.Add($"\"{down.DownstreamReRoute.Key}\":{content}");
    }
    //來自leader的聲音
    results.Add($"\"leader\":{{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.DownstreamResponse.Headers).ToList();
    return new DownstreamResponse(stringContent, HttpStatusCode.OK, headers, "some reason");
}

輸出結果:數據庫

Ocelot_012_aggrleaderadvance_new

官方開放了這麼多信息,相信開發者還可使用進階請求聚合作更多的東西,歡迎你們繼續研究探討,我這裏就暫時介紹到這裏了。

案例四 服務發現

終於到咱們今天的正題——服務發現。關於服務發現,個人我的理解是在這個微服務時代,當下遊服務太多的時候,咱們就須要找一個專門的工具記錄這些服務的地址和端口等信息,這樣會更加便於對服務的管理,而當上遊服務向這個專門記錄的工具查詢某個服務信息的過程,就是服務發現。

舉個例子,之前我要找的人也就只有Willing和Jack,因此我只要本身用本子(數據庫)記住他們兩個的位置就能夠了,那隨着公司發展,部門的人愈來愈多,他們常常會調換位置,還有入職離職的人員,這就致使我本子記錄的信息沒有更新,因此我找來了HR部門(Consul)幫忙統一管理,全部人有信息更新都要到HR部門那裏進行登記(服務註冊),而後當我(上游服務)想找人作某件事情(發出請求)的時候,我先到HR那裏查詢能夠幫我完成這個任務的人員在哪裏(服務發現),獲得這些人員的位置信息,我也就能夠選中某一我的幫我完成任務了。

這裏會涉及到的記錄工具,就是Consul。流程圖以下:

Ocelot_013_consul

固然了,在上面這個例子中好像沒有Ocelot什麼事,可是這樣就須要我每次要找人的時候,都必須先跑到Consul那裏查詢一次位置信息,而後再根據位置信息去找對應的人。而其實這個過程咱們是能夠交給Ocelot來完成的。這樣,每一個下游服務都不須要單獨跑一趟了,只專一於完成本身的任務就能夠了。流程圖以下:

Ocelot_014_consulocelot

一般當服務在10個以上的時候能夠考慮使用服務發現。

關於Consul的介紹跟使用說明,網上已經有不少相關資料,因此我這裏是基於你們都瞭解Consul的狀況下的介紹。
官方建議每一個Consul Cluster至少有3個或以上的運行在Server Mode的Agent,Client節點不限。因爲我就這麼一臺機子,又不想搞虛擬機,因此我就直接用了Docker來部署使用Consul。
(可能這裏又涉及到了一個Docker的知識點,我這裏暫時也不展開細說了。)

由於我電腦的系統是Windows的,因此安裝的Docker for Windows。

拉取鏡像
Docker安裝好以後,就用Windows自帶的PowerShell運行下面的命令,拉取官方的Consul鏡像。

docker pull consul

啓動Consul
節點1

docker run -d -p 8500:8500 --name markserver1 consul agent -server -node marknode1 -bootstrap-expect 3 -data-dir=/tmp/consul -client="0.0.0.0" -ui

-ui 啓用 WEB UI,由於Consul節點啓動默認佔用8500端口,所以8500:8500將節點容器內部的8500端口映射到外部8500,能夠方便經過Web的方式查看Consul集羣的狀態。默認數據中心爲dc1。

查看markserver1的IP

docker inspect -f '{{.NetworkSettings.IPAddress}}' markserver1

假設大家跟我同樣,獲取到的IP地址也是172.17.0.2

節點2

docker run -d --name markserver2 consul agent -server -node marknode2 -join 172.17.0.2

啓動節點markserver2,而且將該節點加入到markserver1中(-join 172.17.0.2)

節點3

docker run -d --name markserver3 consul agent -server -node marknode3 -join 172.17.0.2

節點4以Client模式

docker run -d --name markclient1 consul agent -node marknode4 -join 172.17.0.2

沒有-server參數,就會新建一個Client節點。

這個時候能夠查看一下數據中心dc1的節點

docker exec markserver1 consul members

Ocelot_015_consulmembers

同時也能夠在瀏覽器查看集羣的狀態。由於我在節點1中啓動了WEB UI,並且映射到外部端口8500,因此我在瀏覽器直接訪問http://localhost:8500/

Ocelot_016_consulservice

Ocelot_017_consulnode

OK,這樣咱們就已經啓動了Consul,接下來就是將咱們的下游服務註冊到Consul中。

服務註冊
爲了能讓本案例看到不同的效果,我特地新建了一個下游服務方法。在OcelotDownAPI項目中的Controller添加

// GET api/ocelot/consulWilling
[HttpGet("consulWilling")]
public async Task<IActionResult> ConsulWilling(int id) {
    var result = await Task.Run(() =>
    {
        ResponseResult response = new ResponseResult()
        { Comment = $"我是Willing,你能夠在Consul那裏找到個人信息, host: {HttpContext.Request.Host.Value}, path: {HttpContext.Request.Path}" };
        return response;
    });
    return Ok(result);
}

而後從新發布到本機上的8001和8002端口。

準備好下游服務後,就能夠進行註冊了。在PowerShell執行下面的命令:
<YourIP>爲我本機的IP地址,你們使用時注意替換。這裏須要使用IP,而不能直接用localhost或者127.0.0.1,由於個人Consul是部署在Docker裏面的,因此當Consul進行HealthCheck時,就沒法經過localhost訪問到我本機了。

curl http://localhost:8500/v1/agent/service/register -Method PUT -ContentType 'application/json' -Body '{
  "ID": "ocelotService1",  
  "Name": "ocelotService",
  "Tags": [
    "primary",
    "v1"
  ],
  "Address": "localhost",
  "Port": 8001,
  "EnableTagOverride": false,
  "Check": {
    "DeregisterCriticalServiceAfter": "90m",
    "HTTP": "http://<YourIP>:8001/api/ocelot/5",
    "Interval": "10s"
  }
}'

Ocelot_018_consulregister

我爲了後面能實現負載均衡的效果,所以,也將8002端口的服務也一併註冊進來,命令跟上面同樣,只是要將端口號更換爲8002就能夠了。

多說一句,關於這個命令行,其實就是用命令行的方式調用Consul服務註冊的接口,因此在實際項目中,能夠將這個註冊接口調用放在下游服務的Startup.cs中,當下遊服務運行即註冊,還有註銷接口調用也是同樣的道理。

Ocelot_019_consul_ocelotservice

Ocelot_020_consulcheck

服務發現
直接經過瀏覽器或者PowerShell命令行均可以進行服務發現過程。
瀏覽器訪問http://localhost:8500/v1/catalog/service/ocelotService
或者命令行curl http://localhost:8500/v1/catalog/service/ocelotService

Ocelot_023_consulcatalog

Ocelot添加Consul支持
OcelotDemo項目中安裝Consul支持,命令行或者直接使用Nuget搜索安裝

Install-Package Ocelot.Provider.Consul

在Startup.cs的ConfigureServices方法中

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

Ocelot路由配置
首先在ReRoutes中添加一組路由

{
    "DownstreamPathTemplate": "/api/ocelot/consulWilling",
    "DownstreamScheme": "http",
    "UpstreamPathTemplate": "/ocelot/consulWilling",
    "UpstreamHttpMethod": [ "Get" ],
    "LoadBalancerOptions": {
    "Type": "RoundRobin"
    },
    "ServiceName": "ocelotService",
    "Priority": 2
}

能夠發現這一組路由相對其它路由,少了DownstreamHostAndPorts,多了ServiceName,也就是這一組路由的下游服務,不是由Ocelot直接指定,而是經過Consul查詢獲得。

GlobalConfiguration添加ServiceDiscoveryProvider,指定服務發現支持程序爲Consul。

"GlobalConfiguration": {
"BaseUrl": "http://localhost:4727",
"ServiceDiscoveryProvider": {
    "Host": "localhost",
    "Port": 8500,
    "Type": "Consul"
}
}

運行OcelotDemo,並在瀏覽器中訪問http://localhost:4727/ocelot/consulWilling

Ocelot_021_consul8001

Ocelot_022_consul8002

由於咱們在這組路由中配置了使用輪詢的方式進行負載均衡,因此能夠看到咱們的訪問結果中,是分別從8001和8002中輪詢訪問的。

除了支持Consul,Ocelot還支持Eureka,我這裏暫時就不另外作案例了。

動態路由
當使用服務發現提供程序時,Ocelot支持使用動態路由。

上游服務請求Url模板:<Scheme>://<BaseUrl>/<ServiceName>/<ApiPath>/

例如:http://localhost:4727/ocelotService/api/ocelot/consulWilling

當Ocelot接收到請求,會向Consul查詢服務ocelotService的信息,例如獲取到對應IP爲localhost,Port爲8001,因而Ocelot會轉發請求到http://localhost:8001/api/ocelot/consulWilling.

Ocelot不支持動態路由與ReRoutes配置混合使用,所以,當咱們要使用動態路由,就必需要保證ReRoutes中沒有配置任何路由。

來看Ocelot.json的配置

{
  "ReRoutes": [],
  "Aggregates": [],
  "GlobalConfiguration": {
    "BaseUrl": "http://localhost:4727",
    "ServiceDiscoveryProvider": {
      "Host": "localhost",
      "Port": 8500,
      "Type": "Consul"
    },
    "DownstreamScheme": "http"
  }
}

這就是使用動態路由最簡單的配置,固然,在這種模式下還支持RateLimitOptions,QoSOptions,LoadBalancerOptions和HttpHandlerOptions,DownstreamScheme等配置,也容許針對每一個下游服務進行個性化設置,我這裏不演示具體案例。

{
    "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_024_consuldynamic

由於使用動態路由就要清空其它的路由配置,所以,我就不將動態路由這部分的配置commit到倉庫中了,你們要使用的時候可將我案例中的配置直接複製到Ocelot.json文件中便可。

總結

Ocelot發佈13.5.1這個版本仍是挺有驚喜的,並且正巧我剛作完請求聚合的案例,因此也方便你們實踐。服務發現,就Ocelot而言只是很小的一個篇幅,由於確實只要配置幾個參數就能夠靈活運用了,但在於Consul提供程序,還有Docker,這兩個都是新的知識點,對於已經接觸過的朋友很快就能搭建出來,但對於還沒玩過的朋友,就須要花點時間研究。 OK,今天就先跟你們介紹到這裏,但願你們能持續關注咱們。

相關文章
相關標籤/搜索