.NET Core + Consul 服务注册与发现

Consul 介绍

在分布式架构中,服务治理是必须面对的问题,如果缺乏简单有效治理方案,各服务之间只能通过人肉配置的方式进行服务关系管理,当遇到服务关系变化时,就会变得极其麻烦且容易出错。

Consul 是一个用来实现分布式系统服务发现与配置的开源工具。它内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案,不再需要依赖其他工具(比如 ZooKeeper 等),使用起来也较为简单。

Consul 架构

Consul 架构图

Consul 集群支持多数据中心,在上图中有两个 DataCenter,他们通过 Internet 互联,为了提高通信效率,只有 Server 节点才加入跨数据中心的通信。在单个数据中心中,Consul 分为 Client 和 Server 两种节点(所有的节点也被称为 Agent),Server 节点保存数据,Client 负责健康检查及转发数据请求到 Server,本身不保存注册信息;Server 节点有一个 Leader 和多个 Follower,Leader 节点会将数据同步到 Follower,Server 节点的数量推荐是3个或者5个,在 Leader 挂掉的时候会启动选举机制产生一个新 Leader。

Consul 集群搭建

这里使用 Docker 搭建 3个 Server 节点 + 1 个 Client 节点,API 服务通过 Client 节点进行服务注册和发现。

从 Docker Hub 拉取 Consul 镜像

1
docker pull consul

启动 3个 Server 节点 + 1 个 Client 节点

1
2
3
4
5
6
7
8
9
10
11
// Server  节点 1
docker run --name cs1 -p 8500:8500 -v /data/cs1:/data consul agent -server -bind 172.17.0.2 -node consul-server-1 -data-dir /data -bootstrap-expect 3 -client 0.0.0.0 -ui

// Server 节点 2
docker run --name cs2 -p 7500:8500 -v /data/cs2:/data consul agent -server -bind 172.17.0.3 -node consul-server-2 -data-dir /data -bootstrap-expect 3 -client 0.0.0.0 -ui -join 172.17.0.2

// Server 节点 3
docker run --name cs3 -p 6500:8500 -v /data/cs3:/data consul agent -server -bind 172.17.0.4 -node consul-server-3 -data-dir /data -bootstrap-expect 3 -client 0.0.0.0 -ui -join 172.17.0.2

// Client 节点 1
docker run --name cc1 -p 5500:8500 -v /data/cc1:/data consul agent -bind 172.17.0.5 -node consul-client-1 -data-dir /data -client 0.0.0.0 -ui -join 172.17.0.2

参数说明(–name、-p、-v 为 Docker 相关参数):

参数名 解释
–name Docker 容器名称(每个 Consul 节点一个容器)
-p 容器内部 8500 端口映射到当前主机端口,因为使用的同一台主机,所以这里每个容器内的 8500 端口映射到当前主机的不同端口
-v 将节点相关注册数据挂载到当前主机的指定位置,否则重启后会丢失
-server 设置为 Server 类型节点,不加则为 Client 类型节点
-bind 指定节点绑定的地址
-node 指定节点名称
-data-dir 数据存放位置
-bootstrap-expect 集群期望的 Server 节点数,只有达到这个值才会选举 Leader
-client 注册或者查询等一系列客户端对它操作的IP,默认是127.0.0.1
-ui 启用 UI 界面
-join 指定要加入的节点地址(组建集群)

集群状态

cs1 为容器名称,任意一个即可

查看节点状态和类型
1
docker exec -t cs1 consul members

consul members

当前为3 个 Server 类型节点 ,1 个 Client 类型节点。

查看 Server 节点类型
1
docker exec -t cs1 consul operator raft list-peers

consul raft

当前为 consul-server-1 为 leader,可以测试将 consul-server-1 kill 观察 leader 的重新选举。

通过 http://192.168.124.8:8500 UI 界面查看 Consul 节点状态如下:
:7500、:6500、:5500 均可,192.168.124.8 是当前主机网络的 IPv4 地址
consul ui

.NET Core 接入 Consul

service discovery

  1. 创建 .NET Core WebAPI 服务 ServiceA(2个实例) 和 ServiceB

  2. Nuget 安装 Consul

  3. 注册到 Consul 的核心代码如下(源码下载
    )):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    public static class ConsulBuilderExtensions
    {
    public static IApplicationBuilder RegisterConsul(this IApplicationBuilder app, IApplicationLifetime lifetime, ConsulOption consulOption)
    {
    var consulClient = new ConsulClient(x =>
    {
    // consul 服务地址
    x.Address = new Uri(consulOption.Address);
    });

    var registration = new AgentServiceRegistration()
    {
    ID = Guid.NewGuid().ToString(),
    Name = consulOption.ServiceName,// 服务名
    Address = consulOption.ServiceIP, // 服务绑定IP
    Port = consulOption.ServicePort, // 服务绑定端口
    Check = new AgentServiceCheck()
    {
    DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服务启动多久后注册
    Interval = TimeSpan.FromSeconds(10),//健康检查时间间隔
    HTTP = consulOption.ServiceHealthCheck,//健康检查地址
    Timeout = TimeSpan.FromSeconds(5)
    }
    };

    // 服务注册
    consulClient.Agent.ServiceRegister(registration).Wait();

    // 应用程序终止时,服务取消注册
    lifetime.ApplicationStopping.Register(() =>
    {
    consulClient.Agent.ServiceDeregister(registration.ID).Wait();
    });
    return app;
    }
    }
  4. 添加配置如下:

    1
    2
    3
    4
    5
    "ServiceName": "ServiceA",
    "ServiceIP": "192.168.124.8",
    "ServicePort": 8000,
    "ServiceHealthCheck": "http://192.168.124.8:8000/healthCheck",
    "ConsulAddress": "http://192.168.124.8:8500"
  5. 注册成功结果如下:

    service register

  6. ServiceB 调用 ServiceA 接口

    ServiceB 通过 ConsulClient 进行服务发现,获取到 ServiceA 的地址,然后随机任意一台进行请求,核心代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var url = _configuration["ConsulAddress"].ToString();

    using (var consulClient = new ConsulClient(a => a.Address = new Uri(url)))
    {
    var services = consulClient.Catalog.Service("ServiceA").Result.Response;
    if (services != null && services.Any())
    {
    // 模拟随机一台进行请求,这里只是测试,可以选择合适的负载均衡工具或框架
    Random r = new Random();
    int index = r.Next(services.Count());
    var service = services.ElementAt(index);

    using (HttpClient client = new HttpClient())
    {
    var response = await client.GetAsync($"http://{service.ServiceAddress}:{service.ServicePort}/values/test");
    var result = await response.Content.ReadAsStringAsync();
    return result;
    }
    }
    }
  7. 多次调用 ServiceB 接口结果如下:

result-1

result-2

参考链接

如果对你有帮助就好