Ocelot(三)- 服务发现

Ocelot(三)- 服务发现

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

本文是我关于Ocelot系列文章的第三篇,主要是给你们介绍Ocelot的另外一功能。与其说是给你们介绍,不如说是咱们一块儿来共同探讨,由于我也是在一边学习实践的过程当中,顺便把学习的过程记录下来罢了。
正如本文要介绍的服务发现,在Ocelot中本该是一个较小的功能,但也许你们也注意到,这篇文章距离个人上一篇文章也有一个星期了。主要是由于Ocelot的服务发现支持提供程序Consul,而我对Consul并不怎么了解,所以花了比较长的时间去倒弄Consul。由于这个是关于Ocelot的系列文章,因此我暂时也不打算在本文中详细介绍Consul的功能以及搭建过程了,可能会在完成Ocelot系列文章后,再整理一篇关于Consul的文章。node

关于更多的Ocelot功能介绍,能够查看个人系列文章git

本文中涉及案例的完整代码均可以从个人代码仓库进行下载。github

Ocelot接口更新:进阶请求聚合

好了,也许你们有疑问,为何在这里又会重提请求聚合的内容?
在上一篇文章Ocelot(二)- 请求聚合与负载均衡中,我曾说到进阶请求聚合中,因为Aggregate方法中提供的参数类型只有List<DownstreamResponse>,但DownstreamResponse中并无关于ReRouteKeys的信息,因此处理返回结果时,并无像Ocelot内部返回结果同样使用路由的Key做为属性。
而后,今天我注意到了Ocelot有新版本发布,因而我作了更新,从13.5.0更新到了13.5.1,而后竟然是有意外惊喜。
接口方法Aggregate更新以下:
13.5.0docker

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

13.5.1shell

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

既然如此,那我就按照它内部的输出结果来一遍,固然我这里没有严格按照官方处理过程,只是简单的输出。bootstrap

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");
}

输出结果:c#

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,今天就先跟你们介绍到这里,但愿你们能持续关注咱们。

相关文章
相关标签/搜索