上一次我们介绍了 Ocelot 网关的基本用法。这次我们开始介绍服务注册发现组件 Consul 的简单使用方法。

服务注册发现

首先先让我们回顾下服务注册发现的概念。

在实施微服务之后,我们的调用都变成了服务间的调用。服务间调用需要知道IP、端口等信息。再没有微服务之前,我们的调用信息一般都是写死在调用方的配置文件里(当然这话不绝对,有些公司会把这些信息写到数据库等公共的地方,以方便维护)。又由于业务的复杂,每个服务可能依赖N个其他服务,如果某个服务的IP,端口等信息发生变更,那么所有依赖该服务的服务的配置文件都要去修改,这样显然太麻烦了。有些服务为了负载是有个多个实例的,而且可能是随时会调整实例的数量。如果每次调整实例数量都要去修改其他服务的配置并重启那太麻烦了。

为了解决这个问题,业界就有了服务注册发现组件。

假设我们有服务A需要调用服务B,并且有服务注册发现组件R。整个大致流程将变成大噶3部:

  1. 服务B启动向服务R注册自己的信息
  2. 服务A从服务R拉取服务B的信息
  3. 服务A调用服务B

有了服务注册发现组件之后,当修改A服务信息的时候再也不用去修改其他相关服务了。

Consul

Consul 是 HashiCorp 公司推出的一套服务注册发现工具。它使用 golang 编写并且开源。由于使用 golang 的缘故所以它天生跨平台而且部署简单。它带有 web 管理后台方便用户查看维护 Consul 集群。其实除了服务注册发现功能,Consul 还支持 Key/Value 存储可以当一个简单的配置中心使用。

架构



上面是 Consul 官网上画的架构图。从图上可以看到 Consul 天生支持多数据中心部署。每个数据中心内部有多个 Consul 节点。Consul 的节点分为2种。

  1. Server

    Server 模式的节点是真正意义上集群的节点。它通过RAFT算法实现CAP里的CA。当Leader Server 挂掉的时候会自动选举出新的 Leader 使集群继续正常工作。
  2. Client

    Client 模式的节点虽然也叫节点,但是它并不会持久化数据,不维持状态,它仅仅是转发客户端的请求给后面的 Server 节点,同时负责注册到该 client 节点的服务的健康检测。它非常轻量级,按照 Consul 的说法最好是每个服务都配一个 client 。

为什么要有client模式的节点

我初看 Consul 这套架构的时候觉得很奇怪,为什么要在 Server 节点跟真正的服务之间插入一层 client 模式的节点。按照按照 Consul 的说法还得每个服务配一个 client 节点。



经过思考说说我的一些看法。在这个模式下服务不在关心真正的集群在哪,集群的节点有哪些,只需要知道这个伴随的 client 节点的地址就行了。通过这个 client 节点去感知到真正可用的 server 节点,所有跟 server 节点的交互全部交给 client 节点代理去完成,这就简化了服务跟 consul 交互的难度。还有一个好处是服务的健康检测由 client 节点负责,在一定程度上减轻了 server 节点的压力。当然这也会带来一个问题,那就是如果 client 挂了,那么服务可能就连不上 Consul 集群了,因为对于服务来说这个 client 节点相当于是单点的。

使用 docker 运行 Consul

docker run -p 8500:8500 --name=consulserver consul agent -server -bootstrap -client=0.0.0.0 -ui -node=0

使用 docker 命令运行初始化一个 consul 的 server 模式的节点。

  • -server 启动为Server模式
  • -bootstrap 设置为启动模式,这是第一个server节点,等待其它节点的加入
  • -client 指定可以访问的客户端IP 。
  • -ui 开启管理界面
  • -node 节点的名字
docker run -d --name=consulserver1 consul agent -server -node=1 -join=172.17.0.2

有了第一个节点,我们可以开始创建更多的 Server 节点来构造集群。Consul 推荐至少3个 Server 来组建集群。上面的 docker 命令表示启动第二个 Server 然后加入第一个节点构造的集群。

  • -join 加入某个集群,这里的 IP 为第一个启动的节点的内网 IP 。可以通过 docker exec XXX consul members 命令查看。后面会演示。
docker run --name=consulclient0 -e consul agent -client=0.0.0.0 -node=client0 -retry-join=172.17.0.2

我们有了 Server 集群,现在可以开始建立 Consul 的 client 节点,然后加入集群。启动 Consul client 的命令跟启动 Consul server 的差不多。去掉了 -server 就代表这个 agent 为 client 模式。

使用 docker-compose 运行 Consul

上面分步骤演示了如何使用 docker 命令来运行 Consul 集群。一行行敲还是太麻烦,为了简化部署,这里整理成了 docker-compose 启动文件。

version: '3.9'
services: consulserver1:
image: consul:1.9.4
restart: always
container_name: consulserver1
hostname: consulserver1
command: agent -server -bootstrap -client=0.0.0.0 -ui -node=consulserver1
ports:
- 8500:8500
consulserver2:
image: consul:1.9.4
restart: always
container_name: consulserver2
hostname: consulserver2
command: agent -server -join=consulserver1 -node=consulserver2
depends_on:
- consulserver1 consulserver3:
image: consul:1.9.4
restart: always
container_name: consulserver3
hostname: consulserver3
command: agent -server -join=consulserver1 -node=consulserver3
depends_on:
- consulserver1 consulclient1:
image: consul:1.9.4
restart: always
container_name: consulclient1
hostname: consulclient1
command: agent -client=0.0.0.0 -retry-join=consulserver1 -node=consulclient1
depends_on:
- consulserver2
- consulserver3
ports:
- 8600:8500
consulclient2:
image: consul:1.9.4
restart: always
container_name: consulclient2
hostname: consulclient2
command: agent -client=0.0.0.0 -retry-join=consulserver1 -node=consulclient2
depends_on:
- consulserver2
- consulserver3
ports:
- 8700:8500
consulclient3:
image: consul:1.9.4
restart: always
container_name: consulclient3
hostname: consulclient3
command: agent -client=0.0.0.0 -retry-join=consulserver1 -node=consulclient3
depends_on:
- consulserver2
- consulserver3
ports:
- 8800:8500

这个 docker-compose 文件描述了启动3个 server 模式的实例,3个 client 模式的实例。其中 consulserver1 开启了ui,端口映射8500,consulclient1,、consulclient2、consulclient3 端口分别映射为 8600、8700、8800 ,记住这些端口,后面要用到。

[root@localhost myservices]# docker-compose up -d
[root@localhost myservices]# docker exec consulserver1 consul members Node Address Status Type Build Protocol DC Segment
consulserver1 172.18.0.2:8301 alive server 1.9.4 2 dc1 <all>
consulserver2 172.18.0.3:8301 alive server 1.9.4 2 dc1 <all>
consulserver3 172.18.0.4:8301 alive server 1.9.4 2 dc1 <all>
consulclient1 172.18.0.5:8301 alive client 1.9.4 2 dc1 <default>
consulclient2 172.18.0.6:8301 alive client 1.9.4 2 dc1 <default>
consulclient3 172.18.0.7:8301 alive client 1.9.4 2 dc1 <default>

使用 docker-compose up -d 命令启动所有的容器。启动完成后使用 docker exec consulserver1 consul members 查看整个集群的状态。它列出了所有节点的类型,IP,是否存活等信息。



如果上面的操作一切正常,在浏览器里输入 http://宿主机IP:8500 访问 web 管理界面。界面上会显示6个绿色的节点。表示所有节点都正常运行中。

在 asp.net core 应用内使用 Consul

好了现在我们已经有了 Consul 集群,现在可以开始编写代码来注册跟拉取我们的服务了。我们需要完成4点操作。

  1. 定义一个健康检测的接口
  2. 在服务启动的时候自动注册该服务的基础信息
  3. 在服务关闭的时候自动移除该服务
  4. 拉取服务列表

健康检测

我们的服务注册到 consul 节点后,节点会定时去轮询我们的服务,所以需要提供一个 http 接口,如果返回 200 ok 就表示服务存活,否则代表服务故障。

    [ApiController]
[Route("[controller]")]
public class HealthController : ControllerBase
{
[HttpGet]
public string Get()
{
return "ok";
}
}
}

添加一个HealthController里面就实现一个Get方法简单的返回ok就可以了。

服务注册、移除

我们实现一个HostedService来实现自动注册跟移除服务。HostedService 有2个方法,start 跟 stop 。start 方法会在 app 启动的时候触发 , stop 会在 app 关闭的时候触发。跟我们的需求完美符合。

Install-Package Consul -Version 1.6.10.1

使用 nuget 安装 consul .net client 类库。我们跟 consul 节点的通讯需要它来完成。

    public class ServiceInfo
{
public string Id { get; set; }
public string Name { get; set; } public string IP { get; set; } public int Port { get; set; } public string HealthCheckAddress { get; set; }
}

定义一个类,存储服务的基本信息。

public class ConsulRegisterService : IHostedService
{
IConsulClient _consulClient;
ServiceInfo _serviceInfo;
public ConsulRegisterService(IConfiguration config, IConsulClient consulClient)
{
_serviceInfo = new ServiceInfo();
var sc = config.GetSection("serviceInfo"); _serviceInfo.Id = sc["id"];
_serviceInfo.Name = sc["name"];
_serviceInfo.IP = sc["ip"];
_serviceInfo.HealthCheckAddress = sc["HealthCheckAddress"];
_serviceInfo.Port = int.Parse(sc["Port"]); _consulClient = consulClient;
} public async Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine($"start to register service {_serviceInfo.Id} to consul client ...");
await _consulClient.Agent.ServiceDeregister(_serviceInfo.Id, cancellationToken);
await _consulClient.Agent.ServiceRegister(new AgentServiceRegistration
{
ID = _serviceInfo.Id,
Name = _serviceInfo.Name,// 服务名
Address = _serviceInfo.IP, // 服务绑定IP
Port = _serviceInfo.Port, // 服务绑定端口
Check = new AgentServiceCheck()
{
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(0),//服务启动多久后注册
Interval = TimeSpan.FromSeconds(5),//健康检查时间间隔
HTTP = $"http://{_serviceInfo.IP}:{_serviceInfo.Port}/" + _serviceInfo.HealthCheckAddress,//健康检查地址
Timeout = TimeSpan.FromSeconds(5)
}
});
Console.WriteLine("register service info to consul client Successful ...");
} public async Task StopAsync(CancellationToken cancellationToken)
{
await _consulClient.Agent.ServiceDeregister(_serviceInfo.Id, cancellationToken);
Console.WriteLine($"Deregister service {_serviceInfo.Id} from consul client Successful ...");
}
}

定义一个 ConsulRegisterService 类,实现 IHostedService 接口。在 start 方法内使用 consulclient 注册服务 。在 stop 方法内取消注册该服务。

        public void ConfigureServices(IServiceCollection services)
{
//注册Consulclient对象
services.AddSingleton<IConsulClient>(new ConsulClient(x => {
x.Address = new Uri(Configuration["consul:clientAddress"]);
}));
//注册ConsulRegisterService 这个servcie在app启动的时候会自动注册服务信息
services.AddHostedService<ConsulRegisterService>();
services.AddControllers();
}

在 startup 的 ConfigureServices 方法内先注入一个 IConsulClient 的实例。再注册我们的 ConsulRegisterService 服务。

  "serviceInfo": {
"id": "hote_base_01", //服务id
"name": "hote_base", //服务名
"ip": "192.168.0.200", //服务部署的ip
"port": 6002, //服务对应的端口
"healthCheckAddress": "health" //健康检测的请求path
},
"consul": {
"clientAddress": "http://192.168.0.117:8700" //consul client 的地址
}

以我们的演示项目 hotel_base 为例,在 appsettings.json 文件内添加以上配置信息。其中 consul:clientAddress 为 consule client 节点的地址。

注意:这里的 ip 不要使用 localhost ,因为如果使用 docker 部署 , localhost 会出现网络访问方面的问题。



好了,让我们运行一下我们的项目。等待项目启动完成后,打开 consul 的 web 管理界面。查看 consulclient1 节点,可以看到我们的 hotel_base_01 服务被注册上去了。



我们强制把启动的app关闭,可以看到 consul 管理界面显示 hotel_base 服务红色,代表故障。

注意:要演示故障这种情况,要先注释掉 ConsulRegisterService 的 stop 方法,不然关闭的时候会先取消注册,这样 consul 管理界面上就找不到对应的服务了。



我们按照 hotel_base 的套路,把其他几个服务都添加服务注册的代码。然后全部运行起来

拉取服务列表

下面我们演示下如何通过 consul client 读取服务列表。

   public interface IConsulService
{
Task<List<AgentService>> GetServicesAsync(string serviceName);
}
public class ConsulService : IConsulService
{
public IConsulClient _consulClient;
public ConsulService(IConsulClient consulClient)
{
_consulClient = consulClient;
} public async Task<List<AgentService>> GetServicesAsync(string serviceName)
{
var result = await _consulClient.Health.Service(serviceName, "", true);
return result.Response.Select(x => x.Service).ToList();
}
}

定义一个ConsulService类,里面有个GetServicesAsync方法。该方法通过服务名称从 consul 集群获取服务的列表。

        public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConsulClient>(new ConsulClient(x => {
x.Address = new Uri(Configuration["consul:clientAddress"]);
}));
//注册ConsulService里面封装了一些方法
services.AddSingleton<IConsulService, ConsulService>();
services.AddHostedService<ConsulRegisterService>();
services.AddControllers();
}

在 ConfigureServices 方法内把 ConsulService 注册到容器内。

 [ApiController]
[Route("[controller]")]
public class OrderController : ControllerBase
{
private static readonly List<OrderVM> _orders = new List<OrderVM>() {
new OrderVM {
Id = "OD001",
StartDay = "2021-05-01",
EndDay = "2021-05-02",
RoomNo = "1001",
MemberId = "M001",
HotelId = "H8001",
CreateDay = "2021-05-01"
}
}; private IConsulService _consulservice;
public OrderController(ILogger<OrderController> logger, IConsulService consulService)
{
_consulservice = consulService;
} [HttpGet("{id}")]
public async Task<OrderVM> Get(string id)
{
var order = _orders.FirstOrDefault(x=>x.Id == id);
if (!string.IsNullOrEmpty(order.MemberId))
{
var memberServiceAddresses = await _consulservice.GetServicesAsync("member_center");
var memberServiceAddress = memberServiceAddresses.FirstOrDefault();
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri($"http://{memberServiceAddress.Address}:{memberServiceAddress.Port}");
var memberResult = await httpClient.GetAsync("/member/" + order.MemberId);
var json = await memberResult.Content.ReadAsStringAsync();
var member = JsonConvert.DeserializeObject<MemberVM>(json);
order.Member = member;
}
} return order;
}
}

我们通过在 ordering 服务项目的一个获取订单详细信息的接口来演示下如何使用ConsulService 。订单详细信息需要根据会员id获取会员的详细信息。我们通过 ConsulService 获得 member_center 的服务列表后,取出一个配置信息,获取 IP 跟端口号。组装成服务的真正的请求地址,使用 HttpClient 来请求这个接口,获取会员的基本信息。

当然这里我们有很多可以改进的地方,比如我们可以在本地缓存服务列表,这样不用每次都通过 consul client 拉取。比如我们可以写一个随机算法,每次从服务列表中随机取一个对象,从而达到负载均衡的目的,在这就不再演示了。



把所有项目都跑起来,使用 postman 去访问一下获取订单详情接口,可以看到订单详情的返回值包含了会员信息。

总结

通过以上,我们回顾了服务注册发现的概念。演示了如何通过 docker/docker-compose 环境来部署 Consul 集群。还通过简单的 .NET Core 代码演示了如何注册服务信息到 Consul 集群,如何通过代码获取服务列表并调用它。相信现在大家对服务注册发现、Consul 组件有了一个比较直观的了解。

谢谢阅读。

相关文章

NET Core with 微服务 - 什么是微服务

.Net Core with 微服务 - 架构图

.Net Core with 微服务 - Ocelot 网关

关注我的公众号一起玩转技术

.Net Core with 微服务 - Consul 注册中心的更多相关文章

  1. .Net Core with 微服务 - Consul 配置中心

    上一次我们介绍了Elastic APM组件.这一次我们继续介绍微服务相关组件配置中心的使用方法.本来打算介绍下携程开源的重型配置中心框架 apollo 但是体系实在是太过于庞大,还是让我爱不起来.因为 ...

  2. 小D课堂 - 新版本微服务springcloud+Docker教程_3-01 什么是微服务的注册中心

    笔记 第三章 SpringCloud核心组件注册中心 1.什么是微服务的注册中心     简介:讲解什么是注册中心,常用的注册中心有哪些 (画图)                  理解注册中心:服务 ...

  3. 【微服务】之二:从零开始,轻松搞定SpringCloud微服务系列--注册中心(一)

    微服务体系,有效解决项目庞大.互相依赖的问题.目前SpringCloud体系有强大的一整套针对微服务的解决方案.本文中,重点对微服务体系中的服务发现注册中心进行详细说明.本篇中的注册中心,采用Netf ...

  4. 第二个视频作品《[SpringCloudAlibaba]微服务之注册中心nacos》上线了

    1.场景描述 第二个视频作品出炉了,<[SpringCloudAlibaba]微服务之注册中心nacos>上线了,有需要的朋友可以直接点击链接观看.(如需购买,请通过本文链接购买) 2. ...

  5. 微服务---Eureka注册中心(服务治理)

    在上一篇的初识SpringCloud微服务中,我们简单讲解到服务的提供者与消费者,当服务多了之后,会存在依赖与管理之间混乱的问题,以及需要对外暴露自己的地址,为了解决此等问题,我们学习Eureka注册 ...

  6. 微服务 - Eureka注册中心

    我们来解决微服务的第一问题,服务的管理. 服务中心对外提供服务,需要对外暴露自己的地址.而consumer(调用者)需要记录服务提供者的地址.将来地址出现变更,还需要及时更新.这在服务较少的时候并不觉 ...

  7. 微服务组件--注册中心Spring Cloud Eureka分析

    Eureka核心功能点 [1]服务注册(register):Eureka Client会通过发送REST请求的方式向Eureka Server注册自己的服务,提供自身的元数据,比如ip地址.端口.运行 ...

  8. .Net Core with 微服务 - Seq 日志聚合

    上一次我们介绍并演示了如果使用 Consul 做为我们微服务的注册中心,来实现服务的注册与发现.那么本次我们讲会演示如何做日志聚合.日志聚合比较常用的有 ELK 等,但是这次我想要介绍的是一款比较小众 ...

  9. .Net Core with 微服务 - Elastic APM

    上一次我们介绍了Seq日志聚合组件.这次要给大家介绍的是Elastic APM ,一款应用程序性能监控组件.APM 监控围绕对应用.服务.容器的健康监控,对接口的调用链.性能进行监控.在我们实施微服务 ...

随机推荐

  1. python-内置函数-compile,eval,exec

    #将字符串,编译成python代码 compile()#执行,有返回值,执行表达式并获取结果 eval()#执行python代码,无返回值,接收:代码或者字符串 exec() s = "pr ...

  2. 对标印度的PostMan,一款中国接口测试软件的崛起

    对于我们开发者,Api接口调试一定不陌生.包括我在内,之前进行Api调试时,一直使用的是一款印度的软件Postman.记得刚入手的时候,由于该款软件缺乏中文版本,上手一直比较慢,而且还至少存在如下几个 ...

  3. keep-alive与生命周期函数

    理解keep-alive keep-alive是Vue内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染 router-view也是一个组件,如果直接被keep-alive包在里面,所有路径匹 ...

  4. Postman(接口自动化测试)

    1.Postman 接口测试参数化可能大家都非常的熟悉,但是很多人很难处理参数化后如何断言的问题,特别是当参数中出现中文时,很容易导致在 Runner 页面引入外部文件时导致中文乱码的问题,今天这篇文 ...

  5. synchronized运行原理以及优化

    线程安全问题 线程不安全: 当多线程并发访问临界资源时(可共享的对象),如果破坏原子操作,可能会造成数据不一致. 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可以保证其正确性. 原子操作 ...

  6. 大数据开发-Flink-1.13新特性

    介绍 大概4月,Flink1.13就发布了,参加 了Flink1.13 的Meetup,收获还是挺多,从大的方面讲就是FlingSql的改进和优化,资源调度管理方面的优化,以及流批一体Flink在运行 ...

  7. cmake和make

    学计算机的,在写代码的时候,IDE安装好,环境按着教程配置好,就直接代码了,编辑器的具体原理只是一知半解,现在来系统学习一下,为了方便以后学习HElib! make和cmake 写程序大体步骤为: 1 ...

  8. [bug] CDH 安装 Error : No matching Packages to list

    信息 分析 我的系统是CentOS 7,而 cm 安装包是配合 redhat 6 的,应该选择 redhat 7 目录下的包 参考 https://community.cloudera.com/t5/ ...

  9. [Linux] Linux命令行与Shell脚本编程大全 Part.1

    终端 tty(teletypewriters):控制台,早期计算机通过电传打字机作为输入设备 Console:控制台终端,即显示器 Ctrl+Alt+T:图形界面终端 Ctrl+Alt+F2:tty2 ...

  10. IT菜鸟之虚拟机VMware的安装

    老师说过,如果想学好Linux,最好不要在实体机上安装Linux,因为学习需要经常折腾,在实体机上做实验,出现故障就要重新安装,这样绝大多数时间都会浪费在安装上. 这时我们需要一个工具,它就是虚拟机. ...