zookeeper


zookeeper

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 for

5.伪集群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;
    }
    
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值