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包

[html] view plain copy
  1. <dependency>    
  2.       <groupId>org.apache.zookeeper</groupId>    
  3.       <artifactId>zookeeper</artifactId>    
  4.       <version>3.4.6</version>    
  5. </dependency>  
[java] view plain copy
  1. //创建一个Zookeeper实例,第一个参数为目标服务器地址和端口,第二个参数为Session超时时间,第三个为节点变化时的回调方法  
  2. ZooKeeper zk = new ZooKeeper("127.0.0.1:2181"500000,new Watcher() {  
  3.            // 监控所有被触发的事件  
  4.              public void process(WatchedEvent event) {  
  5.            //dosomething  
  6.            }  
  7.       });  
  8.   
  9. //创建一个节点root,数据是roodata,不进行ACL权限控制,节点为永久性的(即客户端shutdown了也不会消失)  
  10. zk.create("/root""roodata".getBytes(),Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);  
  11.   
  12.   
  13. //在root下面创建一个childone znode,数据为childone,不进行ACL权限控制,节点为永久性的  
  14. zk.create("/root/childone","childone".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);  
  15.   
  16.   
  17. //取得/root节点下的子节点名称,返回List<String>  
  18. zk.getChildren("/root",true);  
  19.   
  20.   
  21. //取得/root/childone节点下的数据,返回byte[]  
  22. zk.getData("/root/childone"truenull);  
  23.   
  24.   
  25. //修改节点/root/childone下的数据,第三个参数为版本,如果是-1,那会无视被修改的数据版本,直接改掉  
  26. zk.setData("/root/childone","childonemodify".getBytes(), -1);  
  27.   
  28.   
  29. //删除/root/childone这个节点,第二个参数为版本,-1的话直接删除,无视版本  
  30. zk.delete("/root/childone", -1);  
  31.         
  32. //关闭session  
  33. 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的时候,会报错,因为是集群,第三个还没启动,前两个会试着与第三个通讯,
都起来了就好了。

[java] view plain copy
  1. public static void main(String[] args) throws Exception {  
  2.     ZooKeeper zk = new ZooKeeper("127.0.0.1:3181,127.0.0.1:3182,127.0.0.1:3183"30000null);  
  3.     Thread.sleep(3000);  
  4.     zk.create("/testccc""2016-11-04 15:16:06".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);  
  5.     byte[] data = zk.getData("/testccc"nullnull);  
  6.     System.out.println(new String(data));  
  7.     zk.close();  
  8. }  

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

[java] view plain copy
  1. import java.util.concurrent.CountDownLatch;  
  2.   
  3. import org.apache.zookeeper.CreateMode;  
  4. import org.apache.zookeeper.WatchedEvent;  
  5. import org.apache.zookeeper.Watcher;  
  6. import org.apache.zookeeper.Watcher.Event.KeeperState;  
  7. import org.apache.zookeeper.ZooDefs;  
  8. import org.apache.zookeeper.ZooKeeper;  
  9. import org.apache.zookeeper.ZooKeeper.States;  
  10. import org.slf4j.Logger;  
  11. import org.slf4j.LoggerFactory;  
  12.   
  13. /** 
  14.  * 同步化的zk连接 
  15.  */  
  16. public class ZKConnection {  
  17.     private static final Logger LOG = LoggerFactory.getLogger(ZKConnection.class);  
  18.   
  19.     private static volatile String REDIS_HOST_LIST_INIT = "localhost";//zk zk_node_pathname节点里的初始化数据  
  20.     private static volatile String REDIS_HOST_LIST = "";//zk zk_node_pathname节点里的数据  
  21.       
  22.     private static ZooKeeper zk = null;  
  23.     public static final String zk_node_pathname = "/redis";//zk节点路径名  
  24.     public static final String zk_url = "localhost";//zk hostinfo  
  25.   
  26.     //test  
  27.     public static void main(String[] args) throws Exception {  
  28.         init();  
  29.         ZooKeeper zk = ZKConnection.zk();  
  30.         byte[] data = zk.getData(zk_node_pathname, nullnull);  
  31.         System.out.println("getData:" + new String(data));  
  32.         System.out.println("host_list:" + REDIS_HOST_LIST);  
  33.     }  
  34.       
  35.     //项目启动时调用,初始化  
  36.     public static void init() throws Exception {  
  37.         zk = ZKConnection.zk();  
  38.         if(zk.exists(zk_node_pathname, false) == null){  
  39.             zk.create(zk_node_pathname, REDIS_HOST_LIST_INIT.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);  
  40.         }  
  41.         byte[] data = zk.getData(zk_node_pathname, new DataWatcher(), null);  
  42.         //设置redis的配置  
  43.         setREDIS_HOST_LIST(new String(data));  
  44.     }  
  45.       
  46.     public static synchronized ZooKeeper zk() {  
  47.         if (zk==null || !zk.getState().isConnected()) {  
  48.             try {  
  49.                 createConnection();//创建zk连接  
  50.             } catch (Exception e) {  
  51.                 return null;  
  52.             }  
  53.         }  
  54.         return zk;  
  55.     }  
  56.   
  57.     /** 
  58.      * 创建zk连接 
  59.      * @throws Exception 
  60.      */  
  61.     private static void createConnection() throws Exception {  
  62.                //ZooKeeper创建连接时是异步的,这里使用CountDownLatch计数器保证获取到连接           
  63.                CountDownLatch connectedLatch = new CountDownLatch(1);  
  64.         Watcher watcher = new ConnectedWatcher(connectedLatch);  
  65.         zk = new ZooKeeper(zk_url, 60000, watcher);  
  66.         //String auth = "zk_username:zk_password";  
  67.         //zk.addAuthInfo("digest", auth.getBytes());  
  68.         if(States.CONNECTING == zk.getState()){  
  69.             connectedLatch.await();  
  70.         }  
  71.     }  
  72.   
  73.     //两个Watcher  
  74.     //Connected(zk连接) Watcher  
  75.     static class ConnectedWatcher implements Watcher {  
  76.           
  77.         private CountDownLatch connectedLatch;  
  78.         ConnectedWatcher(CountDownLatch connectedLatch) {  
  79.             this.connectedLatch = connectedLatch;  
  80.         }  
  81.   
  82.         @Override  
  83.         public void process(WatchedEvent event) {  
  84.             if (event.getState() == KeeperState.SyncConnected) {  
  85.                 connectedLatch.countDown();//计数器减一  
  86.             } else if (event.getState() == KeeperState.Expired) {  
  87.                 // 处理断线重连问题  
  88.                 if(zk != null){  
  89.                     try {  
  90.                         zk.close();  
  91.                     } catch (InterruptedException e) {  
  92.                         LOG.error("Can't close the expired connection!", e);  
  93.                     }  
  94.                 }  
  95.                 LOG.info("*******Expired reReloading********");  
  96.                 try {  
  97.                     init();  
  98.                 } catch (Exception e) {  
  99.                     LOG.error(e.getMessage());  
  100.                 }  
  101.             }  
  102.         }  
  103.     }  
  104.       
  105.     //Data(节点值变化) Watcher  
  106.     static class DataWatcher implements Watcher {  
  107.         private Logger LOG = LoggerFactory.getLogger(DataWatcher.class);  
  108.         @Override  
  109.         public void process(WatchedEvent event) {  
  110.             try {  
  111.                 if (event.getType() == Event.EventType.NodeDataChanged) {  
  112.                                        //<strong>this:Watcher通知是一次性的,当一次数据修改,通知客户端,客户端再次注册watch</strong>     
  113.                     byte[] data = ZKConnection.zk().getData(event.getPath(), thisnull);  
  114.                     if (data!=null && data.length>0) {  
  115.                         String result = new String(data);  
  116.                         ZKConnection.setREDIS_HOST_LIST(result);  
  117.                         LOG.info("Boot watcher has get the json: " + result);  
  118.                     }  
  119.                 }  
  120.             } catch (Exception e) {  
  121.                 LOG.error(e.getMessage());  
  122.             }  
  123.         }  
  124.     }  
  125.    
  126.     public static void setREDIS_HOST_LIST(String redis_host_list){  
  127.         synchronized (REDIS_HOST_LIST){  
  128.             REDIS_HOST_LIST = redis_host_list;  
  129.         }  
  130.     }  
  131.     public static String getREDIS_HostList(){  
  132.         return REDIS_HOST_LIST;  
  133.     }  
  134.       


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值