zookeeper是一个为分布式应用提供一致性服务的软件
1.zookeeper的数据模型
其数据模型有些像操作系统的文件结构,结构如下图所示
(1)每个节点在zookeeper中叫做znode,并且其有一个唯一的路径标识,如/SERVER2节点的标识就为/APP3/SERVER2
(2)znode可以有子znode,并且znode里可以存数据,但是EPHEMERAL类型的节点不能有子节点
(3)znode中的数据可以有多个版本,比如某一个路径下存有多个数据版本,那么查询这个路径下的数据就需要带上版本号。
(4)znode可以是临时节点,一旦创建这个 znode 的客户端与服务器失去联系,这个 znode 也将自动删除,Zookeeper 的客户端和服务器通信采用长连接方式,
每个客户端和服务器通过心跳来保持连接,这个连接状态称为 session,如果 znode 是临时节点,这个 session 失效,znode 也就删除了
(5)znode的目录名可以自动编号,如 App1 已经存在,再创建的话,将会自动命名为 App2(6)znode可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,
通过这个特性可以实现的功能包括配置的集中管理,集群管理,分布式锁等等。
2.ZooKeeper节点类型持久节点(PERSISTENT)、临时节点(EPHEMERAL),以及时序节点(SEQUENTIAL),具体在节点创建过程中,一般是组合使用,可以生成以下4种节点类型。
持久节点(PERSISTENT)
所谓持久节点,是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点——不会因为创建该节点的客户端会话失效而消失。
持久顺序节点(PERSISTENT_SEQUENTIAL)
这类节点的基本特性和上面的节点类型是一致的。额外的特性是,在ZK中,每个父节点会为他的第一级子节点维护一份时序,会记录每个子节点创建的先后顺序。基于这个特性,在创建子节点的时候,可以设置这个属性,那么在创建节点过程中,ZK会自动为给定节点名加上一个数字后缀,作为新的节点名。这个数字后缀的范围是整型的最大值。
临时节点(EPHEMERAL)
和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。
注意,这里提到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。
临时顺序节点(EPHEMERAL_SEQUENTIAL)
可以用来实现分布式锁
3.单机
------------------------------
安装/配置:
conf目录
复制zoo_sample.cfg文件,名称改为zoo.cfg,zookeeper启动时默认的配置文件名
参数说明:
tickTime: zookeeper中使用的基本时间单位, 毫秒值.
dataDir: 数据目录. 可以是任意目录.
dataLogDir: log目录, 同样可以是任意目录. 如果没有设置该参数, 将使用和dataDir相同的设置.
clientPort: 监听client连接的端口号.
启动(windows下):
bin目录
server:双击zkServer.cmd
client:zkCli.cmd
clent命令:
ls(查看当前节点的子节点),
ls2(查看当前子节点并能看到当前节点的日期、版本等信息) ,
create(创建一个节点) ,
get(得到一个节点,包含数据和更新次数等信息),
set(修改节点)
delete(删除一个节点)
4.java api
Zookeeper的使用主要是通过创建其jar包下的Zookeeper实例,并且调用其接口方法进行的,主要的操作就是对znode的增删改查操作,监听znode的变化以及处理。
maven引入jar包
- <dependency>
- <groupId>org.apache.zookeeper</groupId>
- <artifactId>zookeeper</artifactId>
- <version>3.4.6</version>
- </dependency>
- //创建一个Zookeeper实例,第一个参数为目标服务器地址和端口,第二个参数为Session超时时间,第三个为节点变化时的回调方法
- ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 500000,new Watcher() {
- // 监控所有被触发的事件
- public void process(WatchedEvent event) {
- //dosomething
- }
- });
- //创建一个节点root,数据是roodata,不进行ACL权限控制,节点为永久性的(即客户端shutdown了也不会消失)
- zk.create("/root", "roodata".getBytes(),Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
- //在root下面创建一个childone znode,数据为childone,不进行ACL权限控制,节点为永久性的
- zk.create("/root/childone","childone".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
- //取得/root节点下的子节点名称,返回List<String>
- zk.getChildren("/root",true);
- //取得/root/childone节点下的数据,返回byte[]
- zk.getData("/root/childone", true, null);
- //修改节点/root/childone下的数据,第三个参数为版本,如果是-1,那会无视被修改的数据版本,直接改掉
- zk.setData("/root/childone","childonemodify".getBytes(), -1);
- //删除/root/childone这个节点,第二个参数为版本,-1的话直接删除,无视版本
- zk.delete("/root/childone", -1);
- //关闭session
- zk.close();
注意:ZooKeeper创建连接时是异步的,可以使用CountDownLatch来保证创建好连接再使用(如下文的例子),
这里为方便测试,new ZooKeeper()创建连接后,Thread.sleep(2000)sleep一会再进行增删改查操作,否则可能报错
org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode = ConnectionLoss for5.伪集群zk windows单机伪集群
zk windows单机伪集群
1.复制三份zk:
zookeeper-3.4.5-1
zookeeper-3.4.5-2
zookeeper-3.4.5-3
2.修改配置文件conf/zoo.cfg:
#数据文件和日志文件路径
dataDir=E:/testZk/data3 #(data1、data2、data3)
dataLogDir=E:/testZk/log3 #(log1、log2、log3)
#修改端口,三个分别是:
clientPort=3181
clientPort=3182
clientPort=3183
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890
server.A=B:C:D:
A是一个数字,表示这个是第几号服务器;
B是这个服务器的ip地址;
C表示的是这个服务器与集群中的Leader服务器交换信息的端口;
D表示的是万一集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,
而这个端口就是用来执行选举时服务器相互通信的端口。
如果是伪集群的配置方式,由于B都是一样,所以不同的Zookeeper实例通信端口号不能一样,所以要给它们分配不同的端口号。
3.创建myid文件
在dataDir目录下创建一个myid文件,然后分别在myid文件中写入zoo.cfg文件的server.A中A的数值,在不同机器上的该文件中填写相应的值。
4.启动、测试
分别启动三个zk:
bin/zkServer.cmd 直接双击就行;
注意:cmd启动的时候,
zkServer.cmd 不要后面的“start”,否则会报错:
Invalid arguments, exiting abnormally
java.lang.NumberFormatException: For input string:
注意:启动前两个zk的时候,会报错,因为是集群,第三个还没启动,前两个会试着与第三个通讯,
都起来了就好了。
- public static void main(String[] args) throws Exception {
- ZooKeeper zk = new ZooKeeper("127.0.0.1:3181,127.0.0.1:3182,127.0.0.1:3183", 30000, null);
- Thread.sleep(3000);
- zk.create("/testccc", "2016-11-04 15:16:06".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
- byte[] data = zk.getData("/testccc", null, null);
- System.out.println(new String(data));
- zk.close();
- }
6.应用场景
配置管理
集群管理
分布式锁
参考:http://blackproof.iteye.com/blog/2039040
***********************************************************************************************************************
同步化的zk连接
zk节点数据为redis cluster的host port信息,redis增减机器时,会修改zk节点数据,
zk节点会通过DataWatcher修改ZKConnection.REDIS_HOST_LIST,
redis cluster创建redis连接时,连接url是ZKConnection.REDIS_HOST_LIST
- import java.util.concurrent.CountDownLatch;
- import org.apache.zookeeper.CreateMode;
- import org.apache.zookeeper.WatchedEvent;
- import org.apache.zookeeper.Watcher;
- import org.apache.zookeeper.Watcher.Event.KeeperState;
- import org.apache.zookeeper.ZooDefs;
- import org.apache.zookeeper.ZooKeeper;
- import org.apache.zookeeper.ZooKeeper.States;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- /**
- * 同步化的zk连接
- */
- public class ZKConnection {
- private static final Logger LOG = LoggerFactory.getLogger(ZKConnection.class);
- private static volatile String REDIS_HOST_LIST_INIT = "localhost";//zk zk_node_pathname节点里的初始化数据
- private static volatile String REDIS_HOST_LIST = "";//zk zk_node_pathname节点里的数据
- private static ZooKeeper zk = null;
- public static final String zk_node_pathname = "/redis";//zk节点路径名
- public static final String zk_url = "localhost";//zk hostinfo
- //test
- public static void main(String[] args) throws Exception {
- init();
- ZooKeeper zk = ZKConnection.zk();
- byte[] data = zk.getData(zk_node_pathname, null, null);
- System.out.println("getData:" + new String(data));
- System.out.println("host_list:" + REDIS_HOST_LIST);
- }
- //项目启动时调用,初始化
- public static void init() throws Exception {
- zk = ZKConnection.zk();
- if(zk.exists(zk_node_pathname, false) == null){
- zk.create(zk_node_pathname, REDIS_HOST_LIST_INIT.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
- }
- byte[] data = zk.getData(zk_node_pathname, new DataWatcher(), null);
- //设置redis的配置
- setREDIS_HOST_LIST(new String(data));
- }
- public static synchronized ZooKeeper zk() {
- if (zk==null || !zk.getState().isConnected()) {
- try {
- createConnection();//创建zk连接
- } catch (Exception e) {
- return null;
- }
- }
- return zk;
- }
- /**
- * 创建zk连接
- * @throws Exception
- */
- private static void createConnection() throws Exception {
- //ZooKeeper创建连接时是异步的,这里使用CountDownLatch计数器保证获取到连接
- CountDownLatch connectedLatch = new CountDownLatch(1);
- Watcher watcher = new ConnectedWatcher(connectedLatch);
- zk = new ZooKeeper(zk_url, 60000, watcher);
- //String auth = "zk_username:zk_password";
- //zk.addAuthInfo("digest", auth.getBytes());
- if(States.CONNECTING == zk.getState()){
- connectedLatch.await();
- }
- }
- //两个Watcher
- //Connected(zk连接) Watcher
- static class ConnectedWatcher implements Watcher {
- private CountDownLatch connectedLatch;
- ConnectedWatcher(CountDownLatch connectedLatch) {
- this.connectedLatch = connectedLatch;
- }
- @Override
- public void process(WatchedEvent event) {
- if (event.getState() == KeeperState.SyncConnected) {
- connectedLatch.countDown();//计数器减一
- } else if (event.getState() == KeeperState.Expired) {
- // 处理断线重连问题
- if(zk != null){
- try {
- zk.close();
- } catch (InterruptedException e) {
- LOG.error("Can't close the expired connection!", e);
- }
- }
- LOG.info("*******Expired reReloading********");
- try {
- init();
- } catch (Exception e) {
- LOG.error(e.getMessage());
- }
- }
- }
- }
- //Data(节点值变化) Watcher
- static class DataWatcher implements Watcher {
- private Logger LOG = LoggerFactory.getLogger(DataWatcher.class);
- @Override
- public void process(WatchedEvent event) {
- try {
- if (event.getType() == Event.EventType.NodeDataChanged) {
- //<strong>this:Watcher通知是一次性的,当一次数据修改,通知客户端,客户端再次注册watch</strong>
- byte[] data = ZKConnection.zk().getData(event.getPath(), this, null);
- if (data!=null && data.length>0) {
- String result = new String(data);
- ZKConnection.setREDIS_HOST_LIST(result);
- LOG.info("Boot watcher has get the json: " + result);
- }
- }
- } catch (Exception e) {
- LOG.error(e.getMessage());
- }
- }
- }
- public static void setREDIS_HOST_LIST(String redis_host_list){
- synchronized (REDIS_HOST_LIST){
- REDIS_HOST_LIST = redis_host_list;
- }
- }
- public static String getREDIS_HostList(){
- return REDIS_HOST_LIST;
- }
- }