【.NET Core微服务架构】-- 注册中心Consul

1,097 阅读5分钟

序言

[.NET Core微服务架构] -- Ocelot网关路由、缓存、限流、熔断,Polly策略 - 掘金 (juejin.cn)
上篇文章我们讲了网关的一些功能和策略,但是有没有发现一点问题呢?我们配置的下游是不是都是直接写在配置里的,如果同一个服务开启或关闭多个进程的时候,我们怎么去动态的扩展呢?那就是我们今天要说到的服务注册与发现,现在市面上也有比较好的中间件去做这个事情,我们前面用的网关的Oclot也是支持扩展这些中间件。Eureka、Consul和Nacos等等。本篇文章选择Consul来在.NET Core中实现。

产品比较

原文地址:eureka、nacos、consul的区别

配置中心

配置中心
eureka不支持
nacos支持 用起来简单,支持动态刷新
consul支持 但用起来偏麻烦,支持动态刷新

注册中心

eurekanacosconsul
应用内/外直接集成到应用中,依赖于应用自身完成服务的注册与发现属于外部应用,侵入性小属于外部应用,侵入性小
ACP原则遵循AP(可用性+分离容忍)原则,有较强的可用性,服务注册快,但牺牲了一定的一致性通知遵循CP原则(一致性+分离容忍) 和AP原则(可用性+分离容忍)遵循CP原则(一致性+分离容忍) 服务注册稍慢,由于其一致性导致了在Leader挂掉时重新选举期间真个consul不可用
版本迭代目前已经不进行升级目前仍然进行版本迭代目前仍然进行版本迭代
访问协议HTTPHTTP/动态DNS/UDPHTTP/DNS
雪崩保护支持雪崩保护支持雪崩保护不支持雪崩保护
界面英文界面中文界面英文界面
上手容易极易,中文文档复杂一点

整体工作流程

我自己画了一个简单的工作流程图,比较乱,大概能看懂什么意思就行,首先我们服务启动的时候会把自身的信息传给注册中心,包括IP端口之类的,注册中心会定时去检查服务是否运行正常,如果异常的会在服务列表中删掉。然后当前端或者外部发起请求时,先经过网关,网关会调用注册中心获取到符合当前策略的地址,然后去调用服务,拿到返回结果后再返回。

image.png

Consul

简介

Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其他分布式服务注册与发现的方案,Consul 的方案更“一站式”,内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其他工具(比如 ZooKeeper 等)。使用起来也较 为简单。Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。

安装

下载Consul
官网下载地址: www.consul.io/downloads.h… 打开cmd控制台,输入consul -v查看Consul版本,确认Consul是否正常。

image.png

命令consul agent -dev启动,看到Consul agent running! 启动成功

image.png

启动成功后访问http://localhost:8500就能进入Consul的管理控制台

image.png

使用

在服务中添加服务注册代码,为了代码的复用,我们单独新建个类库管理所有的注入。
添加依赖包

<ItemGroup>
	<PackageReference Include="Consul" Version="1.6.10.4" />
	<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
	<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
	<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
</ItemGroup>

新建Consul注册类

using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using System.Net;

namespace Extensions
{
    public static class ConsulRegister
    {
        /// <summary>
        /// 服务注册到consul
        /// </summary>
        /// <param name="app"></param>
        /// <param name="lifetime"></param>
        public static IApplicationBuilder RegisterConsul(this IApplicationBuilder app, IConfiguration configuration, IHostApplicationLifetime lifetime, int Port = 0)
        {
            var consulClient = new ConsulClient(c =>
            {
                //consul地址
                c.Address = new Uri(configuration["ConsulSetting:ConsulAddress"]);
            });
            string ServiceName = configuration["ConsulSetting:ServiceName"];
            string ServiceIP = configuration["ConsulSetting:ServiceIP"];
            int ServicePort = Port != 0 ? Port : int.Parse(configuration["ConsulSetting:ServicePort"]);
            var registration = new AgentServiceRegistration()
            {
                ID = Guid.NewGuid().ToString(),//服务实例唯一标识
                Name = ServiceName,//服务名
                Address = ServiceIP, //服务IP
                Port = ServicePort,//服务端口 因为要运行多个实例,端口不能在appsettings.json里配置,在docker容器运行时传入
                Check = new AgentServiceCheck()
                {
                    DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服务启动多久后注册
                    Interval = TimeSpan.FromSeconds(10),//健康检查时间间隔
                    HTTP = $"http://{ServiceIP}:{ServicePort}/Health",//健康检查地址
                    Timeout = TimeSpan.FromSeconds(5)//超时时间
                }
            };
            //健康检查,不需要新建Controller
            app.MapWhen(context => context.Request.Path.Equals("/Health"), applicationBuilder => applicationBuilder.Run(async context =>
            {
                context.Response.StatusCode = (int)HttpStatusCode.OK;
                await context.Response.WriteAsync("OK");
            }));
            //服务注册
            consulClient.Agent.ServiceRegister(registration).Wait();

            //应用程序终止时,取消注册
            lifetime.ApplicationStopping.Register(() =>
            {
                consulClient.Agent.ServiceDeregister(registration.ID).Wait();
            });
            Console.WriteLine($"{ServiceName}服务注册成功");
            return app;
        }
    }
}

更改服务配置文件appsettings.json

  "ConsulSetting": {
    "ServiceName": "UserService",
    "ServiceIP": "localhost",
    "ConsulAddress": "http://localhost:8500" //注意,docker容器内部无法使用localhost访问宿主机器,如果是控制台启动的话就用localhost
  },

在管道中调用服务注册的方法

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime lifetime)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "UserApis v1"));
            }

            app.UseRouting();

            app.UseAuthorization();

            //注册Consul服务
            app.RegisterConsul(Configuration, lifetime);
            //方便调试传输固定端口
            //app.RegisterConsul(Configuration, lifetime, 8060);
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

我们启动下看服务能不能注册成功

dotnet run --urls="http://localhost:8001" --ConsulSetting:ServicePort="8001"
dotnet run --urls="http://localhost:8002" --ConsulSetting:ServicePort="8002"

image.png image.png

image.png 可以看到Consul的管理节目能够看到注册了两个UserService,端口分别是8001和8002

Consul是注册成功了,那如何搭配网关一起使用呢?

搭配网关一起使用

在网关项目中引入Consul依赖包

<PackageReference Include="Ocelot.Provider.Consul" Version="18.0.0" />

修改Ocelot配置

{
  "Routes": [
    {
      "UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ],
      "UpstreamPathTemplate": "/api/User/{everything}",
      "ServiceName": "UserService", //consul 服务中 Service的名称
      "DownstreamPathTemplate": "/User/{everything}",
      "DownstreamScheme": "http",
      "LoadBalancerOptions": {
        "Type": "RoundRobin" //RoundRobin轮询 最少连接数的服务器LastConnection 不负载均衡NoLoadBalance
      }
    }
  ],
  "GlobalConfiguration": {
    "ServiceDiscoveryProvider": { // Consul 服务发现配置
      "Host": "localhost", // Consul 地址
      "Port": 8500,
      "Type": "Consul" //Nacos  Consul
    }
  }
}

容器中添加Consul

services.AddOcelot().AddConsul();

测试下看看效果

image.png
可以跟我们原来配置的效果是一样的

Nacos

Nacos官网:home (nacos.io)

由于Nacos是阿里巴巴开源出来的,所以没有官方支持.NET的库,我们只能选择第三方的库来实现。感兴趣的可以去了解一下。有时间会单独写篇文章介绍下.NET Core如何使用Nacos。

Ocelot集成Nacos:Ocelot.Provider.Nacos: Repo for Nacos integration with Ocelot (github.com)

.NET支持Nacos的SDK:nacos-sdk-csharp: This nacos csharp sdk (github.com)