欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Ocelot(三)- 服务发现

程序员文章站 2022-03-20 12:14:08
Ocelot(三) 服务发现 作者:markjiang7m2 原文地址: 源码地址:https://gitee.com/Sevenm2/OcelotDemo 本文是我关于Ocelot系列文章的第三篇,主要是给大家介绍Ocelot的另一功能。与其说是给大家介绍,不如说是我们一起来共同探讨,因为我也是在 ......

ocelot(三)- 服务发现

作者:markjiang7m2
原文地址:
源码地址: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(三)- 服务发现

官方开放了这么多信息,相信开发者还可以使用进阶请求聚合做更多的东西,欢迎大家继续研究探讨,我这里就暂时介绍到这里了。

案例四 服务发现

终于到我们今天的正题——服务发现。关于服务发现,我的个人理解是在这个微服务时代,当下游服务太多的时候,我们就需要找一个专门的工具记录这些服务的地址和端口等信息,这样会更加便于对服务的管理,而当上游服务向这个专门记录的工具查询某个服务信息的过程,就是服务发现。

举个例子,以前我要找的人也就只有willing和jack,所以我只要自己用本子(数据库)记住他们两个的位置就可以了,那随着公司发展,部门的人越来越多,他们经常会调换位置,还有入职离职的人员,这就导致我本子记录的信息没有更新,所以我找来了hr部门(consul)帮忙统一管理,所有人有信息更新都要到hr部门那里进行登记(服务注册),然后当我(上游服务)想找人做某件事情(发出请求)的时候,我先到hr那里查询可以帮我完成这个任务的人员在哪里(服务发现),得到这些人员的位置信息,我也就可以选中某一个人帮我完成任务了。

这里会涉及到的记录工具,就是consul。流程图如下:

Ocelot(三)- 服务发现

当然了,在上面这个例子中好像没有ocelot什么事,但是这样就需要我每次要找人的时候,都必须先跑到consul那里查询一次位置信息,然后再根据位置信息去找对应的人。而其实这个过程我们是可以交给ocelot来完成的。这样,每个下游服务都不需要单独跑一趟了,只专注于完成自己的任务就可以了。流程图如下:

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

Ocelot(三)- 服务发现

同时也可以在浏览器查看集群的状态。因为我在节点1中启动了web ui,而且映射到外部端口8500,所以我在浏览器直接访问http://localhost:8500/

Ocelot(三)- 服务发现

Ocelot(三)- 服务发现

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(三)- 服务发现

我为了后面能实现负载均衡的效果,因此,也将8002端口的服务也一并注册进来,命令跟上面一样,只是要将端口号更换为8002就可以了。

多说一句,关于这个命令行,其实就是用命令行的方式调用consul服务注册的接口,所以在实际项目中,可以将这个注册接口调用放在下游服务的startup.cs中,当下游服务运行即注册,还有注销接口调用也是一样的道理。

Ocelot(三)- 服务发现

Ocelot(三)- 服务发现

服务发现
直接通过浏览器或者powershell命令行都可以进行服务发现过程。
浏览器访问http://localhost:8500/v1/catalog/service/ocelotservice
或者命令行curl http://localhost:8500/v1/catalog/service/ocelotservice

Ocelot(三)- 服务发现

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(三)- 服务发现

Ocelot(三)- 服务发现

因为我们在这组路由中配置了使用轮询的方式进行负载均衡,所以可以看到我们的访问结果中,是分别从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(三)- 服务发现

因为使用动态路由就要清空其它的路由配置,因此,我就不将动态路由这部分的配置commit到仓库中了,大家要使用的时候可将我案例中的配置直接复制到ocelot.json文件中即可。

总结

ocelot发布13.5.1这个版本还是挺有惊喜的,而且正巧我刚做完请求聚合的案例,所以也方便大家实践。服务发现,就ocelot而言只是很小的一个篇幅,因为确实只要配置几个参数就可以灵活运用了,但在于consul提供程序,还有docker,这两个都是新的知识点,对于已经接触过的朋友很快就能搭建出来,但对于还没玩过的朋友,就需要花点时间研究。
ok,今天就先跟大家介绍到这里,希望大家能持续关注我们。

Ocelot(三)- 服务发现