.Net Core爲咱們提供了一套強大的Configuration配置系統,使用簡單擴展性強。經過這套配置系統咱們能夠將Json、Xml、Ini等數據源加載到程序中,也能夠本身擴展其餘形式的存儲源。今天咱們要作的就是經過自定義的方式爲其擴展Etcd數據源操做。git
在使用etcd以前咱們先介紹一下Etcd,我相信不少同窗都早有耳聞。Etcd是一款高可用、強一致的分佈式KV存儲系統,它內部採用raft協議做爲一致性算法,自己也是基於GO語言開發的,最新版本爲v3.4.9,具體版本下載地址可參閱官方GitHub地址。相信瞭解過K8S的同窗對這個確定不陌生,它是K8S的數據管理系統。官方地址爲https://etcd.io/。
在此以前,我相信你們已經瞭解過不少存儲系統了,Etcd到底能實現了什麼功能呢?其一用於配置中心和服務發現,再者也能夠實現分佈式鎖和消息系統。它自己就是基於目錄型存儲,而且內部有一套強大的Watch機制能夠監聽針對節點和數據的操做變化,每次對節點的事務操做都會有對於的版本信息。github
經過上面的介紹是否是感受和Zookeeper有點相似呢😂😂😂,網上有不少不少關於Etcd和Zookeeper的對比文章,大體以下能夠獲得如下結論
算法
功能 | Etcd | Zookeeper |
---|---|---|
分佈式鎖 | 有(採用節點版本號信息) | 有(採用臨時節點和順序臨時節點) |
watcher | 有 | 有 |
一致性算法 | raft | zab |
選舉 | 有 | 有 |
元數據(metadata)存儲 | 有 | 有 |
應用場景 | Etcd | Zookeeper |
---|---|---|
發佈與訂閱(配置中心) | 有(不限次Watch) | 有(一次性觸發的,須要從新註冊Watch) |
軟負載均衡 | 有 | 有 |
命名服務(Naming Service) | 有 | 有 |
服務發現 | 有(基於租約節點) | 有(基於臨時節點) |
分佈式通知/協調 | 有 | 有 |
集羣管理與Master選舉 | 有 | 有 |
分佈式鎖 | 有 | 有 |
分佈式隊列 | 有 | 有 |
在Nuget上能夠搜索到不少.Net Core的Etcd客戶端驅動程序,我使用了下載量最多的一個名字叫dotnet-etcd的驅動包,順便找到了它在GayHub上,很差意思手滑打錯了😱😱😱GitHub上的項目地址,大概學習了一下基本的使用方式。其實咱們結合Configuration配置這一塊,只須要兩個功能。一個是Get獲取數據,另外一個是Watch節點變化(更新數據會用到)。我的認爲,前期有目有邊界的學習仍是很是重要的。json
前面咱們講到過自定義擴展Configuration是很是方便的,相信瞭解過Configuration相關源碼的小夥伴們已經很是熟悉了,大體總結一下分爲三步:數據結構
public static class EtcdConfigurationExtensions { /// <summary> /// AddEtcd擴展方法 /// </summary> /// <param name="serverAddress">Etcd地址</param> /// <param name="path">讀取路徑</param> /// <returns></returns> public static IConfigurationBuilder AddEtcd(this IConfigurationBuilder builder, string serverAddress,string path) { return AddEtcd(builder, serverAddress:serverAddress, path: path,reloadOnChange: false); } /// <summary> /// AddEtcd擴展方法 /// </summary> /// <param name="serverAddress">Etcd地址</param> /// <param name="path">讀取路徑</param> /// <param name="reloadOnChange">若是數據發送改變是否刷新</param> /// <returns></returns> public static IConfigurationBuilder AddEtcd(this IConfigurationBuilder builder, string serverAddress, string path, bool reloadOnChange) { return AddEtcd(builder,options => { options.Address = serverAddress; options.Path = path; options.ReloadOnChange = reloadOnChange; }); } public static IConfigurationBuilder AddEtcd(this IConfigurationBuilder builder, Action<EtcdOptions> options) { EtcdOptions etcdOptions = new EtcdOptions(); options.Invoke(etcdOptions); return builder.Add(new EtcdConfigurationSource { EtcdOptions = etcdOptions }); } }
這裏我還定義了一個EtcdOptions的POCO,用於承載讀取Etcd的配置屬性併發
public class EtcdOptions { /// <summary> /// Etcd地址 /// </summary> public string Address { get; set; } /// <summary> /// Etcd訪問用戶名 /// </summary> public string UserName { get; set; } /// <summary> /// Etcd訪問密碼 /// </summary> public string PassWord { get; set; } /// <summary> /// Etcd讀取路徑 /// </summary> public string Path { get; set; } /// <summary> /// 數據變動是否刷新讀取 /// </summary> public bool ReloadOnChange { get; set; } }
接下來咱們定義EtcdConfigurationSource,這個類很是簡單就是返回一個配置提供對象負載均衡
public class EtcdConfigurationSource : IConfigurationSource { public EtcdOptions EtcdOptions { get; set; } public IConfigurationProvider Build(IConfigurationBuilder builder) { return new EtcdConfigurationProvider(EtcdOptions); } }
真正的讀取操做都在EtcdConfigurationProvider裏分佈式
public class EtcdConfigurationProvider : ConfigurationProvider { private readonly string _path; private readonly bool _reloadOnChange; private readonly EtcdClient _etcdClient; public EtcdConfigurationProvider(EtcdOptions options) { //實例化EtcdClient _etcdClient = new EtcdClient(options.Address,username: options.UserName,password: options.PassWord); _path = options.Path; _reloadOnChange = options.ReloadOnChange; } /// <summary> /// 重寫加載方法 /// </summary> public override void Load() { //讀取數據 LoadData(); //數據發生變化是否從新加載 if (_reloadOnChange) { ReloadData(); } } private void LoadData() { //讀取Etcd裏的數據 string result = _etcdClient.GetValAsync(_path).GetAwaiter().GetResult(); if (string.IsNullOrEmpty(result)) { return; } //轉換一下數據結構,這裏我使用的是json格式 //讀取的數據只要賦值到Data屬性上便可,IConfiguration真正讀取的數據就是存儲到Data的字典數據 Data = ConvertData(result); } private IDictionary<string,string> ConvertData(string result) { byte[] array = Encoding.UTF8.GetBytes(result); MemoryStream stream = new MemoryStream(array); //JsonConfigurationFileParser是將json數據轉換爲Configuration可讀取的結構(複製JsonConfiguration類庫裏的😄😄😄) return JsonConfigurationFileParser.Parse(stream); } private void ReloadData() { WatchRequest request = new WatchRequest() { CreateRequest = new WatchCreateRequest() { //須要轉換一個格式,由於etcd v3版本的接口都包含在grpc的定義中 Key = ByteString.CopyFromUtf8(_path) } }; //監聽Etcd節點變化,獲取變動數據,更新配置 _etcdClient.Watch(request, rsp => { if (rsp.Events.Any()) { var @event = rsp.Events[0]; //須要轉換一個格式,由於etcd v3版本的接口都包含在grpc的定義中 Data = ConvertData(@event.Kv.Value.ToStringUtf8()); //須要調用ConfigurationProvider的OnReload方法觸發ConfigurationReloadToken通知 //這樣才能對使用Configuration的類發送數據變動通知 //好比IOptionsMonitor就是經過ConfigurationReloadToken通知變動數據的 OnReload(); } }); } }
使用方式以下ide
builder.AddEtcd("http://127.0.0.1:2379", "service/mydemo", true);
順便給你們推薦一個Etcd可視化管理工具ETCD Manager,以便更好的學習Etcd。
到這裏,基本上就結束了,是否是很是簡單。主要仍是Configuration自己的設計思路比較清晰,因此實現起來也不費勁。工具
以上代碼都已經上傳了個人GitHub,該倉庫還擴展了其餘數據源的讀取好比Consul、Properties文件、Yaml文件的讀取,實現思路也都大體類似,有興趣的同窗能夠自行查閱。因爲主要是講解實現思路,可能許多細節並未作處理還望見諒。若是有疑問或者更好的建議,歡迎評論區交流指導。