.net core consul 服务配置 服务发现 服务健康检测 服务变更加载
准备环境
安装consul之后
1. 创建一个.net core webapi 举例为UsercenterService
2. nuget引用Consul组件 https://github.com/PlayFab/consuldotnet
3. 创建配置实体类 (后面涉及功能介绍时候再解释属性含义)
1 public class AppSettings 2 { 3 /// <summary> 4 /// 数据库连接字符串 5 /// </summary> 6 public string DbConnection { get; set; } 7 8 /// <summary> 9 /// 服务注册参数 10 /// </summary> 11 public ServiceRegisterOptions ServiceRegisterOptions { get; set; } 12 } 13 14 public class ServiceRegisterOptions 15 { 16 /// <summary> 17 /// 是否启用 18 /// </summary> 19 public bool IsActive { get; set; } 20 /// <summary> 21 /// 服务名称 22 /// </summary> 23 public string ServiceName { get; set; } 24 /// <summary> 25 /// 服务IP或者域名 26 /// </summary> 27 public string ServiceHost { get; set; } 28 /// <summary> 29 /// 服务端口号 30 /// </summary> 31 public int ServicePort { get; set; } 32 /// <summary> 33 /// consul注册地址 34 /// </summary> 35 public string ConsulRegisterUrl { get; set; } 36 /// <summary> 37 /// 标签 例如laiwutest 38 /// </summary> 39 public string[] Tags { get; set; } 40 }
4. appsettings配置consul服务地址和UserService配置在consul的节点key
4.1 配置consul地址(举例是在VS调试开发环境。所以在appsettings.Development.json中配置)
1 { 2 "ConsulForConfig": { 3 "Host": "{IP}:8500",//这里替换成自己consul服务的IP地址 4 "Prefix": "git-dev/huangqiang/usercenterRegionIIS.json" 5 } 6 }
4.2 在consul上创建该节点并且配置
1 { 2 "DbConnection": "111111111111111111111111111111111111111", 3 "ServiceRegisterOptions": 4 { 5 "IsActive":true, 6 "ServiceName":"UserCenterRegion", 7 "ServiceHost":"{IP}",//修改{IP}为你注入的服务的ip地址 8 "ServicePort":"{Port}",//修改{Port}为你注入的服务的端口 9 "ConsulRegisterUrl":"{IP}:8500",//修改{IP}为你的consul服务的IP 10 "Tags":["浙江杭州"] 11 }, 12 }
获取配置
1 public static AppSettings AddAppSettingByConsul(this IServiceCollection sc, IConfiguration configuration) 2 { 3 try 4 { 5 //get local consul service address configration consulclient 6 var consulAddress = $"http://" + configuration["ConsulForConfig:Host"]; 7 var key = configuration["ConsulForConfig:Prefix"]; 8 if (string.IsNullOrWhiteSpace(consulAddress) || string.IsNullOrWhiteSpace(key)) 9 { 10 throw new Exception("无法获取consulAddress地址或者consul key"); 11 } 12 var consulClient = new ConsulClient(cfg => { cfg.Address = new Uri(consulAddress); }); 13 sc.AddSingleton<IConsulClient>(p => consulClient); 14 //get app config 15 var res = consulClient.KV.Get(key).GetAwaiter().GetResult(); 16 var resStr = Encoding.UTF8.GetString(res.Response.Value); 17 var appSettings = JsonConvert.DeserializeObject<AppSettings>(resStr); 18 if (appSettings == null) 19 { 20 throw new Exception($"appSettings 为null,consul 配置:{resStr}"); 21 } 22 sc.AddSingleton<AppSettings>(appSettings); 23 return appSettings; 24 } 25 catch (Exception e) 26 { 27 _log.Main.Error($"获取consul appsettings配置异常:{e.Message}"); 28 Environment.Exit(-1); 29 } 30 return null; 31 }
这里抽了一个扩展方法。使用的时候在Startup.cs类中的方法ConfigureServices中加入,这里弄了返回值只是偷懒下。
AddAppSettingByConsul方法逻辑:先是拿到配置的consull服务地址和Key,再通过前面nuget引用的consul组件中的consulclient获取配置,最后注入到容器
调试下 就拿到配置了。这样方便分布式服务,不用每台都配置,直接consul管理
配置健康检测和服务注册
准备健康检测接口:
1 [Route("api/v1/[controller]")] 2 [ApiController] 3 public class HealthController : ControllerBase 4 { 5 [HttpGet] 6 public IActionResult Get() => Ok("ok"); 7 }
.net core 配置注册和健康检测的地址
1 public static void UseConsul(this IApplicationBuilder app, IApplicationLifetime appLife) 2 { 3 try 4 { 5 var appSettings = app.ApplicationServices.GetService<AppSettings>(); 6 var consulClient = app.ApplicationServices.GetService<IConsulClient>(); 7 8 //config consul health check 9 var healthCheck = new AgentServiceCheck 10 { 11 DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5), 12 Interval = TimeSpan.FromSeconds(30), 13 HTTP = $"{appSettings.ServiceRegisterOptions.ServiceHost}:{appSettings.ServiceRegisterOptions.ServicePort}/api/v1/Health", 14 }; 15 16 //service register 17 var serviceId = $"{appSettings.ServiceRegisterOptions.ServiceName}_{appSettings.ServiceRegisterOptions.ServiceHost}:{appSettings.ServiceRegisterOptions.ServicePort}"; 18 var registration = new AgentServiceRegistration 19 { 20 Checks = new[] { healthCheck }, 21 Address = appSettings.ServiceRegisterOptions.ServiceHost, 22 Port = appSettings.ServiceRegisterOptions.ServicePort, 23 ID = serviceId, 24 Name = appSettings.ServiceRegisterOptions.ServiceName, 25 Tags = appSettings.ServiceRegisterOptions.Tags 26 }; 27 consulClient.Agent.ServiceRegister(registration).GetAwaiter().GetResult(); 28 29 //service Deregister when app stop 30 appLife.ApplicationStopped.Register(() => 31 { 32 consulClient.Agent.ServiceDeregister(serviceId).GetAwaiter().GetResult(); 33 }); 34 35 36 } 37 catch (Exception e) 38 { 39 _logger.Main.Error($"UseConsul error:{e.Message}"); 40 Environment.Exit(-1); 41 } 42 43 }
这里也是抽了个扩展方法。调用放到Startup
UseConsul方法解释:先是从容器中拿到前面注入的配置实体AppSettings和ConsulClient。再配置健康检测,再配置服务注册,再配置当服务关闭时候注销服务。
其中健康检测的DeregisterCriticalServiceAfter表示如果服务启动失败,多少时间内注销consul上的该服务。
服务注册的参数就不介绍了
然后跑起来之后,到consul ui瞧一瞧。是不是注册成功,心跳正常
状态为passing为正常的,刚启动时候状态会为critical。 当你的状态一直为critical时候,过了前面DeregisterCriticalServiceAfter的时间,服务将会注销,也就是注册失败。可能原因:服务地址配置有问题,consul无法访问你的health地址,也可能你的端口没打开。telnet看看
当都成功的时候,服务已经正常注册到consul。下面再说说服务发现和服务变更发现
服务发现和服务变更发现
服务发现调用的方法有很多,agent,catalog,health,都可以获取列表。但是agent是查询本地自己的,catalog是整个集群的,heath是查询健康的。这里用health获取举例
关键就一句话:_consulClient.Health.Service(serviceName, tag, true, queryOptions).Result。
这样就能获取到注册到consul的服务列表了,但是如果有服务变更了(新的服务注册,旧的服务停止),应该怎么办?
一般想到启动一个线程不停的去拿,是没有问题,但是有个更好的东西,“Blocking Queries” https://www.consul.io/api/index.html
这个东西简单来说就是会记录一个版本,consul服务端通过这个版本来判断是不是已经是最新的服务列表,如果是的话,那么将会阻塞一定时间(这个时间可配置)
在c# 里面体现就是第三个参数queryOptions的WaitIndex和WaitTime,以及返回LastIndex,下面po出一部分代码。
public void GetAllService() { _serviceIndexList.ForEach(p => { Task.Run(() => { var queryOptions = new QueryOptions { WaitTime = TimeSpan.FromSeconds(_waitTime) }; while (true) { GetAgentServices(queryOptions, p.ServiceName, p.Tag); } }); }); } public void GetAgentServices(QueryOptions queryOptions, string serviceName, string tag = null) { try { var res = _consulClient.Health.Service(serviceName, tag, true, queryOptions).Result; _logger.Main.Info($"GetServiceList:{serviceName} {tag} waitIndex:{res.LastIndex}"); if (queryOptions.WaitIndex != res.LastIndex) { queryOptions.WaitIndex = res.LastIndex; var currentService = _consulServices.FirstOrDefault(p => p.ServiceName == serviceName); if (currentService == null) { _consulServices.Add(new ConsulService { ServiceName = serviceName, Tag = tag, ServiceEntries = new ConcurrentBag<ServiceEntry>(res.Response) }); } else { currentService.ServiceEntries = new ConcurrentBag<ServiceEntry>(res.Response); } } } catch (AggregateException ae) { _logger.Main.Error($"consul获取{serviceName},{tag}服务列表资源错误:{ae.Flatten()}",ae); } catch (Exception e) { _logger.Main.Error($"consul获取{serviceName},{tag}服务列表资源错误",e); } }
注:代码中的_serviceIndexList是存着需要获取哪些服务的服务tag,_consulServices是程序维护的最新服务列表