作者: markjiang7m2
源码地址: https://gitee.com/Sevenm2/OcelotDemo
本文是我关于 Ocelot 系列文章的第三篇, 主要是给大家介绍 Ocelot 的另一功能. 与其说是给大家介绍, 不如说是我们一起来共同探讨, 因为我也是在一边学习实践的过程中, 顺便把学习的过程记录下来罢了.
正如本文要介绍的服务发现, 在 Ocelot 中本该是一个较小的功能, 但也许大家也注意到, 这篇文章距离我的上一篇文章也有一个星期了. 主要是因为 Ocelot 的服务发现支持提供程序 Consul, 而我对 Consul 并不怎么了解, 因此花了比较长的时间去倒弄 Consul. 因为这个是关于 Ocelot 的系列文章, 所以我暂时也不打算在本文中详细介绍 Consul 的功能以及搭建过程了, 可能会在完成 Ocelot 系列文章后, 再整理一篇关于 Consul 的文章.
关于更多的 Ocelot 功能介绍, 可以查看我的系列文章
Ocelot - .Net Core 开源网关
Ocelot(二)- 请求聚合与负载均衡
本文中涉及案例的完整代码都可以从我的代码仓库进行下载.
仓库地址: https://gitee.com/Sevenm2/OcelotDemo
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 https://github.com/ThreeMammals/Ocelot/pull/892 和 issue#890 https://github.com/ThreeMammals/Ocelot/pull/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>/
例如:
当 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, 今天就先跟大家介绍到这里, 希望大家能持续关注我们.
来源: https://www.cnblogs.com/letyouknowdotnet/p/10972495.html