Net Core Consul
Consul 是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置,内置了服务注册发现框架,分布式一致性实现,健康检查,负载均衡,服务治理,多数据中心方案等。Consul用GoLang语言实现,因此具有天然可移植性(支持Linux,Window和Mac OS),安装包仅包含一个可执行文件(consul.exe),方便部署,与Docker等轻量级容器可无缝链接。
要想利用Consul提供的服务实现服务的注册与实现,我们需要建立Consul Cluster,在Consul方案中,每个提供服务的节点上都要部署和运行Consul的Client Agent,所有运行Consul Agent节点的集合构成Consul Cluster。Consul Agent有两种运行模式:Server和Client。这里的Server和CLient只是Consul集群层面的区分,与搭建在Cluster之上的应用服务无关,以Server模式运行的Consul Agent节点用于维护Consul集群的状态,官方建议每个Consul Cluster至少有3个或以上的运行在Server Mode的Agent,Client节点不限。
Consul支持多数据中心,每个数据中心的Consul Cluster都会运行于Server模式下的Agent节点中选出一个Leader节点,这个选举过程通过Consul实现Raft协议保证,多个Server节点上的Consul数据信息是强一致的,处于Client Mode的Consul Agent节点比较简单,无状态,仅仅负责将请求转发给Server Agent。
Consul官网下载地址 Consul 解压文件,通过cmd指定解压文件路径
本示例通过dev方式启动 如图所示:
服务注册到consul可以通过Http API (8500端口),Consul client及Consul配置文件的方式。
1、下面介绍通过Consul Client API方式将service注册到Consul:
1)、appsettings.json consul节点配置信息
1 "Consul": { 2 "Address": "http://127.0.0.1:8500", 3 "ServiceName": "ServiceA", 4 "ServiceIP": "localhost", 5 "ServicePort": "5000", 6 "HealthCheck": "/HealthCheck" 7 }
2)、Nuget package下载Consul(1.6.10.8)
3)、Program配置Consul
1 builder.Services.AddSingleton<IConsulClient, ConsulClient>(p 2 => new ConsulClient(config => 3 { 4 config.Address = new Uri(configuration["Consul:Address"]); 5 }));
1 #region Consul 2 app.UseConsul(configuration, app.Lifetime); 3 #endregion
1 public static class CousulExtension 2 { 3 public static IApplicationBuilder UseConsul(this IApplicationBuilder app, IConfiguration configuration, IHostApplicationLifetime lifetime) 4 { 5 var client = new ConsulClient(option => 6 { 7 option.Address = new Uri(configuration["Consul:Address"]); 8 }); 9 10 var registration = new AgentServiceRegistration 11 { 12 ID = Guid.NewGuid().ToString(), 13 //Name = configuration["Consul:ServiceName"], 14 //Address = configuration["Consul:ServiceIP"], 15 //Port = Convert.ToInt32(configuration["Consul:ServicePort"]), 16 //dotnet run --urls=https://localhost:5001 --ip=localhost --port=5001 --service=serviceA 17 Name = configuration["service"], 18 Address = configuration["ip"], 19 Port = Convert.ToInt32(configuration["port"]), 20 Check = new AgentServiceCheck 21 { 22 DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5), 23 Interval = TimeSpan.FromSeconds(10), 24 //HTTP = $"https://{configuration["Consul:ServiceIP"]}:{configuration["Consul:ServicePort"]}{configuration["Consul:HealthCheck"]}", 25 HTTP = $"https://{configuration["ip"]}:{configuration["port"]}{configuration["Consul:HealthCheck"]}", 26 Timeout = TimeSpan.FromSeconds(5) 27 } 28 }; 29 30 client.Agent.ServiceRegister(registration).GetAwaiter().GetResult(); 31 32 lifetime.ApplicationStopping.Register(() => 33 { 34 client.Agent.ServiceDeregister(registration.ID).GetAwaiter().GetResult(); 35 }); 36 return app; 37 } 38 }
Notes: Register到Consul的service Id必须唯一,原因是通过Ocelot 集成Consul service需要指定ServiceName,如果Consul service需要负载均衡的话service要指定同一个ServiceName.
e.g.
一个ServiceName有多个实例,从而可以做负载均衡。
4、创建一个NetCore API 项目,添加测试controller
1 [HttpGet] 2 public IActionResult Index([FromServices]IConfiguration configuration) 3 { 4 var result = new 5 { 6 msg = $"This is service1, current date: {DateTime.Now:G}", 7 ip = configuration["ip"], 8 port = configuration["port"] 9 }; 10 return Ok(result); 11 } 12 13 [Authorize] 14 [HttpGet("all")] 15 public IActionResult Get([FromServices] IConfiguration configuration) 16 { 17 return Ok( 18 new 19 { 20 ID = 1, 21 Name = "consul service", 22 version = 1.0, 23 serviceIP = configuration["ip"], 24 servicePort = configuration["port"] 25 }); 26 }
添加健康检查API Controller
1 [Route("[controller]")] 2 [ApiController] 3 public class HealthCheckController : Controller 4 { 5 [HttpGet] 6 public IActionResult Index() 7 { 8 return Ok(); 9 } 10 }
5、模拟服务集群创建多个服务实例分别注册到consul
dotnet run --urls=https://localhost:5001 --ip=localhost --port=5001 --service=serviceA dotnet run --urls=https://localhost:5002 --ip=localhost --port=5002 --service=serviceA dotnet run --urls=https://localhost:5003 --ip=localhost --port=5003 --service=serviceA
6、consul service
下面通过IConsulDiscoveryService测试是否可以正常从consul获取到服务
1 public class ConsulDiscoveryService : IConsulDiscoveryService 2 { 3 private readonly IConfiguration _configuration; 4 private IConsulClient _consulClient { get; set; } 5 private readonly IHttpClientFactory _httpClientFactory; 6 public ConsulDiscoveryService(IConfiguration configuration, IConsulClient consulClient, IHttpClientFactory httpClientFactory) 7 { 8 this._configuration = configuration; 9 this._consulClient = new ConsulClient(options => 10 { 11 options.Address = new Uri(_configuration["Consul:Address"]); 12 }); 13 this._httpClientFactory = httpClientFactory; 14 } 15 16 public async Task<IEnumerable<HttpResponseMessage>> GetAsync() 17 { 18 var responses = new List<HttpResponseMessage>(); 19 var serviceNames = new List<string>() { "Service1" }; 20 var tasks = new List<Task>(); 21 foreach (var serviceName in serviceNames) 22 { 23 var task = Task.Run(async () => 24 { 25 var options = new QueryOptions 26 { 27 Datacenter = "dc1", 28 WaitTime = TimeSpan.FromMinutes(5) 29 }; 30 var result = await _consulClient.Health.Service(serviceName, null, true, options); 31 var services = result.Response; 32 var serviceUrls = services.Select(s => $"https://{s.Service.Address}:{s.Service.Port}"); 33 foreach (var serviceUrl in serviceUrls) 34 { 35 using var httpClient = _httpClientFactory.CreateClient(); 36 var response = await httpClient.GetAsync($"{serviceUrl}/api/Service1/all"); 37 responses.Add(response); 38 } 39 }); 40 tasks.Add(task); 41 } 42 await Task.WhenAll(tasks); 43 return responses; 44 } 45 }
将ConsulDiscoveryService添加到Configure Service中
builder.Services.AddTransient<IConsulDiscoveryService, ConsulDiscoveryService>();
验证
[HttpGet("consulservice")] public async Task<IActionResult> GetConsulDiscoveryService([FromServices]IConsulDiscoveryService service) { var result = await service.GetAsync(); return Ok(result); }
2、通过配置文件的方式注册service,这里只创建了一个NetCore API作为demo
1)、Add一个.json 文件
1 { 2 "services": [ 3 { 4 "id": "consul_service01", 5 "name": "service1", 6 "tags": [ 7 "urlprefix-/ClientService01" 8 ], 9 "address": "localhost", 10 "port": 5000, 11 "checks": [ 12 { 13 "name": "clientservice_check", 14 "http": "https://localhost:5000/HealthCheck", 15 "interval": "5s", 16 "timeout": "5s" 17 } 18 ] 19 } 20 ] 21 }
2)、以熟悉的方式启动NetCore API,例: dotnet run
3)、启动consul,依然以dev方式启动,这里在实际使用中通常创建consul cluster,官方建议3-5个consul server,consul client数量不限
consul agent -dev -node MicServices -config-dir service.consul.json
4)、consul中查看注册的service
5)、通过API查看服务
OK, 可以正常获取consul下的所有services。