本系列相关文章:
本文将基于前两篇文章所述内容,继续介绍如何在服务发现和动态路由的基础上,使用Ocelot实现负载均衡。Ocelot本身是带有负载均衡功能的,这一点其实跟Nginx提供的HTTP load balancer是类似的功能(我觉得整个Ocelot提供的功能,通过Nginx也都可以实现,不过Ocelot更加.NET化,对于.NET开发人员来说更为简单和容易接受)。根据官方文档,Ocelot支持如下几种负载均衡策略:
- LeastConnection:根据服务当前正在处理的请求个数来决定将使用哪个服务来处理新接收到的请求,将请求转发给当前连接数最少的服务
- RoundRobin:经典模式,轮询法,逐个选择可用的服务来处理接收到的请求
- NoLoadBalancer:仅使用第一个可用的服务来处理接收到的请求
- CookieStickySessions:通过使用Cookie,确保特定的请求能够被分配到特定的服务上进行处理
今天我们选择RoundRobin来看看如何基于服务发现来实现负载均衡。同样,首先需要对架构进行调整。
调整架构
与上文中的架构相比,这里不会引入新的服务,而相比之下会让两个A服务的实例同时运行。调整后的架构如下图所示:
整个API的调用过程如下:
- A服务的两个实例、B服务以及API网关在启动的时候均向Spring Cloud Eureka注册自己
- API用户通过访问Eureka获得API网关的地址
- API用户使用获得的API网关地址,发送一个查询A服务的请求
- API网关根据指定的A服务的名称,从Eureka查询A服务所注册的服务实例
- API网关根据设定的负载均衡策略,向找到的服务实例发出请求,并将调用结果反馈给API用户
可以看到,前面部分的调用过程与上文所述都是非常类似的,不同的仅有API网关在寻找A服务的实例这个部分,前面是直接获得访问地址,而此处则通过负载均衡来选择一个地址。接下来,我们看看如何改变我们的代码,来实现这个架构。
代码修改
这里的代码修改会基于上文结尾时的代码,也就是实现了Ocelot的动态路由。首先,我们在计算服务(也就是A服务)中增加一个API,用以返回当前设置在主机上的machineName环境变量(如果设置为空,那么就直接返回主机机器名):
[Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { [HttpGet("info")] public ActionResult<string> Info() => Environment.GetEnvironmentVariable("machineName") ?? Environment.MachineName; // 其它代码省略 }
然后,就是配置Ocelot,使其能够实现负载均衡:
{ "ReRoutes": [ ], "GlobalConfiguration": { "RequestIdKey": "OcRequestId", "AdministrationPath": "/administration", "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 8761, "Type": "Eureka", "Token": null, "ConfigurationKey": null }, "LoadBalancerOptions": { "Type": "RoundRobin" }, "DownstreamScheme": "http" } }
只需注意上面的LoadBalancerOptions部分,这里我们采用了RoundRobin模式,这个配置文件的其它部分都与之前的一样,没有区别。
好了,代码改好了。什么?就改好了?对的,就是这么简单!接下来让我们测试一下。先在Eclipse里启动Spring Cloud Eureka:
然后,进入CalcService的编译输出目录,首先设置machineName环境变量,然后用下面的命令启动服务:
用同样的命令再启动另一个CalcService的实例:
OK,两个CalcService的实例已经启动,分别侦听49814和49815两个端口,接下来,启动我们的Ocelot API网关。等Ocelot API网关启动之后,查看Eureka的服务注册,可以看到所有的服务已经就绪:
请注意,对于CALC这个应用程序,我们可以看到,有两个实例已经注册成功。然后,我们通过访问API网关进而访问刚刚新加的Info API,可以看到,服务调用成功。然后按F5刷新,可见返回的结果会在CalcService-1与CalcService-2之间来回切换,也就意味着我们的请求被依次分配到两个不同的Calc服务的实例上执行。动图为证:
由此可见,我们已经实现了基于Ocelot API网关的负载均衡。当然,我们可以继续修改ASP.NET Core MVC的前端页面,让它能够直观地显示这个效果。这里也就不贴代码了,大家可以按本文后面的源代码链接下载源码,自己研究。
解决方案容器化
同样,我们可以把整个解决方案容器化,与上一篇文章所述的容器化有区别的一点是,对于CalcService的Dockerfile,我们要扩充它的EXPOSE的端口范围,原来是写死的49814,现在让它能够曝露从49800到49899的所有端口,以便新的服务可以通过不同的端口接收请求。此外,还需要在docker-compose.yml中增加另一个Calc服务的配置,详细可以仿照docker-compose.yml中已有的服务配置信息,这里也不多啰嗦了,源代码库中有完整的内容供参考。
完成这些配置之后,可以直接用docker-compose一次性启动所有服务,然后看看我们的API页面,其中的“计算服务名称”会随着页面的刷新动态改变:
总结
本文对前文的案例做了一些简单的调整,实现了基于Ocelot API网关的负载均衡。其实,负载均衡还可以实现在某个微服务的多个实例的层面,然后将这个层面的负载均衡器地址注册到Eureka上,也是可以的。这样的架构能够更好地控制每个服务的伸缩,并对其进行监控。接下来的文章中,我会继续尝试基于微服务的一些部署拓扑,以及在云中如何运行我们的微服务架构。
源代码的使用
请访问https://github.com/daxnet/ocelot-sample/releases/tag/chapter_3下载本文相关案例的源代码。