Ocelot(三)- 服務發現


Ocelot(三)- 服務發現

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

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

關於更多的Ocelot功能介紹,可以查看我的系列文章

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

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

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

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

13.5.1

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

參數類型從List<DownstreamResponse>更改為List<DownstreamContext>。我們再來看看DownstreamContext

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#890

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

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,今天就先跟大家介紹到這里,希望大家能持續關注我們。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM