前言:马上要过年了,祝大家新年快乐!在过年回家前分享一篇关于Zookeeper的文章,我们都知道现在微服务盛行,大数据、分布式系统中经常会使用到Zookeeper,它是微服务、分布式系统中必不可少的分布式协调框架。它的作用体现在分布式系统中解决了配置中心的问题,以及解决了在分布式环境中不同进程之间争夺资源的问题,也就是分布式锁的功能以及分布式消息队列功能等等。所以在微服务的环境中Zookeeper是现在很多公司首选的分布式协调框架,包括我之前的公司也在使用Zookeeper。说了这么多,没别的就是想说一下Zookeeper的重要性,废话不多说,进入正题。本篇博客只是演示在.Net Core 环境中如何使用Zookeeper组件进行基本的增删改查和一些注意的要点,如果对Zookeeper还不是太了解的话,建议认认真真、仔仔细细地阅读该文章:http://www.cnblogs.com/sunddenly/p/4033574.html   否则可能下面演示的你会看不懂。

一、Zookeeper基本概念快速介绍

概念:

Zookeeper是一个开源的分布式协调框架,它具有高性能 、高可用的特点,同时具有严格的顺序访问控制能力(主要是写操作的严格顺序性),基于对ZAB(Zookeeper原子消息广播协议)的实现,它能够很好的保证分布式环境下的数据一致性。也正是基于这样的特征,使得Zookeeper称为解决分布式数据一致性问题的利器,Zookeeper由两部分组成:Zookeeper服务端和客户端。

特点:

  • 全局一致性:每个server保存一份相同的数据副本,client无论链接哪个server,展示的数据都是一致的,这是最重要的特征。
  • 可靠性:如果消息其中一台服务器接受,那么将被所有的服务器接受。
  • 顺序性:包括全局有序性和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有server上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。
  • 数据更新原子性:一次数据更新要么成功,要么失败,不存在中间状态。
  • 实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失败的信息。

数据结构:

图片来源:(https://www.cnblogs.com/xums/p/7074008.html)

  • Zookeeper的数据结构模型采用类似于文件系统的树结构。树上的每个节点称为ZNode,而每个节点都可能有一个或者多个子节点。ZNode的节点路径标识方式是由一系列斜杠"/"进行分割的路径表示,必须是绝对路径。既可以向ZNode节点写入、修改和读取数据,也可以创建、删除ZNode节点或ZNode节点下的子节点。
  • 值的注意的是,Zookeeper的设计目标不是传统的数据库存储或大数据对象存储,而是协同数据的存储,因此在实现的时候,ZNode存储的数据大小不应该超过1MB。另外,每一个节点都有一个ACL(访问控制列表),据此控制该节点的访问权限。
  • ZNode数据节点是有生命周期的,其生命周期的长短取决于数据节点的节点类型。节点类型共有四种:持久节点、持久顺序节点、临时节点、临时顺序节点

好了,基本的概念就聊到这里,先有一个印象,如果需要详细的学习,建议认认真真阅读这篇博客:http://www.cnblogs.com/sunddenly/p/4033574.html,下面就开始演示基本的api操作。

二、ASP.Net Core 中使用ZooKeeper

首先,添加下面的依赖包:

新建一个.Net Core的控制台应用:

Zookeeper的服务端使用的是张辉清老师新书《中小研发团队架构实践》里面的服务,我这里不再安装Zookeeper服务端,只是介绍一下Zookeeper的目录结构

  • Zookeeper目录介绍

(1)bin:主要的一些运行命令

(2)conf:存放配置文件,其中我们需要修改zk.cfg

(3)contrib:附加的一些功能

(4)dist-maven:mvn编译后的目录

(5)docs:文档

(6)lib:需要依赖的jar包

配置文件zk.cfg文件内容介绍(单机版)

(1)trickTime:用于计算的时间单元,比如session超时:N*trickTime

(2)initLimit:用于集群,允许从节点链接并同步到master节点的初始化链接时间,以trickTime的倍数来表示

(3)syncLimit:用于集群,master主节点与从节点之间发送消息,请求和应答时间长度(心跳机制)

(4)dataDir:必须配置

(5)dataLogDir:日志目录,如果不配置会和dataDir公用

(6)clientPort:链接服务器的端口,默认是2181

好了就介绍到这里,下面让我会演示关于Zookeeper  API的各种操作。

  • 如何连接Zookeeper的服务端

(1)代码如下:

using org.apache.zookeeper;
using org.apache.zookeeper.data;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static org.apache.zookeeper.Watcher.Event; namespace ZookeeperNetCore
{
public class ZookeeperClient
{
public ZooKeeper ZK { get; set; } // 配置项
public string QueryPath { get; set; }= "/Configuration";
//节点状态信息
public Stat Stat { get; set; } // 配置数据
public byte[] ConfigData { get; set; } = null; public ZookeeperClient(string serviceAddress, int timeout)
{
ZK = new ZooKeeper(serviceAddress, timeout, new ConfigServiceWatcher(this)); Console.WriteLine("客户端开始连接zookeeper服务器...");
Console.WriteLine($"连接状态:{ZK.getState()}");
Thread.Sleep(1000);//注意:为什么要加上这行代码,如果不加会出现什么问题
Console.WriteLine($"连接状态:{ZK.getState()}");
} // 读取节点的配置数据
public async Task<string> ReadConfigDataAsync()
{
if (this.ZK == null)
{
return string.Empty;
} var stat = await ZK.existsAsync(QueryPath, true); if (stat == null)
{
return string.Empty;
} this.Stat = stat; var dataResult = await ZK.getDataAsync(QueryPath, true); return Encoding.UTF8.GetString(dataResult.Data);
} public class ConfigServiceWatcher : Watcher
{
private ZookeeperClient _cs = null; public ConfigServiceWatcher(ZookeeperClient cs)
{
_cs = cs;
} public override async Task process(WatchedEvent @event)
{
Console.WriteLine($"Zookeeper链接成功:{@event.getState() == KeeperState.SyncConnected}"); if (@event.get_Type() == EventType.NodeDataChanged)
{
var data = await _cs.ReadConfigDataAsync(); Console.WriteLine("{0}收到修改此节点【{1}】值的通知,其值已被改为【{2}】。", Environment.NewLine, _cs.QueryPath, data);
}
}
} }
}

解释:

首先,我们来看看创建Zookeeper对象时,应该注意的问题:

Zookeeper的构造函数参数解释如下:

客户端和zk服务端链接是一个异步的过程,当连接成功后后,客户端会收的一个watch通知,就是调用回调函数:ConfigServiceWatcher.process(WatchedEvent @event)注意这个类ConfigServiceWatcher必须要继承Watcher,重写 process(WatchedEvent @event),所以就打印出了。关于Zookeeper的watcher后面会详细介绍,不明白的不要紧,后面会通过代码给大家演示。

(1)connectString:连接服务器的ip字符串,比如: "192.168.1.1:2181,192.168.1.2:2181,192.168.1.3:2181"可以是一个ip,也可以是多个ip,一个ip代表单机,多个ip代表集群,也可以在ip后加路径。

(2)sessionTimeout:超时时间,心跳收不到了,那就超时

(3)watcher:通知事件,如果有对应的事件触发,则会收到一个通知;如果不需要,那就设置为null,在上面的演示中,我们设置了一个watcher。

(4)canBeReadOnly:可读,当这个物理机节点断开后,还是可以读到数据的,只是不能写,此时数据被读取到的可能是旧数据,此处建议设置为false,不推荐使用。

(5)sessionId:会话的id

(6)sessionPasswd:会话密码 当会话丢失后,可以依据 sessionId 和 sessionPasswd 重新获取会话。

好了,基本的参数已经介绍完毕,那么,来解释一下为什么在创建Zookeeper对象时添加下面这句代码:

其实上面我已经解释了,由于客户端和zk服务端链接是一个异步的过程,需要一定的时间间隔,所以,如果不添加效果这样:

(2)zookeeper 恢复之前的会话连接演示

using org.apache.zookeeper;
using org.apache.zookeeper.data;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static org.apache.zookeeper.Watcher.Event; namespace ZookeeperNetCore
{
public class ZookeeperClient
{
public ZooKeeper ZK { get; set; } // 配置项
public string QueryPath { get; set; }= "/Configuration";
//节点状态信息
public Stat Stat { get; set; } // 配置数据
public byte[] ConfigData { get; set; } = null; public ZookeeperClient(string serviceAddress, int timeout)
{
ZK = new ZooKeeper(serviceAddress, timeout, new ConfigServiceWatcher(this)); } public ZookeeperClient(string serviceAddress, int timeout, long sessionId, byte[] sessionPasswd)
{
ZK = new ZooKeeper(serviceAddress, timeout, new ConfigServiceWatcher2(this), sessionId, sessionPasswd); } // 读取节点的配置数据
public async Task<string> ReadConfigDataAsync()
{
if (this.ZK == null)
{
return string.Empty;
} var stat = await ZK.existsAsync(QueryPath, true); if (stat == null)
{
return string.Empty;
} this.Stat = stat; var dataResult = await ZK.getDataAsync(QueryPath, true); return Encoding.UTF8.GetString(dataResult.Data);
} public class ConfigServiceWatcher : Watcher
{
private ZookeeperClient _cs = null; public ConfigServiceWatcher(ZookeeperClient cs)
{
_cs = cs;
} public override async Task process(WatchedEvent @event)
{
Console.WriteLine($"Zookeeper链接成功:{@event.getState() == KeeperState.SyncConnected}"); if (@event.get_Type() == EventType.NodeDataChanged)
{
var data = await _cs.ReadConfigDataAsync(); Console.WriteLine("{0}收到修改此节点【{1}】值的通知,其值已被改为【{2}】。", Environment.NewLine, _cs.QueryPath, data);
}
}
} public class ConfigServiceWatcher2 : Watcher
{
private ZookeeperClient _cs = null; public ConfigServiceWatcher2(ZookeeperClient cs)
{
_cs = cs;
} public override async Task process(WatchedEvent @event)
{
Console.WriteLine($"Zookeeper链接成功:{@event.getState() == KeeperState.SyncConnected}"); if (@event.get_Type() == EventType.NodeDataChanged)
{
var data = await _cs.ReadConfigDataAsync(); Console.WriteLine("{0}收到修改此节点【{1}】值的通知,其值已被改为【{2}】。", Environment.NewLine, _cs.QueryPath, data);
}
}
}
}
}

  • ZNode创建删除修改查询

代码:

using org.apache.zookeeper;
using org.apache.zookeeper.data;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static org.apache.zookeeper.Watcher.Event;
using static org.apache.zookeeper.ZooDefs; namespace ZookeeperNetCore
{
public class ZookeeperClient
{
public ZooKeeper ZK { get; set; } // 配置项
public string QueryPath { get; set; }= "/Configuration";
//节点状态信息
public Stat Stat { get; set; } // 配置数据
public byte[] ConfigData { get; set; } = null; public ZookeeperClient(string serviceAddress, int timeout)
{
ZK = new ZooKeeper(serviceAddress, timeout, new ConfigServiceWatcher(this)); } public ZookeeperClient(string serviceAddress, int timeout, long sessionId, byte[] sessionPasswd)
{
ZK = new ZooKeeper(serviceAddress, timeout, new ConfigServiceWatcher2(this), sessionId, sessionPasswd); } // 读取节点的配置数据
public async Task<string> ReadConfigDataAsync()
{
if (this.ZK == null)
{
return string.Empty;
} var stat = await ZK.existsAsync(QueryPath, true); if (stat == null)
{
return string.Empty;
} this.Stat = stat; var dataResult = await ZK.getDataAsync(QueryPath, true); return Encoding.UTF8.GetString(dataResult.Data);
} public class ConfigServiceWatcher : Watcher
{
private ZookeeperClient _cs = null; public ConfigServiceWatcher(ZookeeperClient cs)
{
_cs = cs;
} public override async Task process(WatchedEvent @event)
{
Console.WriteLine($"Zookeeper链接成功:{@event.getState() == KeeperState.SyncConnected}"); if (@event.get_Type() == EventType.NodeDataChanged)
{
var data = await _cs.ReadConfigDataAsync(); Console.WriteLine("{0}收到修改此节点【{1}】值的通知,其值已被改为【{2}】。", Environment.NewLine, _cs.QueryPath, data);
}
}
} public class ConfigServiceWatcher2 : Watcher
{
private ZookeeperClient _cs = null; public ConfigServiceWatcher2(ZookeeperClient cs)
{
_cs = cs;
} public override async Task process(WatchedEvent @event)
{
Console.WriteLine($"Zookeeper链接成功:{@event.getState() == KeeperState.SyncConnected}"); if (@event.get_Type() == EventType.NodeDataChanged)
{
var data = await _cs.ReadConfigDataAsync(); Console.WriteLine("{0}收到修改此节点【{1}】值的通知,其值已被改为【{2}】。", Environment.NewLine, _cs.QueryPath, data);
}
}
} // 关闭ZooKeeper连接
// 释放资源
public async Task Close()
{
if (this.ZK != null)
{
await ZK.closeAsync();
} this.ZK = null;
} }
}
using org.apache.zookeeper;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static org.apache.zookeeper.ZooDefs; namespace ZookeeperNetCore
{
class Program
{
public const int timeout = ;
static async Task Main(string[] args)
{
var conf = new ZookeeperClient("", timeout); try
{
conf.QueryPath = "/UserName"; Console.WriteLine("客户端开始连接zookeeper服务器...");
Console.WriteLine($"连接状态:{conf.ZK.getState()}");
Thread.Sleep();//注意:为什么要加上这行代码,如果不加会出现什么问题
Console.WriteLine($"连接状态:{conf.ZK.getState()}"); if (await conf.ZK.existsAsync(conf.QueryPath, false) == null)
{
conf.ConfigData = Encoding.Default.GetBytes("guozheng");
await conf.ZK.createAsync(conf.QueryPath, conf.ConfigData, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
} string configData = await conf.ReadConfigDataAsync();
Console.WriteLine("节点【{0}】目前的值为【{1}】。", conf.QueryPath, configData);
Console.ReadLine(); Random random = new Random((int)DateTime.Now.Ticks & 0x0000FFFF);
conf.ConfigData = Encoding.UTF8.GetBytes(string.Format("Mike_{0}", random.Next())); await conf.ZK.setDataAsync(conf.QueryPath, conf.ConfigData, -); Console.WriteLine("节点【{0}】的值已被修改为【{1}】。", conf.QueryPath, Encoding.UTF8.GetString(conf.ConfigData)); Console.ReadLine(); if (await conf.ZK.existsAsync(conf.QueryPath, false) != null)
{
await conf.ZK.deleteAsync(conf.QueryPath, -); Console.WriteLine("已删除此【{0}】节点。{1}", conf.QueryPath, Environment.NewLine);
} }
catch (Exception ex)
{
if (conf.ZK == null)
{
Console.WriteLine("已关闭ZooKeeper的连接。");
Console.ReadLine();
return;
} Console.WriteLine("抛出异常:{0}【{1}】。", Environment.NewLine, ex.ToString());
}
finally
{
await conf.Close();
Console.WriteLine("已关闭ZooKeeper的连接。");
Console.ReadLine();
} ////开始会话重连
//Console.WriteLine("开始会话重连..."); //var conf2 = new ZookeeperClient("", timeout, sessionId, sessionPassword); //Console.WriteLine(conf2.ZK.getSessionId());
//Console.WriteLine( Encoding.UTF8.GetString(conf2.ZK.getSessionPasswd())); //Console.WriteLine($"重新连接状态zkSession:{conf2.ZK.getState()}");
//Thread.Sleep(1000);//注意:为什么要加上这行代码,如果不加会出现什么问题
//Console.WriteLine($"重新连接状态zkSession:{conf2.ZK.getState()}"); Console.ReadKey();
}
}
}

解释:

关于异步创建节点的方法,是不支持子节点的递归创建,参数介绍:

(1)path:创建的路径

(2)data:存储的数据的byte[]

(3)acl:控制权限策略   Ids.OPEN_ACL_UNSAFE --> world:anyone:cdrwa      CREATOR_ALL_ACL --> auth:user:password:cdrwa

(4)createMode: 节点类型, 是一个枚举    PERSISTENT:持久节点   PERSISTENT_SEQUENTIAL:持久顺序节点   EPHEMERAL:临时节点   EPHEMERAL_SEQUENTIAL:临时顺序节点

关于上面参数引出来的知识点,需要几章来讲解,本篇文章先不介绍,后面会介绍。好了,关于.Net Core中使用Zookeeper的介绍就到这里,关于上面演示的结果,我先抛出一个问题,大家可以思考一下:为什么“Zookeeper链接成功:True”会输出多次?也就是我们下节要讨论的Zookeeper的watcher机制。时间到了,收拾行李,准备一下回家啦,先写到这里,祝大家新年快乐!希望对你有帮助,过完年来见!

三、总结

可能有些地方解释的不是太清楚,大家多多见谅,有些的不对的地方,希望能指正出来。

说明:演示代码里面使用的Zookeeper服务过一段时间能用,不能用的话,在评论区留言,后面用阿里云自己搭建一个。

代码地址:

https://github.com/guozheng007/ZookeeperNetCoreDemo

参考资料:

(1)张辉清:《中小研发团队架构实践》

(2) 风间影月:《ZooKeeper分布式专题与Dubbo微服务入门》

(3)sunddenly:http://www.cnblogs.com/sunddenly/p/4033574.html

作者:郭峥

出处:http://www.cnblogs.com/runningsmallguo/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

ASP.Net Core 中使用Zookeeper搭建分布式环境中的配置中心系列一:使用Zookeeper.Net组件演示基本的操作的更多相关文章

  1. Zookeeper和分布式环境中的假死脑裂问题(转)

    Zookeeper和分布式环境中的假死脑裂问题 最近和同事聊天无意间发现他们的系统也存在脑裂的问题.想想当初在我们的系统中为了解决脑裂花了非常大的功夫,现在和大家一起讨论下脑裂,假死等等这些问题和解决 ...

  2. ZooKeeper学习第五期--ZooKeeper管理分布式环境中的数据

    引言 本节本来是要介绍ZooKeeper的实现原理,但是ZooKeeper的原理比较复杂,它涉及到了paxos算法.Zab协议.通信协议等相关知识,理解起来比较抽象所以还需要借助一些应用场景,来帮我们 ...

  3. 【Zookeeper系列】ZooKeeper管理分布式环境中的数据(转)

    原文地址:https://www.cnblogs.com/sunddenly/p/4092654.html 引言 本节本来是要介绍ZooKeeper的实现原理,但是ZooKeeper的原理比较复杂,它 ...

  4. ZooKeeper管理分布式环境中的数据

    Reference: http://www.cnblogs.com/wuxl360/p/5817549.html 本节本来是要介绍ZooKeeper的实现原理,但是ZooKeeper的原理比较复杂,它 ...

  5. 分布式服务框架 Zookeeper — 管理分布式环境中的数据

    本节本来是要介绍ZooKeeper的实现原理,但是ZooKeeper的原理比较复杂,它涉及到了paxos算法.Zab协议.通信协议等相关知识,理解起来比较抽象所以还需要借助一些应用场景,来帮我们理解. ...

  6. ZooKeeper学习第五期--ZooKeeper管理分布式环境中的数据(转)

    转载来源:https://www.cnblogs.com/sunddenly/p/4092654.html 引言 本节本来是要介绍ZooKeeper的实现原理,但是ZooKeeper的原理比较复杂,它 ...

  7. 分布式服务框架 Zookeeper -- 管理分布式环境中的数据

    转自:http://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/index.html Zookeeper 分布式服务框架是 Apa ...

  8. 分布式服务框架 Zookeeper -- 管理分布式环境中的数据(转载)

    本文转载自:http://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/ Zookeeper 分布式服务框架是 Apache Had ...

  9. 分布式服务框架 Zookeeper -- 管理分布式环境中的数据--转载

    原文:http://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/ Zookeeper 分布式服务框架是 Apache Hadoop ...

随机推荐

  1. .NET Core 2.0

    下载 Visual Studio 2017 version 15.3 下载 .NET Core 2.0 下载 Visual Studio for Mac 微软今天发布了.NET Core 2.0 版本 ...

  2. python Kmeans算法解析

    一. 概述 首先需要先介绍一下无监督学习,所谓无监督学习,就是训练样本中的标记信息是位置的,目标是通过对无标记训练样本的学习来揭示数据的内在性质以及规律.通俗得说,就是根据数据的一些内在性质,找出其内 ...

  3. [20181206]关于一致性读取3.txt

    [20181206]关于一致性读取3.txt --//简单演示一致性读取以及如何读取undo重构数据块的.我不想转储对应的undo块,解析那些复杂的过程. 1.环境:SCOTT@book> @ ...

  4. c/c++ 智能指针 weak_ptr 使用

    智能指针 weak_ptr 使用 weak_ptr用途: 1,解决空悬指针问题 2,解决循环引用问题 weak_ptr特点:没有*操作和->操作 weak_ptr是不控制所指对象生存周期的智能指 ...

  5. python数据类型分类以及运算类型

    一.python数据类型 目录: 1.数字(整数.小数) 2.字符串(单引号.双引号.三引号) 3.元组 #元素确定之后不能修改 4.列表 #元素可以修改 5.集合  #不讲顺序,得到的结果没有重复元 ...

  6. VS快捷键失效问题

    VS作为宇宙最强IDE,为我们提供了强大的快捷键组合,熟练的使用这些快捷键能极大提高我们的编码效率,但是在我们实际使用的过程中经常会遇到某个快捷键组合失效的问题. 问题原因: 一般都是VS的快捷键与电 ...

  7. 【PAT】 B1006 换个格式输出整数

    超简单题 //直接将各位分开,分别用for循环输出 #include<stdio.h> int main(){ int num; scanf("%d",&num ...

  8. Tomcat结构

    Tomcat结构 Server(服务器) 服务器代表整个Tomcat容器. Tomcat提供了服务器接口的默认实现(很少由用户定制). Service(服务) 服务是位于服务器内部的中间组件,将一个或 ...

  9. C#基础知识之Sender

    /// <summary> /// sender就是事件发起者,e存储事件发起者的一些参数 /// 例如: /// private void button1_Click(object se ...

  10. sboot mybatis

    https://www.cnblogs.com/lspz/p/6723603.html spring.datasource.url=jdbc:mysql://10.46.52.205:3306/tes ...