Ocelot(三) 服務發現 作者:markjiang7m2 原文地址: 源碼地址:https://gitee.com/Sevenm2/OcelotDemo 本文是我關於Ocelot系列文章的第三篇,主要是給大家介紹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#892和issue#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");
}
輸出結果:
官方開放了這麼多信息,相信開發者還可以使用進階請求聚合做更多的東西,歡迎大家繼續研究探討,我這裡就暫時介紹到這裡了。
案例四 服務發現
終於到我們今天的正題——服務發現。關於服務發現,我的個人理解是在這個微服務時代,當下游服務太多的時候,我們就需要找一個專門的工具記錄這些服務的地址和埠等信息,這樣會更加便於對服務的管理,而當上游服務向這個專門記錄的工具查詢某個服務信息的過程,就是服務發現。
舉個例子,以前我要找的人也就只有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,今天就先跟大家介紹到這裡,希望大家能持續關註我們。