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

  • Consul官网:https://www.consul.io

  • 开源地址:https://github.com/hashicorp/consul、https://github.com/G-Research/consuldotnet

安装

Consul支持各种平台的安装,安装文档:https://www.consul.io/downloads,为了快速使用,我这里选择用docker方式安装。

version: "3"services:service_1:image: consulcommand: agent -server -client=0.0.0.0 -bootstrap-expect=3 -node=service_1volumes:- /usr/local/docker/consul/data/service_1:/dataservice_2:image: consulcommand: agent -server -client=0.0.0.0 -retry-join=service_1 -node=service_2volumes:- /usr/local/docker/consul/data/service_2:/datadepends_on:- service_1service_3:image: consulcommand: agent -server -client=0.0.0.0 -retry-join=service_1 -node=service_3volumes:- /usr/local/docker/consul/data/service_3:/datadepends_on:- service_1client_1:image: consulcommand: agent -client=0.0.0.0 -retry-join=service_1 -ui -node=client_1ports:- 8500:8500volumes:- /usr/local/docker/consul/data/client_1:/datadepends_on:- service_2- service_3

提供一个docker-compose.yaml,使用docker-compose up编排脚本启动Consul,如果你不熟悉,可以选择其它方式能运行Consul即可。

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

安装完成启动Consul,打开默认地址 http://localhost:8500 可以看到Consului界面。

快速使用

添加两个webapi服务,ServiceA和ServiceB,一个webapi客户端Client来调用服务。

dotnet new sln -n consul_demodotnet new webapi -n ServiceA
dotnet sln add ServiceA/ServiceA.csprojdotnet new webapi -n ServiceB
dotnet sln add ServiceB/ServiceB.csprojdotnet new webapi -n Client
dotnet sln add Client/Client.csproj

在项目中添加Consul组件包

Install-Package Consul

服务注册

接下来在两个服务中添加必要的代码来实现将服务注册到Consul中。

首先将Consul配置信息添加到appsettings.json

{"Consul": {"Address": "http://host.docker.internal:8500","HealthCheck": "/healthcheck","Name": "ServiceA","Ip": "host.docker.internal"}
}

因为我们要将项目都运行在docker中,所以这里的地址要用 host.docker.internal 代替,使用 localhost 无法正常启动,如果不在 docker 中运行,这里就配置层 localhost。

添加一个扩展方法UseConul(this IApplicationBuilder app, IConfiguration configuration, IHostApplicationLifetime lifetime)

using System;
using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;namespace ServiceA
{public static class Extensions{public static IApplicationBuilder UseConul(this IApplicationBuilder app, IConfiguration configuration, IHostApplicationLifetime lifetime){var client = new ConsulClient(options =>{options.Address = new Uri(configuration["Consul:Address"]); // Consul客户端地址});var registration = new AgentServiceRegistration{ID = Guid.NewGuid().ToString(), // 唯一IdName = configuration["Consul:Name"], // 服务名Address = configuration["Consul:Ip"], // 服务绑定IPPort = Convert.ToInt32(configuration["Consul:Port"]), // 服务绑定端口Check = new AgentServiceCheck{DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5), // 服务启动多久后注册Interval = TimeSpan.FromSeconds(10), // 健康检查时间间隔HTTP = $"http://{configuration["Consul:Ip"]}:{configuration["Consul:Port"]}{configuration["Consul:HealthCheck"]}", // 健康检查地址Timeout = TimeSpan.FromSeconds(5) // 超时时间}};// 注册服务client.Agent.ServiceRegister(registration).Wait();// 应用程序终止时,取消服务注册lifetime.ApplicationStopping.Register(() =>{client.Agent.ServiceDeregister(registration.ID).Wait();});return app;}}
}

然后在Startup.cs中使用扩展方法即可。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime lifetime)
{...app.UseConul(Configuration, lifetime);
}

注意,这里将IConfigurationIHostApplicationLifetime作为参数传进来的,根据实际开发做对应的修改就可以了。

分别在ServiceA和ServiceB都完成一遍上述操作,因为不是实际项目,这里就产生的许多重复代码,在真正的项目开发过程中可以考虑放在一个单独的项目中,ServiceA和ServiceB分别引用,调用。

接着去实现健康检查接口。

// ServiceA
using Microsoft.AspNetCore.Mvc;namespace ServiceA.Controllers
{[Route("[controller]")][ApiController]public class HealthCheckController : ControllerBase{/// <summary>/// 健康检查/// </summary>/// <returns></returns>[HttpGet]public IActionResult api(){return Ok();}}
}
// ServiceB
using Microsoft.AspNetCore.Mvc;namespace ServiceB.Controllers
{[Route("[controller]")][ApiController]public class HealthCheckController : ControllerBase{/// <summary>/// 健康检查/// </summary>/// <returns></returns>[HttpGet]public IActionResult Get(){return Ok();}}
}

最后在ServiceA和ServiceB中都添加一个接口。

// ServiceA
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;namespace ServiceA.Controllers
{[Route("api/[controller]")][ApiController]public class ServiceAController : ControllerBase{[HttpGet]public IActionResult Get([FromServices] IConfiguration configuration){var result = new{msg = $"我是{nameof(ServiceA)},当前时间:{DateTime.Now:G}",ip = Request.HttpContext.Connection.LocalIpAddress.ToString(),port = configuration["Consul:Port"]};return Ok(result);}}
}
// ServiceB
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;namespace ServiceB.Controllers
{[Route("api/[controller]")][ApiController]public class ServiceBController : ControllerBase{[HttpGet]public IActionResult Get([FromServices] IConfiguration configuration){var result = new{msg = $"我是{nameof(ServiceB)},当前时间:{DateTime.Now:G}",ip = Request.HttpContext.Connection.LocalIpAddress.ToString(),port = configuration["Consul:Port"]};return Ok(result);}}
}

这样我们写了两个服务,ServiceA和ServiceB。都添加了健康检查接口和一个自己的服务接口,返回一段json。

我们现在来运行看看效果,可以使用任何方式,只要能启动即可,我这里选择在docker中运行,直接在 Visual Studio中对着两个解决方案右键添加,选择Docker支持,默认会帮我们自动创建好Dockfile,非常方便。

生成的Dockfile文件内容如下:

# ServiceA
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["ServiceA/ServiceA.csproj", "ServiceA/"]
RUN dotnet restore "ServiceA/ServiceA.csproj"
COPY . .
WORKDIR "/src/ServiceA"
RUN dotnet build "ServiceA.csproj" -c Release -o /app/buildFROM build AS publish
RUN dotnet publish "ServiceA.csproj" -c Release -o /app/publishFROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ServiceA.dll"]
# ServiceB
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["ServiceB/ServiceB.csproj", "ServiceB/"]
RUN dotnet restore "ServiceB/ServiceB.csproj"
COPY . .
WORKDIR "/src/ServiceB"
RUN dotnet build "ServiceB.csproj" -c Release -o /app/buildFROM build AS publish
RUN dotnet publish "ServiceB.csproj" -c Release -o /app/publishFROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ServiceB.dll"]

然后定位到项目根目录,使用命令去编译两个镜像,service_a和service_b

docker build -t service_a:dev -f ./ServiceA/Dockerfile .docker build -t service_b:dev -f ./ServiceB/Dockerfile .

看到 Successfully 就成功了,通过docker image ls可以看到我们打包的两个镜像。

这里顺便提一句,已经可以看到我们编译的镜像,service_a和service_b了,但是还有许多名称为<none>的镜像,这些镜像可以不用管它,这种叫做虚悬镜像,既没有仓库名,也没有标签。是因为docker build导致的这种现象。由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为 <none> 的镜像。

一般来说,虚悬镜像已经失去了存在的价值,是可以随意删除的,可以docker image prune命令删除,这样镜像列表就干净多了。

最后将两个镜像service_a和service_b,分别运行三个实例。

docker run -d -p 5050:80 --name service_a1 service_a:dev --Consul:Port="5050"
docker run -d -p 5051:80 --name service_a2 service_a:dev --Consul:Port="5051"
docker run -d -p 5052:80 --name service_a3 service_a:dev --Consul:Port="5052"docker run -d -p 5060:80 --name service_b1 service_b:dev --Consul:Port="5060"
docker run -d -p 5061:80 --name service_b2 service_b:dev --Consul:Port="5061"
docker run -d -p 5062:80 --name service_b3 service_b:dev --Consul:Port="5062"

运行成功,接下来就是见证奇迹的时刻,去到Consul看看。

成功将两个服务注册到Consul,并且每个服务都有多个实例。

访问一下接口试试吧,看看能不能成功出现结果。

因为终端编码问题,导致显示乱码,这个不影响,ok,至此服务注册大功告成。

服务发现

搞定了服务注册,接下来演示一下如何服务发现,在Client项目中先将Consul地址配置到appsettings.json中。

{"Consul": {"Address": "http://host.docker.internal:8500"}
}

然后添加一个接口,IService.cs,添加三个方法,分别获取两个服务的返回结果以及初始化服务的方法。

using System.Threading.Tasks;namespace Client
{public interface IService{/// <summary>/// 获取 ServiceA 返回数据/// </summary>/// <returns></returns>Task<string> GetServiceA();/// <summary>/// 获取 ServiceB 返回数据/// </summary>/// <returns></returns>Task<string> GetServiceB();/// <summary>/// 初始化服务/// </summary>void InitServices();}
}

实现类:Service.cs

using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Consul;
using Microsoft.Extensions.Configuration;namespace Client
{public class Service : IService{private readonly IConfiguration _configuration;private readonly ConsulClient _consulClient;private ConcurrentBag<string> _serviceAUrls;private ConcurrentBag<string> _serviceBUrls;private IHttpClientFactory _httpClient;public Service(IConfiguration configuration, IHttpClientFactory httpClient){_configuration = configuration;_consulClient = new ConsulClient(options =>{options.Address = new Uri(_configuration["Consul:Address"]);});_httpClient = httpClient;}public async Task<string> GetServiceA(){if (_serviceAUrls == null)return await Task.FromResult("ServiceA正在初始化...");using var httpClient = _httpClient.CreateClient();var serviceUrl = _serviceAUrls.ElementAt(new Random().Next(_serviceAUrls.Count()));Console.WriteLine("ServiceA:" + serviceUrl);var result = await httpClient.GetStringAsync($"{serviceUrl}/api/servicea");return result;}public async Task<string> GetServiceB(){if (_serviceBUrls == null)return await Task.FromResult("ServiceB正在初始化...");using var httpClient = _httpClient.CreateClient();var serviceUrl = _serviceBUrls.ElementAt(new Random().Next(_serviceBUrls.Count()));Console.WriteLine("ServiceB:" + serviceUrl);var result = await httpClient.GetStringAsync($"{serviceUrl}/api/serviceb");return result;}public void InitServices(){var serviceNames = new string[] { "ServiceA", "ServiceB" };foreach (var item in serviceNames){Task.Run(async () =>{var queryOptions = new QueryOptions{WaitTime = TimeSpan.FromMinutes(5)};while (true){await InitServicesAsync(queryOptions, item);}});}async Task InitServicesAsync(QueryOptions queryOptions, string serviceName){var result = await _consulClient.Health.Service(serviceName, null, true, queryOptions);if (queryOptions.WaitIndex != result.LastIndex){queryOptions.WaitIndex = result.LastIndex;var services = result.Response.Select(x => $"http://{x.Service.Address}:{x.Service.Port}");if (serviceName == "ServiceA"){_serviceAUrls = new ConcurrentBag<string>(services);}else if (serviceName == "ServiceB"){_serviceBUrls = new ConcurrentBag<string>(services);}}}}}
}

代码就不解释了,相信都可以看懂,使用了Random类随机获取一个服务,关于这点可以选择更合适的负载均衡方式。

Startup.cs中添加接口依赖注入、使用初始化服务等代码。

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;namespace Client
{public class Startup{public Startup(IConfiguration configuration){Configuration = configuration;}public IConfiguration Configuration { get; }public void ConfigureServices(IServiceCollection services){services.AddControllers();services.AddHttpClient();services.AddSingleton<IService, Service>();}public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IService service){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseHttpsRedirection();app.UseRouting();app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapControllers();});service.InitServices();}}
}

一切就绪,添加api访问我们的两个服务。

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;namespace Client.Controllers
{[Route("api")][ApiController]public class HomeController : ControllerBase{[HttpGet][Route("service_result")]public async Task<IActionResult> GetService([FromServices] IService service){return Ok(new{serviceA = await service.GetServiceA(),serviceB = await service.GetServiceB()});}}
}

直接在Visual Studio中运行Client项目,在浏览器访问api。

大功告成,服务注册与发现,现在就算之中的某个节点挂掉,服务也可以照常运行。

.NET Core 使用 Consul 服务注册发现相关推荐

  1. ASP.NET Core gRPC 使用 Consul 服务注册发现

    一. 前言 gRPC 在当前最常见的应用就是在微服务场景中,所以不可避免的会有服务注册与发现问题,我们使用gRPC实现的服务可以使用 Consul 或者 etcd 作为服务注册与发现中心,本文主要介绍 ...

  2. Consul服务注册发现与健康检查

    一.Consul概述 consul是一个开源的使用go语言开发的服务发现.配置管理中心服务.内置了服务注册与发现框 架.分布一致性协议实现.健康检查.Key/Value存储.多数据中心方案,不再需要依 ...

  3. consul 日志配置_微服务:服务注册发现+ API 网关+配置中心+服务跟踪+服务熔断...

    服务注册发现 服务注册就是维护一个登记簿,它管理系统内所有的服务地址.当新的服务启动后,它会向登记 簿交待自己的地址信息.服务的依赖方直接向登记簿要 Service Provider 地址就行了.当下 ...

  4. 微服务注册发现集群搭建——Registrator + Consul + Consul-template + nginx

    在互联网应用领域,服务的动态性需求十分常见,这就对服务的自动发现和可动态扩展提出了很高的要求. 微服务系统动辄上万个服务,而且还要动态伸缩.以人工写好的IP.Port 硬编码脚本的方式无法做到大规模自 ...

  5. k8s consul 服务发现_Swoft之服务注册发现Consul服务器配置

    Consul服务器配置 微服务带来最大的好处就是把整个大项目分割成不同的服务,运行在不同服务器上,实现解耦和分布式处理.微服务虽然有很多好处,但是也会有不好的一方面.任何事物都会有两面性,在微服务里面 ...

  6. Consul 服务注册与发现一站式解决方案

    Consul 服务注册与发现一站式解决方案 参考文章: (1)Consul 服务注册与发现一站式解决方案 (2)https://www.cnblogs.com/seattle-xyt/p/103660 ...

  7. SpringCloud从入门到放弃 03 ——Consul服务注册与发现

    文章目录 SpringCloud从入门到放弃 03 --Consul服务注册与发现 一.Consul简介 1.什么是consul 2.consul能做什么 二.安装并运行Consul 1.下载安装 2 ...

  8. Consul服务注册与发现

    Consul服务注册与发现 1.Consul简介 1.1 什么是Consul? 1.2 Consul能干什么? 1.3 下载Consul 2.安装并运行Consul 2.1 官网安装说明 2.2 使用 ...

  9. 七、consul服务注册与发现

    1.Consul简介 是什么? Consul是一套开源的分布式服务发现的配置管理系统,由HashiCorp用go语言开发 提供了微服务系统中的服务治理,配置中心,控制总线等功能.这些功能中的每一个可以 ...

最新文章

  1. TFS2012导Bug流程时,提示TF26204: The account you entered is not recognized.
  2. 初入react -01
  3. ggplot2 theme相关设置—文本调整
  4. 【leetcode】41. First Missing Positive
  5. [转]iOS开发使用半透明模糊效果方法整理
  6. 剑指offer系列14---合并两个升序链表
  7. 牛客网刷题(纯java题型 211~240题)
  8. 一个事务复制的bug--更新丢失
  9. python cv2 绘制不规则形状的最小外接矩形、最大内接矩形、最大内接圆、最小外接圆、拟合椭圆
  10. 百度自动php推送蜘蛛怎么不来访问,使用代码向百度蜘蛛主动推送链接
  11. ALTREA cyclone IV e系列程序固化方法
  12. html调用短信接口发送消息的实例,HTTP电脑发送短信接口调用示例
  13. 青少年编程究竟应该从什么语言学起?
  14. oracle apex接口文件,Oracle_APEX开发指南
  15. 贵州2021高考成绩排名查询,贵州高考排名查询方法,2021年贵州高考成绩位次全省排名查询...
  16. Manifest merger failed with multiple errors, see logs问题解决
  17. 外文翻译原文附在后面_外文翻译原文及配套译文
  18. Monthly Expense(二分专题)
  19. Linux 之systemd服务简介
  20. 立创商城中元器件封装的3d模型导出STEP格式文件

热门文章

  1. 业务id转密文短链的一种实现思路
  2. 【python】python中的定义类属性和对像属性
  3. Meta http-equiv属性详解
  4. 2.2 PostgreSQL 概念
  5. 退出Activity(转)
  6. VS2010插件之NuGet
  7. javascript:设置URL参数的方法,适合多条件查询
  8. SharePoint 2010 中的BCS身份验证模式
  9. 12种方法返回2个文件路径之间的公共基路径ExtractBasePath
  10. Mac OS使用技巧之十五:快捷方便的Mini Dock