準備環境html
安裝consul以後git
1. 建立一個.net core webapi 舉例爲UsercenterServicegithub
2. nuget引用Consul組件 https://github.com/PlayFab/consuldotnetweb
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的節點keyjson
4.1 配置consul地址(舉例是在VS調試開發環境。因此在appsettings.Development.json中配置) c#
1 { 2 "ConsulForConfig": { 3 "Host": "{IP}:8500",//這裏替換成本身consul服務的IP地址 4 "Prefix": "git-dev/huangqiang/usercenterRegionIIS.json" 5 } 6 }
4.2 在consul上建立該節點而且配置api
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 }
獲取配置 app
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是程序維護的最新服務列表