准備環境
安裝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是程序維護的最新服務列表