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 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;
}
}