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>
。咱們再來看看DownstreamContext
:github
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#892和issue#890。docker
既然如此,那我就按照它內部的輸出結果來一遍,固然我這裏沒有嚴格按照官方處理過程,只是簡單的輸出。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");
}
輸出結果:數據庫
官方開放了這麼多信息,相信開發者還可使用進階請求聚合作更多的東西,歡迎你們繼續研究探討,我這裏就暫時介紹到這裏了。
案例四 服務發現
終於到咱們今天的正題——服務發現。關於服務發現,個人我的理解是在這個微服務時代,當下遊服務太多的時候,咱們就須要找一個專門的工具記錄這些服務的地址和端口等信息,這樣會更加便於對服務的管理,而當上遊服務向這個專門記錄的工具查詢某個服務信息的過程,就是服務發現。
舉個例子,之前我要找的人也就只有Willing和Jack,因此我只要本身用本子(數據庫)記住他們兩個的位置就能夠了,那隨着公司發展,部門的人愈來愈多,他們常常會調換位置,還有入職離職的人員,這就致使我本子記錄的信息沒有更新,因此我找來了HR部門(Consul)幫忙統一管理,全部人有信息更新都要到HR部門那裏進行登記(服務註冊),而後當我(上游服務)想找人作某件事情(發出請求)的時候,我先到HR那裏查詢能夠幫我完成這個任務的人員在哪裏(服務發現),獲得這些人員的位置信息,我也就能夠選中某一我的幫我完成任務了。
這裏會涉及到的記錄工具,就是Consul
。流程圖以下:
固然了,在上面這個例子中好像沒有Ocelot什麼事,可是這樣就須要我每次要找人的時候,都必須先跑到Consul那裏查詢一次位置信息,而後再根據位置信息去找對應的人。而其實這個過程咱們是能夠交給Ocelot來完成的。這樣,每一個下游服務都不須要單獨跑一趟了,只專一於完成本身的任務就能夠了。流程圖以下:
一般當服務在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
同時也能夠在瀏覽器查看集羣的狀態。由於我在節點1中啓動了WEB UI,並且映射到外部端口8500,因此我在瀏覽器直接訪問http://localhost:8500/
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" } }'
我爲了後面能實現負載均衡的效果,所以,也將8002端口的服務也一併註冊進來,命令跟上面同樣,只是要將端口號更換爲8002就能夠了。
多說一句,關於這個命令行,其實就是用命令行的方式調用Consul服務註冊的接口,因此在實際項目中,能夠將這個註冊接口調用放在下游服務的Startup.cs
中,當下遊服務運行即註冊,還有註銷接口調用也是同樣的道理。
服務發現
直接經過瀏覽器或者PowerShell命令行均可以進行服務發現過程。
瀏覽器訪問http://localhost:8500/v1/catalog/service/ocelotService
或者命令行curl http://localhost:8500/v1/catalog/service/ocelotService
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
由於咱們在這組路由中配置了使用輪詢的方式進行負載均衡,因此能夠看到咱們的訪問結果中,是分別從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
}
}
}
運行結果以下:
由於使用動態路由就要清空其它的路由配置,所以,我就不將動態路由這部分的配置commit到倉庫中了,你們要使用的時候可將我案例中的配置直接複製到Ocelot.json
文件中便可。
總結
Ocelot發佈13.5.1這個版本仍是挺有驚喜的,並且正巧我剛作完請求聚合的案例,因此也方便你們實踐。服務發現,就Ocelot而言只是很小的一個篇幅,由於確實只要配置幾個參數就能夠靈活運用了,但在於Consul提供程序,還有Docker,這兩個都是新的知識點,對於已經接觸過的朋友很快就能搭建出來,但對於還沒玩過的朋友,就須要花點時間研究。 OK,今天就先跟你們介紹到這裏,但願你們能持續關注咱們。