(九)分散式服務----Zookeeper註冊中心

来源:https://www.cnblogs.com/hongwei918/archive/2019/10/05/11617770.html
-Advertisement-
Play Games

==>>點擊查看本系列文章目錄 首先看一下幾種註冊中心: 最老的就是Zookeeper了, 比較新的有Eureka,Consul 都可以做註冊中心。可以自行搜索對比三者的優缺點。 Zookeeper 最開始就是hadoop大家族中的一員,用於做協調的框架,後來已經是apache的子項目了。 幾年前大 ...


 ==>>點擊查看本系列文章目錄

 

首先看一下幾種註冊中心:

最老的就是Zookeeper了, 比較新的有Eureka,Consul 都可以做註冊中心。可以自行搜索對比三者的優缺點。

Zookeeper 最開始就是hadoop大家族中的一員,用於做協調的框架,後來已經是apache的子項目了。

幾年前大數據很火的時候,只要學hadoop必學zookeeper,當然還有其他成員。

大數據簡單說就是分散式,比如分散式文件存儲hdfs,分散式資料庫hbase,分散式協調zookeeper,還有kafka,Flume等等都是hadoop大家族。

zookeeper,現在更多被用來做註冊中心,比如阿裡的開源SOA框架dubbo就經常搭配zookeeper做註冊中心。

Eureka:java的微服務框架Spring Cloud中內部已經集成了Eureka註冊中心。

我選擇zookeeper,不是因為他比另外兩個強,而是因為我幾年前就已經學習過一些zookeeper的原理,上手更容易。網路上學習書籍、資料、視頻教程也特別多,學習資料完善。

 

註冊中心的基本功能:

1. 註冊服務,有點類似DNS,所有的服務註冊到註冊中心,包含服務的地址等信息。

2. 服務訂閱,客戶端請求服務,註冊中心就要把那些能用的伺服器地址告訴客戶端,服務端有變動時,註冊中心也能及時通知到客戶端。

3. 性能好且高可用,註冊中心自身也是一個集群,如果只有一個註冊中心機器的話那豈不是把註冊中心累死啊,而且他一旦壞了以後,那客戶端都找不到伺服器了。所有註冊中心就有很多台,其中只有一個老大(leader),老大用來寫,小弟用來讀。就是說老大來決定一臺伺服器能不能註冊進來,小弟負責幫助客戶端查找伺服器。因為註冊服務的次數是很少的,通常有新伺服器加入才需要註冊,但是客戶端訂閱那就很多了,所以註冊中心只有一個leader。leader萬一壞掉的話,會從小弟中選舉出一個來當老大接替工作。

 

上面提到說zookeeper集群,就是說有很多台機器做zookeeper機器,但是這些機器里存儲的東西基本上都是一樣的,就是說客戶端不管連到哪個zookeeper 都是一樣的,能做服務訂閱。

每一個zookeeper 中都有很多節點(Znode)。

接下來說的zookeeper節點和集群完全不是一回事。 有些人喜歡吧集群中的每一臺zookeeper機器稱為一個節點,但是這個節點(zookeeper機器)和我說的節點(Znode)完全不是一回事。

如下圖:

 

 本例的圖中可以看到,一共有5台機器,每台機器都有5個znode,Znode下麵的子節點就更多了。

先看5台機器:

一臺leader,老大,上文已經介紹,服務都從這些註冊寫入。

兩台follower,小弟,平時用於服務訂閱,老大掛掉以後,follower內部就會自行選出老大。

兩台observer,觀察者,就是屬於無業游民,只能看,沒有選老大的資格,不能參與競選也不能投票,唯一的功能就是服務訂閱。

  observer模式需要手動開啟,為什麼會出現observer呢,是因為機器太多的話,每個機器都有選舉權的話特別影響性能。全中國14億人口,每個人都參與國家競選的話,效率極低。所以呢,選舉的工作就交給follower完成就行了,只需要確保一直都有leader接班人就好。

 

再看看zookeeper有什麼基本功能:

基本功能很簡單,組合以後卻可以完成各種複雜工作。

1. 可以創建:臨時節點(斷開連接時便刪除節點) 和 持久化節點(必須手動刪除節點)。

2. 可以創建:無序節點 和 有序節點。

3. 節點上可以添加watcher監聽功能,監聽該節點的增刪改,然後觸發自定義的事件。

 

看看這些功能怎麼用:

1. 節點: 每次註冊一個服務就創建一個節點,節點的名稱(Key)就是服務的名稱,服務的詳細信息存儲在節點value中,客戶端通過key找到對應的節點,再找打節點中的value。

2. 臨時節點:服務端註冊一個服務時創建一個臨時節點,服務斷開時,臨時節點自動銷毀,自動完成服務註銷。

3. watcher監聽: 客戶端在註冊中心訂閱了一個服務的時候,同時在這個服務所在的節點上加一個監聽事件,每當服務節點信息有變化的時候,註冊中心會自動回調通知客戶端。

4. 有序臨時節點:分散式鎖或者分散式隊列(這裡與服務註冊無關),客戶端1想要操作一條數據的時候,在A節點下創建一個有序臨時節點,自動分配編號001;客戶端1也要操作該數據的時候,在A節點下也創建一個有序臨時節點,自動分配編號002。只有編號最小的子節點才會被執行,因此001節點會被執行,客戶端1執行完畢後,自動刪除001節點,此時002編號為最小子節點。即鎖的概念,不能同時操作同一數據;也可以做隊列,按照先後順序依次執行。

5. 有序臨時節點+watcher監聽: 上面第4條中說到每次執行編號最小的節點,因此需要有一個程式,每次都需要遍歷全部節點,然後找出最小的節點,假如是002節點,這時客戶端2開始執行。但是添加監聽機制以後就不一樣了,002監聽001,003監聽比他小一號的002,這樣001銷毀的同時通知002開始執行,002銷毀的時候通知003開始執行,不需要遍歷最小節點,也能有序依次執行。

6. 臨時節點+watcher監聽: 集群master選舉以及高可用。比如hadoop集群,也有一個resourcemanager資源管理器,負責調度其它節點機器,相當於hadoop集群的leader節點。這個leader就可以交由zookeeper管理,所有的hadoop機器同時在zookeeper中創建一個同名的臨時節點,由於是同名互斥的節點,因此只有一個節點能被創建,成功創建這個節點的hadoop機器就是leader。同時添加Watcher監聽,這個leader只要斷開連接,臨時節點自動銷毀,觸發監聽,其它hadoop開始新一輪的master選舉。這也是zookeeper最初在hadoop家族中的重要使命。

7....... 還要很多地方都能用zookeeper,簡直無所不能,而且自身也是高可用,高性能,牛x

 

zookeeper本身的操作還是很簡單的,無非就是節點的增刪改查,可以選擇要創建節點的類型,還有就是在節點上添加watcher監聽器。就這些。

 

文件結構:

 

上代碼:

zookeeper客戶端管理類:

public class ZookeeperClientProvider
    {
        private ConfigInfo _config;
        private readonly ILogger<ZookeeperClientProvider> _logger;
        private readonly Dictionary<string, ZooKeeper> _zookeeperClients = new Dictionary<string, ZooKeeper>();

        public ZookeeperClientProvider(ConfigInfo config, ILogger<ZookeeperClientProvider> logger)
        {
            _config = config;
            _logger = logger;
        }

        public async Task<ZooKeeper> GetZooKeeper()
        {
            return await CreateZooKeeper(_config.Addresses.FirstOrDefault());
        }
        public async Task<ZooKeeper> CreateZooKeeper(string address)
        {
            if (!_zookeeperClients.TryGetValue(address, out ZooKeeper result))
            {
                await Task.Run(() =>
                {
                    result = new ZooKeeper(address, (int)_config.SessionTimeout.TotalMilliseconds,
                        new ReconnectionWatcher(
                            async () =>
                            {
                                if (_zookeeperClients.Remove(address, out ZooKeeper value))
                                {
                                    await value.closeAsync();
                                }
                                await CreateZooKeeper(address);
                            }));
                    _zookeeperClients.TryAdd(address, result);
                });
            }
            return result;
        }

        public async Task<IEnumerable<ZooKeeper>> GetZooKeepers()
        {
            var result = new List<ZooKeeper>();
            foreach (var address in _config.Addresses)
            {
                result.Add(await CreateZooKeeper(address));
            }
            return result;
        }
    }
ZookeeperClientProvider

zookeeper服務註冊類:

/// <summary>
    /// 一個抽象的服務路由發現者。
    /// </summary>
    public interface IServiceRouteManager
    {

        /// <summary>
        /// 服務路由被創建。
        /// </summary>
        event EventHandler<ServiceRouteEventArgs> Created;

        /// <summary>
        /// 服務路由被刪除。
        /// </summary>
        event EventHandler<ServiceRouteEventArgs> Removed;

        /// <summary>
        /// 服務路由被修改。
        /// </summary>
        event EventHandler<ServiceRouteChangedEventArgs> Changed;

        /// <summary>
        /// 獲取所有可用的服務路由信息。
        /// </summary>
        /// <returns>服務路由集合。</returns>
        Task<IEnumerable<ServiceRoute>> GetRoutesAsync();

        /// <summary>
        /// 設置服務路由。
        /// </summary>
        /// <param name="routes">服務路由集合。</param>
        /// <returns>一個任務。</returns>
        Task SetRoutesAsync(IEnumerable<ServiceRoute> routes);

        /// <summary>
        /// 移除地址列表
        /// </summary>
        /// <param name="routes">地址列表。</param>
        /// <returns>一個任務。</returns>
        Task RemveAddressAsync(IEnumerable<string> Address);
        /// <summary>
        /// 清空所有的服務路由。
        /// </summary>
        /// <returns>一個任務。</returns>
        Task ClearAsync();
    }

    /// <summary>
    /// 服務路由事件參數。
    /// </summary>
    public class ServiceRouteEventArgs
    {
        public ServiceRouteEventArgs(ServiceRoute route)
        {
            Route = route;
        }

        /// <summary>
        /// 服務路由信息。
        /// </summary>
        public ServiceRoute Route { get; private set; }
    }

    /// <summary>
    /// 服務路由變更事件參數。
    /// </summary>
    public class ServiceRouteChangedEventArgs : ServiceRouteEventArgs
    {
        public ServiceRouteChangedEventArgs(ServiceRoute route, ServiceRoute oldRoute) : base(route)
        {
            OldRoute = oldRoute;
        }

        /// <summary>
        /// 舊的服務路由信息。
        /// </summary>
        public ServiceRoute OldRoute { get; set; }
    }
IServiceRouteManager
public class ZooKeeperServiceRouteManager : IServiceRouteManager, IDisposable
    {
        private readonly ConfigInfo _configInfo;
        private readonly ISerializer<byte[]> _serializer;
        private readonly ILogger<ZooKeeperServiceRouteManager> _logger;
        private ServiceRoute[] _routes;
        private readonly ZookeeperClientProvider _zookeeperClientProvider;

        public ZooKeeperServiceRouteManager(ConfigInfo configInfo, ISerializer<byte[]> serializer,
            ISerializer<string> stringSerializer,
            ILogger<ZooKeeperServiceRouteManager> logger,
            ZookeeperClientProvider zookeeperClientProvider)
        {
            _configInfo = configInfo;
            _serializer = serializer;
            _logger = logger;
            _zookeeperClientProvider = zookeeperClientProvider;
            EnterRoutes().Wait();
        }

        private EventHandler<ServiceRouteEventArgs> _created;
        private EventHandler<ServiceRouteEventArgs> _removed;
        private EventHandler<ServiceRouteChangedEventArgs> _changed;

        /// <summary>
        /// 服務路由被創建。
        /// </summary>
        public event EventHandler<ServiceRouteEventArgs> Created
        {
            add { _created += value; }
            remove { _created -= value; }
        }

        /// <summary>
        /// 服務路由被刪除。
        /// </summary>
        public event EventHandler<ServiceRouteEventArgs> Removed
        {
            add { _removed += value; }
            remove { _removed -= value; }
        }

        /// <summary>
        /// 服務路由被修改。
        /// </summary>
        public event EventHandler<ServiceRouteChangedEventArgs> Changed
        {
            add { _changed += value; }
            remove { _changed -= value; }
        }



        protected void OnCreated(params ServiceRouteEventArgs[] args)
        {
            if (_created == null)
                return;

            foreach (var arg in args)
                _created(this, arg);
        }

        protected void OnChanged(params ServiceRouteChangedEventArgs[] args)
        {
            if (_changed == null)
                return;

            foreach (var arg in args)
                _changed(this, arg);
        }

        protected void OnRemoved(params ServiceRouteEventArgs[] args)
        {
            if (_removed == null)
                return;

            foreach (var arg in args)
                _removed(this, arg);
        }


        /// <summary>
        /// 獲取所有可用的服務路由信息。
        /// </summary>
        /// <returns>服務路由集合。</returns>
        public async Task<IEnumerable<ServiceRoute>> GetRoutesAsync()
        {
            await EnterRoutes();
            return _routes;
        }

        /// <summary>
        /// 清空所有的服務路由。
        /// </summary>
        /// <returns>一個任務。</returns>
        public async Task ClearAsync()
        {
            if (_logger.IsEnabled(LogLevel.Information))
                _logger.LogInformation("準備清空所有路由配置。");
            var zooKeepers = await _zookeeperClientProvider.GetZooKeepers();
            foreach (var zooKeeper in zooKeepers)
            {
                var path = _configInfo.RoutePath;
                var childrens = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);

                var index = 0;
                while (childrens.Count() > 1)
                {
                    var nodePath = "/" + string.Join("/", childrens);

                    if (await zooKeeper.existsAsync(nodePath) != null)
                    {
                        var result = await zooKeeper.getChildrenAsync(nodePath);
                        if (result?.Children != null)
                        {
                            foreach (var child in result.Children)
                            {
                                var childPath = $"{nodePath}/{child}";
                                if (_logger.IsEnabled(LogLevel.Debug))
                                    _logger.LogDebug($"準備刪除:{childPath}。");
                                await zooKeeper.deleteAsync(childPath);
                            }
                        }
                        if (_logger.IsEnabled(LogLevel.Debug))
                            _logger.LogDebug($"準備刪除:{nodePath}。");
                        await zooKeeper.deleteAsync(nodePath);
                    }
                    index++;
                    childrens = childrens.Take(childrens.Length - index).ToArray();
                }
                if (_logger.IsEnabled(LogLevel.Information))
                    _logger.LogInformation("路由配置清空完成。");
            }
        }

        /// <summary>
        /// 設置服務路由。
        /// </summary>
        /// <param name="routes">服務路由集合。</param>
        /// <returns>一個任務。</returns>
        public async Task SetRoutesAsync(IEnumerable<ServiceRoute> routes)
        {
            var hostAddr = NetUtils.GetHostAddress();
            var serviceRoutes = await GetRoutes(routes.Select(p => p.serviceRouteDescriptor.Id));
            if (serviceRoutes.Count() > 0)
            {
                foreach (var route in routes)
                {
                    var serviceRoute = serviceRoutes.Where(p => p.serviceRouteDescriptor.Id == route.serviceRouteDescriptor.Id).FirstOrDefault();
                    if (serviceRoute != null)
                    {
                        var addresses = serviceRoute.Address.Concat(
                          route.Address.Except(serviceRoute.Address)).ToList();

                        foreach (var address in route.Address)
                        {
                            addresses.Remove(addresses.Where(p => p.ToString() == address.ToString()).FirstOrDefault());
                            addresses.Add(address);
                        }
                        route.Address = addresses;
                    }
                }
            }
            await RemoveExceptRoutesAsync(routes, hostAddr);

            if (_logger.IsEnabled(LogLevel.Information))
                _logger.LogInformation("準備添加服務路由。");
            var zooKeepers = await _zookeeperClientProvider.GetZooKeepers();
            foreach (var zooKeeper in zooKeepers)
            {
                await CreateSubdirectory(zooKeeper, _configInfo.RoutePath);

                var path = _configInfo.RoutePath;
                if (!path.EndsWith("/"))
                    path += "/";

                routes = routes.ToArray();

                foreach (var serviceRoute in routes)
                {
                    var nodePath = $"{path}{serviceRoute.serviceRouteDescriptor.Id}";
                    var nodeData = _serializer.Serialize(serviceRoute);
                    if (await zooKeeper.existsAsync(nodePath) == null)
                    {
                        if (_logger.IsEnabled(LogLevel.Debug))
                            _logger.LogDebug($"節點:{nodePath}不存在將進行創建。");

                        await zooKeeper.createAsync(nodePath, nodeData, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                    }
                    else
                    {
                        if (_logger.IsEnabled(LogLevel.Debug))
                            _logger.LogDebug($"將更新節點:{nodePath}的數據。");

                        var onlineData = (await zooKeeper.getDataAsync(nodePath)).Data;
                        if (!DataEquals(nodeData, onlineData))
                            await zooKeeper.setDataAsync(nodePath, nodeData);
                    }
                }
                if (_logger.IsEnabled(LogLevel.Information))
                    _logger.LogInformation("服務路由添加成功。");
            }
        }

        public async Task RemveAddressAsync(IEnumerable<string> Address)
        {
            var routes = await GetRoutesAsync();
            foreach (var route in routes)
            {
                route.Address = route.Address.Except(Address);
            }
            await SetRoutesAsync(routes);
        }

        private async Task RemoveExceptRoutesAsync(IEnumerable<ServiceRoute> routes, string hostAddr)
        {
            var path = _configInfo.RoutePath;
            if (!path.EndsWith("/"))
                path += "/";
            routes = routes.ToArray();
            var zooKeepers = await _zookeeperClientProvider.GetZooKeepers();
            foreach (var zooKeeper in zooKeepers)
            {
                if (_routes != null)
                {
                    var oldRouteIds = _routes.Select(i => i.serviceRouteDescriptor.Id).ToArray();
                    var newRouteIds = routes.Select(i => i.serviceRouteDescriptor.Id).ToArray();
                    var deletedRouteIds = oldRouteIds.Except(newRouteIds).ToArray();
                    foreach (var deletedRouteId in deletedRouteIds)
                    {
                        var addresses = _routes.Where(p => p.serviceRouteDescriptor.Id == deletedRouteId).Select(p => p.Address).FirstOrDefault();
                        if (addresses.Contains(hostAddr))
                        {
                            var nodePath = $"{path}{deletedRouteId}";
                            await zooKeeper.deleteAsync(nodePath);
                        }
                    }
                }
            }
        }

        private async Task CreateSubdirectory(ZooKeeper zooKeeper, string path)
        {
            if (await zooKeeper.existsAsync(path) != null)
                return;

            if (_logger.IsEnabled(LogLevel.Information))
                _logger.LogInformation($"節點{path}不存在,將進行創建。");

            var childrens = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
            var nodePath = "/";

            foreach (var children in childrens)
            {
                nodePath += children;
                if (await zooKeeper.existsAsync(nodePath) == null)
                {
                    await zooKeeper.createAsync(nodePath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                }
                nodePath += "/";
            }
        }

        private async Task<ServiceRoute> GetRoute(byte[] data)
        {
            if (_logger.IsEnabled(LogLevel.Debug))
                _logger.LogDebug($"準備轉換服務路由,配置內容:{Encoding.UTF8.GetString(data)}。");

            if (data == null)
                return null;

            return await Task.Run(() =>
            {
                return _serializer.Deserialize<ServiceRoute>(data);
            });
        }

        private async Task<ServiceRoute> GetRoute(string path)
        {
            ServiceRoute result = null;
            var zooKeeper = await GetZooKeeper();
            var watcher = new NodeMonitorWatcher(GetZooKeeper(), path,
                 async (oldData, newData) => await NodeChange(oldData, newData));
            if (await zooKeeper.existsAsync(path) != null)
            {
                var data = (await zooKeeper.getDataAsync(path, watcher)).Data;
                watcher.SetCurrentData(data);
                result = await GetRoute(data);
            }
            return result;
        }

        private async Task<ServiceRoute[]> GetRoutes(IEnumerable<string> childrens)
        {
            var rootPath = _configInfo.RoutePath;
            if (!rootPath.EndsWith("/"))
                rootPath += "/";

            childrens = childrens.ToArray();
            var routes = new List<ServiceRoute>(childrens.Count());

            foreach (var children in childrens)
            {
                if (_logger.IsEnabled(LogLevel.Debug))
                    _logger.LogDebug($"準備從節點:{children}中獲取路由信息。");

                var nodePath = $"{rootPath}{children}";
                var route = await GetRoute(nodePath);
                if (route != null)
                    routes.Add(route);
            }

            return routes.ToArray();
        }

        private async Task EnterRoutes()
        {
            if (_routes != null)
                return;
            var zooKeeper = await GetZooKeeper();
            var watcher = new ChildrenMonitorWatcher(GetZooKeeper(), _configInfo.RoutePath,
             async (oldChildrens, newChildrens) => await ChildrenChange(oldChildrens, newChildrens));
            if (await zooKeeper.existsAsync(_configInfo.RoutePath, watcher) != null)
            {
                var result = await zooKeeper.getChildrenAsync(_configInfo.RoutePath, watcher);
                var childrens = result.Children.ToArray();
                watcher.SetCurrentData(childrens);
                _routes = await GetRoutes(childrens);
            }
            else
            {
                if (_logger.IsEnabled(LogLevel.Warning))
                    _logger.LogWarning($"無法獲取路由信息,因為節點:{_configInfo.RoutePath},不存在。");
                _routes = new ServiceRoute[0];
            }
        }

        private static bool DataEquals(IReadOnlyList<byte> data1, IReadOnlyList<byte> data2)
        {
            if (data1.Count != data2.Count)
                return false;
            for (var i = 0; i < data1.Count; i++)
            {
                var b1 = data1[i];
                var b2 = data2[i];
                if (b1 != b2)
                    return false;
            }
            return true;
        }

        public async Task NodeChange(byte[] oldData, byte[] newData)
        {
            if (DataEquals(oldData, newData))
                return;

            var newRoute = await GetRoute(newData);
            //得到舊的路由。
            var oldRoute = _routes.FirstOrDefault(i => i.serviceRouteDescriptor.Id == newRoute.serviceRouteDescriptor.Id);

            lock (_routes)
            {
                //刪除舊路由,並添加上新的路由。
                _routes =
                    _routes
                        .Where(i => i.serviceRouteDescriptor.Id != newRoute.serviceRouteDescriptor.Id)
                        .Concat(new[] { newRoute }).ToArray();
            }

            //觸發路由變更事件。
            OnChanged(new ServiceRouteChangedEventArgs(newRoute, oldRoute));
        }

        public async Task ChildrenChange(string[] oldChildrens, string[] newChildrens)
        {
            if (_logger.IsEnabled(LogLevel.Debug))
                _logger.LogDebug($"最新的節點信息:{string.Join(",", newChildrens)}");

            if (_logger.IsEnabled(LogLevel.Debug))
                _logger.LogDebug($"舊的節點信息:{string.Join(",", oldChildrens)}");

            //計算出已被刪除的節點。
            var deletedChildrens = oldChildrens.Except(newChildrens).ToArray();
            //計算出新增的節點。
            var createdChildrens = newChildrens.Except(oldChildrens).ToArray();

            if (_logger.IsEnabled(LogLevel.Debug))
                _logger.LogDebug($"需要被刪除的路由節點:{string.Join(",", deletedChildrens)}");
            if (_logger.IsEnabled(LogLevel.Debug))
                _logger.LogDebug($"需要被添加的路由節點:{string.Join(",", createdChildrens)}");

            //獲取新增的路由信息。
            var newRoutes = (await GetRoutes(createdChildrens)).ToArray();

            var routes = _routes.ToArray();
            lock (_routes)
            {
                _routes = _routes
                    //刪除無效的節點路由。
                    .Where(i => !deletedChildrens.Contains(i.serviceRouteDescriptor.Id))
                    //連接上新的路由。
                    .Concat(newRoutes)
                    .ToArray();
            }
            //需要刪除的路由集合。
            var deletedRoutes = routes.Where(i => deletedChildrens.Contains(i.serviceRouteDescriptor.Id)).ToArray();
            //觸發刪除事件。
            OnRemoved(deletedRoutes.Select(route => new ServiceRouteEventArgs(route)).ToArray());

            //觸發路由被創建事件。
            OnCreated(newRoutes.Select(route => new ServiceRouteEventArgs(route)).ToArray());

            if (_logger.IsEnabled(LogLevel.Information))
                _logger.LogInformation("路由數據更新成功。");
        }


        /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
        public void Dispose()
        {
        }

        private async Task<ZooKeeper> GetZooKeeper()
        {
            return await _zookeeperClientProvider.GetZooKeeper();
        }

    }
ZooKeepe
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ## 業務需求 1. 人-項目關係 一個人可以屬於多個項目,一個項目可以有多個人加入,通知的時候,可以通知項目內的所有人,也可以通知部分人或者某個責任人。 2. 登錄互斥 同一個人不允許登錄兩次(不同瀏覽器或者不同電腦登),後面登錄的會將前面登錄的人擠下線。 3. 聊天 可以私聊、也可以創建群聊、上 ...
  • Signalr是以Group、Connect為核心來進行推送,比如,給某個組、某個連接來推送,但實際場景中,核心應該是某個組、某個人;然而一個人可以對應多個連接(瀏覽器多個tab頁);本節就來介紹下自行管理人、組、連接這些關係 由於signalr連接的時候不那麼方便附帶header和cookie(因 ...
  • 在實際的系統中,可能需要多台機器部署;然而,Signalr的連接信息是跟站點走的,舉個例子 推送系統部署了A、B兩個伺服器,張三訪問A伺服器,李四訪問B伺服器,當張三通過A伺服器向李四推送的時候,A伺服器上是找不到李四的連接信息的,自然也就推送不過了,這個時候就需要有一個統一協調的玩意,signal ...
  • ## MessagePack基礎介紹 Signalr預設使用的是json形式傳遞數據,但是signalr提供了靈活的擴展,支持MessagePack形式序列化數據,以增加性能降低網路傳輸的效果,極大的提高響應速度。 先看一個MessagePack自定義序列化的例子,以一個自定義的實體對象為例,可以使 ...
  • ## 強類型的優缺點 - 優點 強類型的Hub可以避免魔法函數名,相比弱類型更容易維護和發現問題,直接上代碼 - 缺點 特麽的得多些好幾行代碼 ## 代碼 ### 介面定義 ``` C# /// /// 服務端介面 /// public interface IServerNotifyHub { } ...
  • 因為將signalr作為單獨的站點,此處需要建立兩個項目,一個專門用於signalr作為推送項目,一個客戶端(實際的業務項目) ## 基礎知識速覽 ### Clients對象屬性 | 屬性 | 描述 | : | : | All | 在所有連接的客戶端上調用方法 | Caller | 在調用集線器方法 ...
  • 構建ML模型的步驟 現在我們已經看瞭解到了一些ML應用程式的例子,問題是,我們如何構建這樣的ML應用程式和系統? 下圖總結了我們使用ML開發應用程式的方法,我們將在下麵更詳細地討論這個問題: 如上圖所示,建立學習模型的步驟如下: 問題定義:任何項目的第一步不僅是理解我們想要解決的問題,也定義了我們如 ...
  • ## 介紹 ASP.NET Core SignalR 是一個開源代碼庫,它簡化了嚮應用添加實時 Web 功能的過程。 實時 Web 功能使伺服器端代碼能夠即時將內容推送到客戶端。 SignalR 的適用對象: - 需要來自伺服器的高頻率更新的應用。 例如:游戲、社交網路、投票、拍賣、地圖和 GPS ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...