zk 是一个开源的的分布式协调服务,由雅虎创建,基于google chubby.
提供 一个分布式数据一致性解决方案 简单的说,zookeeper=文件系统+通知机制。
zk 能做什么?
数据的 发布/订阅(配置中心:disconf) ,负载均衡( dubbo利用zookeeper机制实现负载均衡),命名服务,master选举(kafka,hadoop,hbase), 分布式队列
zk 的特性
顺序一致性:从同一个客户端发起的事务请求,最终会严格按照顺序应用到zookeeper中。
原子性:所有事务的请求处理结果在整个中的所有机器上的应用情况是一致的。也就是说 要么集群中的所以节点都成功应用了某一事务,要么全都不应用。
可靠性:一旦服务器成功应用某一事务数据,并且对客户端做去了响应,那么该数据在整个集群中一定是同步并且保留下来的。
实时性:一旦一个事务被重新成功应用,客户端就能够立即从服务器端读取到事务变更后的最新数据状态;(zk仅仅保证在一段时间内,接近实时)
zookeeper安装
单机环境安装
- 下载zookeeper的安装包
http://apache.fayea.com/zookeeper/stable/zookeeper-3.4.10.tar.gz
- 解压zookeeper
tar -zxvf zookeeper-3.4.10.tar.gz
2、cd 到 ZK_HOME/conf , copy一份zoo.cfg 备份配置文件
cp zoo_sample.cfg zoo.cfg
3、sh zkServer.sh 启动server
{start|start-foreground|stop|restart|status|upgrade|print-cmd}
4、sh zkCli.sh -server ip:port 启动client
集群环境
zookeeper集群, 包含三种角色: leader / follower /observer zookeeper一般是由 2n+1台服务器组成
leader
leader是zookeeper集群的核心。
- 事务请求的唯一调度者和处理者,保证集群事务处理的顺序性
- 集群内部各个服务器的调度者
follower
- 处理客户端非事务请求,以及转发事务请求给leader服务器
- 参与事务请求提议(proposal)的投票(客户端的一个事务请求,leader会发起一个提案,要求follower投票,需要半数服务器投票通过以后才能通知leader commit)
- 参与leader选举的投票
observer
observer 是一种特殊的zookeeper节点。可以帮助解决zookeeper的扩展性(如果大量客户端访问我们zookeeper集群,需要增加zookeeper集群机器数量。从而增加zookeeper集群的性能。 导致zookeeper写性能下降, zookeeper的数据变更需要半数以上服务器投票通过。造成网络消耗增加投票成本)
- observer不参与投票。 只接收投票结果。
- 不属于zookeeper的关键部位。
增加observer不影响集群中的事务处理能力,同时还能提升集群的非事务处理能力。
- 修改zoo.cfg
server.id=ip:port:port
server.1=192.168.11.129:2888:3181
server.2=192.168.11.135:2888:3181
server.3=192.168.111.136:2888:3181
id的取值范围: 1~255; 用id来标识该机器在集群中的机器序号
2888 表示follower节点与leader节点交换信息的端口号 可以自己定义
3181 如果leader节点挂掉了, 需要一个端口来重新选举。
2、zoo.cfg中有一个dataDir = /tmp/zookeeper
$dataDir/myid 添加一个myid文件。
在每一个服务器的dataDir目录下创建一个myid的文件,文件就一行数据,数据内容是每台机器对应的server ID的数字
3、启动服务
129/135/136
4、如果需要增加observer节点
zoo.cfg中 增加 ;peerType=observer
server.1=192.168.11.129:2888:3181
server.2=192.168.11.135:2888:3181
server.3=192.168.111.136:2888:3181:observer
zoo.cfg配置文件分析
tickTime=2000 zookeeper中最小的时间单位长度 (ms)
initLimit=10 follower节点启动后与leader节点完成数据同步的时间
syncLimit=5 leader节点和follower节点进行心跳检测的最大延时时间
dataDir=/tmp/zookeeper 表示zookeeper服务器存储快照文件的目录
dataLogDir 表示配置 zookeeper事务日志的存储路径,默认指定在dataDir目录下
clientPort 表示客户端和服务端建立连接的端口号: 2181
zookeeper中的一些概念
数据模型
Zookeeper维护一个类似文件系统的数据结构,每一个节点成为:Znode,是zk中的最小数据单元,每个znode上都可以保存数据和挂载子节点,从而构成一个层次化的属性结构。
节点特性
- 持久化节点:节点创建后会一直存在zookeeper服务器上,直到主动删除
- 持久化有序节点:每个节点都会为它的一级子节点维护一个顺序
- 临时节点 : 临时节点的生命周期和客户端的会话保持一致。当客户端会话失效,该节点自动清理
- 临时有序节点 : 在临时节点上多了一个顺序性特性
会话
zk客户端与zk服务器端的连接过程
watcher
zookeeper 提供了分布式数据发布和订阅,zookeeper允许客户端向服务器注册一个watcher监听。当服务端的节点触发指定事件的时候会触发watcher。服务器端会向客户端发送一个事件通知。
watcher 的通知是一次性的。一旦触发一次通知后,该watcher就失效
ACL
zookeeper提供控制节点访问权限的功能,用于有效的保证zookeeper中数据的安全性。避免应为误操作而导致系统出现重大事故。
CREATE/READ/WRITE/DELETER/ADMIN
zookeeper的命令操作
1. create [-s] [-e] path data acl
-s 表示节点是否有序
-e 表示是否为临时节点
默认情况下,是持久化节点
2. get path [watch]
获得指定 path的信息
3.set path data [version]
修改节点 path对应的data
version:版本号: 引入了乐观锁的概念。
乐观锁的概念 :数据库里面有一个 version 字段去控制数据行的版本号
4.delete path [version]
删除节点
节点的状态信息 status
cversion=0 子节点的版本号
aclVersion=0 表示acl的版本号。修改节点权限。
dataVersion=1 表示当前节点数据的版本号
cZxid 节点被创建时的事务id
mZxid 节点最后一次被更新的事务id
pZxid 当前接下子节点最后本更新的事务id
ctime 创建时间
mtime 修改时间
cZxid = 0x500000015
ctime = Sat Aug 05 20:48:26 CST 2017
mZxid = 0x500000016
mtime = Sat Aug 05 20:48:50 CST 2017
pZxid = 0x500000015
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0 创建临时节点的时候,会有一个sessionId 。 该值存储的就是这个sessionid。回话关闭时根据该ephemeralOwner的值 ,删除该临时节点。
dataLength = 3 数据值长度
numChildren = 0 子节点数
java API的使用
- 导入jar包
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.8</version>
</dependency>
- 具体见代码
public class ApiOperatorDemo implements Watcher{
private final static String CONNECTSTRING="192.168.11.129:2181,192.168.11.134:2181," +
"192.168.11.135:2181,192.168.11.136:2181";
private static CountDownLatch countDownLatch=new CountDownLatch(1);
private static ZooKeeper zookeeper;
private static Stat stat=new Stat();
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
zookeeper=new ZooKeeper(CONNECTSTRING, 5000, new ApiOperatorDemo());
countDownLatch.await();
ACL acl=new ACL(ZooDefs.Perms.ALL,new Id("ip","192.168.11.129"));
List<ACL> acls=new ArrayList<>();
acls.add(acl);
// zookeeper.create("/authTest","111".getBytes(),acls,CreateMode.PERSISTENT);
zookeeper.getData("/authTest",true,new Stat());
/* System.out.println(zookeeper.getState());
//创建节点
String result=zookeeper.create("/node1","123".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
zookeeper.getData("/node1",new ZkClientApiOperatorDemo(),stat); //增加一个
System.out.println("创建成功:"+result);
//修改数据
zookeeper.setData("/node1","mic123".getBytes(),-1);
Thread.sleep(2000);
//修改数据
zookeeper.setData("/node1","mic234".getBytes(),-1);
Thread.sleep(2000);
*//* //删除节点
zookeeper.delete("/mic/mic1",-1);
Thread.sleep(2000);*//*
//创建节点和子节点
String path="/node11";
zookeeper.create(path,"123".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
TimeUnit.SECONDS.sleep(1);
Stat stat=zookeeper.exists(path+"/node1",true);
if(stat==null){//表示节点不存在
zookeeper.create(path+"/node1","123".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
TimeUnit.SECONDS.sleep(1);
}
//修改子路径
zookeeper.setData(path+"/node1","mic123".getBytes(),-1);
TimeUnit.SECONDS.sleep(1);*/
//获取指定节点下的子节点
/* List<String> childrens=zookeeper.getChildren("/node",true);
System.out.println(childrens);*/
}
public void process(WatchedEvent watchedEvent) {
//如果当前的连接状态是连接成功的,那么通过计数器去控制
if(watchedEvent.getState()==Event.KeeperState.SyncConnected){
if(Event.EventType.None==watchedEvent.getType()&&null==watchedEvent.getPath()){
countDownLatch.countDown();
System.out.println(watchedEvent.getState()+"-->"+watchedEvent.getType());
}else if(watchedEvent.getType()== Event.EventType.NodeDataChanged){
try {
System.out.println("数据变更触发路径:"+watchedEvent.getPath()+"->改变后的值:"+
zookeeper.getData(watchedEvent.getPath(),true,stat));
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if(watchedEvent.getType()== Event.EventType.NodeChildrenChanged){//子节点的数据变化会触发
try {
System.out.println("子节点数据变更路径:"+watchedEvent.getPath()+"->节点的值:"+
zookeeper.getData(watchedEvent.getPath(),true,stat));
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if(watchedEvent.getType()== Event.EventType.NodeCreated){//创建子节点的时候会触发
try {
System.out.println("节点创建路径:"+watchedEvent.getPath()+"->节点的值:"+
zookeeper.getData(watchedEvent.getPath(),true,stat));
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if(watchedEvent.getType()== Event.EventType.NodeDeleted){//子节点删除会触发
System.out.println("节点删除路径:"+watchedEvent.getPath());
}
System.out.println(watchedEvent.getType());
}
}
}
权限控制模式:
schama:授权对象
ip:192.168.1.1
Digest:username:password
world:开放式的权限控制模式,数据节点的访问权限对所有用户开放。world:anyone
super:超级用户,可以对zookeeper上的数据节点进行操作
public class AuthControlDemo implements Watcher{
private final static String CONNECTSTRING="192.168.11.129:2181,192.168.11.134:2181," +
"192.168.11.135:2181,192.168.11.136:2181";
private static CountDownLatch countDownLatch=new CountDownLatch(1);
private static ZooKeeper zookeeper;
private static Stat stat=new Stat();
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
zookeeper=new ZooKeeper(CONNECTSTRING, 5000, new AuthControlDemo());
countDownLatch.await();
ACL acl=new ACL(ZooDefs.Perms.CREATE, new Id("digest","root:root"));
ACL acl2=new ACL(ZooDefs.Perms.CREATE, new Id("ip","192.168.1.1"));
List<ACL> acls=new ArrayList<>();
acls.add(acl);
acls.add(acl2);
zookeeper.create("/auth1","123".getBytes(),acls,CreateMode.PERSISTENT);
zookeeper.addAuthInfo("digest","root:root".getBytes());
zookeeper.create("/auth1","123".getBytes(), ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
zookeeper.create("/auth1/auth1-1","123".getBytes(), ZooDefs.Ids.CREATOR_ALL_ACL,CreateMode.EPHEMERAL);
ZooKeeper zooKeeper1=new ZooKeeper(CONNECTSTRING, 5000, new AuthControlDemo());
countDownLatch.await();
zooKeeper1.delete("/auth1",-1);
// acl (create /delete /admin /read/write)
//权限模式: ip/Digest(username:password)/world/super
}
public void process(WatchedEvent watchedEvent) {
//如果当前的连接状态是连接成功的,那么通过计数器去控制
if(watchedEvent.getState()==Event.KeeperState.SyncConnected){
if(Event.EventType.None==watchedEvent.getType()&&null==watchedEvent.getPath()){
countDownLatch.countDown();
System.out.println(watchedEvent.getState()+"-->"+watchedEvent.getType());
}
}
}
}
连接状态
KeeperStat.Expired 在一定时间内客户端没有收到服务器的通知, 则认为当前的会话已经过期了。
KeeperStat.Disconnected 断开连接的状态
KeeperStat.SyncConnected 客户端和服务器端在某一个节点上建立连接,并且完成一次version、zxid同步
KeeperStat.authFailed 授权失败
事件类型
NodeCreated 当节点被创建的时候,触发
NodeChildrenChanged 表示子节点被创建、被删除、子节点数据发生变化
NodeDataChanged 节点数据发生变化
NodeDeleted 节点被删除
None 客户端和服务器端连接状态发生变化的时候,事件类型就是None