.Net Core Configuration Etcd數據源

前言

    .Net Core爲咱們提供了一套強大的Configuration配置系統,使用簡單擴展性強。經過這套配置系統咱們能夠將Json、Xml、Ini等數據源加載到程序中,也能夠本身擴展其餘形式的存儲源。今天咱們要作的就是經過自定義的方式爲其擴展Etcd數據源操做。git

何爲Etdc

    在使用etcd以前咱們先介紹一下Etcd,我相信不少同窗都早有耳聞。Etcd是一款高可用、強一致的分佈式KV存儲系統,它內部採用raft協議做爲一致性算法,自己也是基於GO語言開發的,最新版本爲v3.4.9,具體版本下載地址可參閱官方GitHub地址。相信瞭解過K8S的同窗對這個確定不陌生,它是K8S的數據管理系統。官方地址爲https://etcd.io/
    在此以前,我相信你們已經瞭解過不少存儲系統了,Etcd到底能實現了什麼功能呢?其一用於配置中心和服務發現,再者也能夠實現分佈式鎖和消息系統。它自己就是基於目錄型存儲,而且內部有一套強大的Watch機制能夠監聽針對節點和數據的操做變化,每次對節點的事務操做都會有對於的版本信息。github

Etcd VS Zookeeper

經過上面的介紹是否是感受和Zookeeper有點相似呢😂😂😂,網上有不少不少關於Etcd和Zookeeper的對比文章,大體以下能夠獲得如下結論
算法

功能 Etcd Zookeeper
分佈式鎖 有(採用節點版本號信息) 有(採用臨時節點和順序臨時節點)
watcher
一致性算法 raft zab
選舉
元數據(metadata)存儲
應用場景 Etcd Zookeeper
發佈與訂閱(配置中心) 有(不限次Watch) 有(一次性觸發的,須要從新註冊Watch)
軟負載均衡
命名服務(Naming Service)
服務發現 有(基於租約節點) 有(基於臨時節點)
分佈式通知/協調
集羣管理與Master選舉
分佈式鎖
分佈式隊列
說白了就是Zookeeper能幹的活,Etcd也能幹。那既然有了Zookeeper爲啥還要選擇Etcd,主要基於如下緣由
  • 更輕量級(Etcd基於GO語言開發,Zookeeper基於Java開發)、更易用(開箱即用)
  • 高負載下的穩定讀寫
  • 數據模型的多版本併發控制
  • 穩定的watcher功能,通知訂閱者監聽值的變化(Zookeeper基於數據的監聽是一次性的,每次監聽完成還需從新註冊)
  • 客戶端協議使用GRPC協議,支持語言更普遍
一言以蔽之,就是不只實現了Zookeeper的功能,還在不少方面吊打Zookeeper😏😏😏,這麼強大的東西忍不住都要試一試。

在.Net Core中使用Etcd

    在Nuget上能夠搜索到不少.Net Core的Etcd客戶端驅動程序,我使用了下載量最多的一個名字叫dotnet-etcd的驅動包,順便找到了它在GayHub上,很差意思手滑打錯了😱😱😱GitHub上的項目地址,大概學習了一下基本的使用方式。其實咱們結合Configuration配置這一塊,只須要兩個功能。一個是Get獲取數據,另外一個是Watch節點變化(更新數據會用到)。我的認爲,前期有目有邊界的學習仍是很是重要的。json

Configuration擴展Etcd

前面咱們講到過自定義擴展Configuration是很是方便的,相信瞭解過Configuration相關源碼的小夥伴們已經很是熟悉了,大體總結一下分爲三步:數據結構

  • 編寫IConfigurationBuilder擴展方法,咱們這裏叫AddEtcd
  • 編寫實現IConfigurationSource的配置源信息類,咱們這裏叫EtcdConfigurationSource
  • 編寫繼承自ConfigurationProvider的ConfigurationSource的配置數據提供類,咱們這裏叫EtcdConfigurationProvider
由於微軟已經給咱們提供了一部分便利,因此編寫起來仍是很是的簡單的。好了,接下來咱們開始編寫具體的實現代碼,重點的地方我會在代碼中註釋說明。
首先是定義擴展類EtcdConfigurationExtensions,這個類是針對IConfigurationBuilder的擴展方法,實現以下
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文件的讀取,實現思路也都大體類似,有興趣的同窗能夠自行查閱。因爲主要是講解實現思路,可能許多細節並未作處理還望見諒。若是有疑問或者更好的建議,歡迎評論區交流指導。

👇歡迎掃碼關注👇
相關文章
相關標籤/搜索