zookeeper安装包资源
百度网盘链接:点击打开链接 密码:po8x
单机模式安装
实现步骤:
1.准备虚拟机
然后通过xshell来连接
2.关闭防火墙
3.安装jdk和配置环境变量
4.上传安装zookeeper
目录结构:
bin 命令
lib 运行的jar包类库
conf 配置文件
5.配置zk的配置文件
对zoo_sample.cfg进行拷贝和重命名为zoo.cfg,注意这个名字写死
6.启动zk服务
切换到bin目录:执行 sh zkServer.sh start
7.启动zk客户端,操作zk服务
执行:sh zkCli.sh
Zookeeper命令:
1.znode表示的每个节点。Zookeeper有多个znode节点组成了一个znode树
2.znode可以有子节点,此外,znode也可以存储数据
3.znode是有监听机制的,即zonde节点的状态变化是可以被监听的,这个是zookeeper核心特性,很多Zookeeper实现的功能都是基于这个特性来实现的
4.znode节点是可以被创建的,一共有4种类型的节点,分别是:
①.持久节点
②.临时节点:当创建此临时的客户端退出时,临时节点会消失
③.持久顺序节点:在节点名称后,zk会自动在节点后加一个递增的顺序号
④.临时顺序节点:
5.znode路径的唯一性
create /park01 数据
需要注意的是:
①要基于根路径来创建
②在创建节点的同时,要分配初始数据
③也可以通过 :create /park02 '' 相当于创建空数据
④路径的唯一性
create -e /park03 数据
创建临时节点
create -s /park04 数据
创建持久顺序节点
create -s -e /park05 数据
创建临时顺序节点
create /park01/ch1 数数据
创建 pak01的子节点ch1 ,并且这个节点类型是持久节点
ls /park01
get /park01
查看指定路径下的数据
delete /park02
删除节点指令
如果有子节点的话,不能用这个指令
rmr /park01
无论如何都会删除节点
set /park01 要更新的数据
zookeeperAPI操作
一、
连接zookeeper和创建节点数据代码:
public class ZkDemo {
public static void main(String[] args) throws Exception {
final CountDownLatch cdl =new CountDownLatch(1);
//第一个参数,连接的zookeeper节点ip,
//第二个参数,会话超时时间,以毫秒为单位。比如设置1000*30 是30秒。如果30秒内zookeeper没有收到客户端节点的心跳,则断开连接
//第三个参数,Watcher,观察者,当zookeeper里有事件发生时,会通知到这个process方法里
//需要注意,连接是异步的,当连接成功是,会回调process方法,我们需要做相应的处理
//此外,连接一个zookeeper连接地址也可以,因为zookeep会将数据同步至其他的zookeerper节点
ZooKeeper zk=new ZooKeeper("192.168.234.137:2181",30000,new Watcher(){
@Override
public void process(WatchedEvent event) {
if(event.getState()==KeeperState.SyncConnected){
//说明连接成功了
cdl.countDown();
}
}
});
//之所以用递减锁,因为:连接是非阻塞的,到底连接成功没,不知道。所以,用递减锁,把这个连接变成了阻塞式连接,当成功连接后,才走
//await()之后的代码
cdl.await();
//创建节点
//1.path 节点路径;2.data[] 节点数据;3.acl 权限策略,一般设为所用用户都可以操作这个节点,并且具有所有权限,所以选:Ids.OPEN_ACL_UNSAFE;
//4.createMode 创建模式:
//PERSISTENT 持久
//PERSISTENT_SEQUENTITAL 持久顺序
//EPHEMERAL 临时
//EPHEMERAL_SEQUENTITAL 临时顺序
zk.create("/node", "hello1604Zookeeper".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
//也可以通过StringCallBack()回调函数来拿到创建后的节点名。rc 是返回码。0代表节点创建成功。-110代表此节点已存在,节点创建失败。
//ctx 可以是任意一个对象,这个对象也可以回调函数里接收
//需要注意的是,如果是用这种方式来创建节点,是非阻塞的
zk.create("/02","helloZK".getBytes(),Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT,new StringCallback() {
public void processResult(int rc, String path, Object ctx, String name) {
System.out.println(rc+":"+path+":"+ctx+":"+name);
}
}, 1);
}
}
二、
更新节点数据:
@Test
public void testSet() throws Exception{
final CountDownLatch cdl=new CountDownLatch(1);
ZooKeeper zk=new ZooKeeper("192.168.234.137:2181",30000,new Watcher(){
@Override
public void process(WatchedEvent event) {
if(event.getState()==KeeperState.SyncConnected){
cdl.countDown();
}
}
});
cdl.await();
//version版本号的作用:在指定路径下,有一个dataVersion,这个是数据更改的版本号,从0开始,在此节点下
//每更改一次数据,版本号递增1
//而setData(, ,version)的version的意思是:指定一个版本号,基于这个版本号来修改,但是需要注意:
//如果指定的版本号是3,而当前的dataVersion是4,即指定的版本号不存在(已过时),则会报错
//如果指定的版本号是-1,则说明无论当前dataVersion是多少,都会进行数据的更新,最常用的就是-1
zk.setData("/node", "ccc".getBytes(), -1);
}
三、
获取节点数据
@Test
public void testGetData() throws Exception{
final CountDownLatch cdl=new CountDownLatch(1);
ZooKeeper zk=new ZooKeeper("192.168.234.137:2181",3000,new Watcher(){
@Override
public void process(WatchedEvent event) {
cdl.countDown();
}
});
cdl.await();
//watch,当指定节点发生监听的事件时,会触发Watcher里的方法,
//监听的事件有:1.节点数据发生变化 2.节点创建 3.节点删除 4.节点的子节点发生变化。但是监听的事件只会触发一次回调方法。
//stat,节点环境信息,一般设置为null即可。如果想要,可以传入一个空的Stat对象来接收
byte[] data=zk.getData("/node",new Watcher(){
@Override
public void process(WatchedEvent event) {
if(event.getType()==EventType.NodeDataChanged){
System.out.println("节点数据发生变化");
}
}
},null);
System.out.println(new String(data));
//保持线程开启
//getACL,得到指定路径的权限信息,权限信息被封装在返回的集合里
//此外,也可以传入一个Stat对象,用来分装节点的环境信息,比如zxid,ctime等等
Stat s1=new Stat();
List<ACL> list=zk.getACL("/02",s1);
for(ACL a:list){
System.out.println(a);
}
System.out.println(s1);
while(true){}
}
四
获取子节点
@Test
public void testGetChildren() throws Exception{
//连接代码省略
List<String> list=zk.getChildren("/", null);
for(String s:list)System.out.println(s);
}
五
删除节点
@Test
public void testDelete() throws Exception{
//连接代码略
zk.delete("/node", -1);
}
六
判断节点是否存在
@Test
public void testExists() throws Exception{
//如果节点不存在,stat返回值=null
//如果节点存在,stat返回节点的元数据
Stat stat=zk.exists("/node02", null);
System.out.println(stat);
}
七
获取创建的节点信息:如果创建的是临时节点该方法很有必要知道
@Test
publicvoid testCreateByAsynchronous() throws Exception{
zk.create("/node","helloworld".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT,
new StringCallback() {
public void processResult(int rc, String path, Object ctx,String name) {
System.out.println("节点创建成功"+rc+":"+path+":"+ctx+":"+name);
}
},null);
}
八
实现永久监听
第一种方式代码:
public class TestZK {
@Test
public void test01() throws Exception{
final CountDownLatch cdl=new CountDownLatch(1);
ZooKeeper zk=new ZooKeeper("192.168.234.137:2181",30000,new Watcher(){
@Override
public void process(WatchedEvent event) {
System.out.println("已连接zookeeper服务");
cdl.countDown();
}
});
cdl.await();
Watcher wc=new Watcher(){
public void process(WatchedEvent event) {
if(event.getType().equals(EventType.NodeDataChanged)){
System.out.println("节点数据发生变化");
}
};
};
for(;;){
zk.getData("/engine1/engine01", wc,null);
}
}
}
第二种方式代码(建议使用):
@Test
public void test01() throws Exception{
final CountDownLatch cdl=new CountDownLatch(1);
ZooKeeper zk=new ZooKeeper("192.168.234.137:2181",30000,new Watcher(){
@Override
public void process(WatchedEvent event) {
System.out.println("已连接zookeeper服务");
cdl.countDown();
}
});
cdl.await();
for(;;){
final CountDownLatch cdll=new CountDownLatch(1);
zk.getData("/engine1/engine01", new Watcher(){
@Override
public void process(WatchedEvent event) {
if(event.getType().equals(EventType.NodeDataChanged)){
System.out.println("节点数据发生变化");
cdll.countDown();
}
}
},null);
cdll.await();
}
}
永久监听+得知更新后数据:
@Test
public void testGetData() throws Exception{
final CountDownLatch cdl=new CountDownLatch(1);
ZooKeeper zk=new ZooKeeper("192.168.234.170:2181",2000,new Watcher(){
@Override
public void process(WatchedEvent event) {
if(event.getState()==KeeperState.SyncConnected){
System.out.println("已成功连接服务器");
cdl.countDown();
}
}
});
cdl.await();
for(;;){
final AuthCallBack ac=new AuthCallBack();
final CountDownLatch cdl2=new CountDownLatch(1);
zk.getData("/02",new Watcher(){
@Override
public void process(WatchedEvent event) {
if(event.getType()==EventType.NodeDataChanged){
System.out.println("有节点数据发生变化");
System.out.println("更新后的数据"+ac.getNewData());
cdl2.countDown();
}
}
},ac,null);
cdl2.await();
}
}
class AuthCallBack implements DataCallback {
private String newData;
@Override
public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
this.newData = new String(data);
}
public String getNewData() {
return newData;
}
public void setNewData(String newData) {
this.newData = newData;
}
}
zookeeper集群搭建
1.准备三台虚拟机
2.把每台虚拟机防火墙关掉
执行:service iptables stop(如果是centos7将防火墙策略情况即可)
3.配置JDK
4.安装Zookeeper
5.对配置文件进行配置
(1)对zoo_sample.cfg进行拷贝和重命名为zoo.cfg,注意这个名字写死
tickTime =2000 毫秒
minSessiontimeout=tickTime*2
maxSessiontimeout=tickTime*20
Zookeeper的规定:如果API指定的SessionTimeout在区间范围内2s~40s,Zookeeper取得就是API里指定的时间。
比如传的是1s 1<2 ,则用的是下限2s
如果41s 41>40 则用的是上限40s
initLimit Follower初始化连接时和Leader连接的超时时间。initLimit=tickTime*10(initLimit的参数)。注意:Follower和Leader针对的是zk服务集群而言的,一个zk服务集群里只有一个Leader。
syncLimit:当Follower和Leader连接上之后,Leader和Follower要实时进行数据通信,如果某一Follower在syncLimit时间期限内,没有和Leader建立通信,则Leader将其抛弃
应用:比如在网络环境不好的时候,可以适当调大此值。
synclimit=tickTime*syncLimit
dataDir默认的数据存储路径是:/tmp/zookeeper。但是这个路径是一个临时目录路径。所以我们需要针对这个参数进行更改:/home/software/zookeeper-3.4.7/tmp
server.1=192.168.234.186:2888:3888
server.2=192.168.234.187:2888:3888
server.3=192.168.234.188:2888:3888
server是个关键字,后面跟一个数字,这个数字指定就是当前zookeeper的领导id
2888端口是原子广播端口,zk服务集群在进行集群间数据中一致性通信时,是通过这个端口来通信。当然这个端口,不唯一,可以自己指定。
3888是选举端口,zk服务集群的Leader是通过选举机制来选出来的,在选举时的通信端口是3888。当然这个端口可以指定。
(2)配置文件配完之后,创建指定的目录 tmp
(3)然后进入tmp目录,创建myid文件,并写当前zookeeper的领导id,就是server.1的那个数字
zookeeper集群中的选举机制原理
由于内容偏长,改为了资源,见我的资源中:zookeeper选举机制图。
详细介绍了zookeeper集群中的leader是如何选举出来的
Zk的选举机制,是基于Paxos这个算法来实现,这个算法的核心思想是:
要解决在分布式环境下,就某一个协议达成一致的算法。
zk 用的是fast paxos 算法,相当于做了改进。
Paxos算法容易在分布式环境下产生活锁。
zookeeper集群命令
由于内容偏长,改为了资源,见我的资源中:zookeeperj集群命令。
操作zookeeper集群的api
package com.liming.zookeeper;
import java.util.concurrent.CountDownLatch;
import org.apache.zookeeper.AsyncCallback.StringCallback;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.junit.Before;
import org.junit.Test;
public class TestZookeeperCluster {
static ZooKeeper zk;
@Before//连接zookeeper集群
public void initZk() throws Exception {
final CountDownLatch cdl = new CountDownLatch(1);
zk = new ZooKeeper("192.168.239.129:2181,192.168.239.130:2181,192.168.239.131:2181", 3000,
new Watcher() {
public void process(WatchedEvent event) {
//KeeperState.SyncConnected代表连接成功事件
if(event.getState()==KeeperState.SyncConnected){
System.out.println("连接成功");
cdl.countDown();
}
}
});
cdl.await();
}
@Test//永久监听zookeeper集群节点的数据变化
public void testWatch() throws Exception{
for(;;){
final CountDownLatch cdl = new CountDownLatch(1);
zk.getData("/park01", new Watcher() {
public void process(WatchedEvent event) {
if(event.getType()==EventType.NodeDataChanged){
System.out.println("节点数据发生变化");
cdl.countDown();
}
}
}, null);
cdl.await();
}
}
@Test
//永久监听zookeeper集群子节点的事件变化
//park01子节点的删除、创建会被监听到,子节点数据变化,此方法不会监听到还得使用getdata
public void testWatchChildren() throws Exception{
for(;;){
final CountDownLatch cdl = new CountDownLatch(1);
zk.getChildren("/park01", new Watcher() {
public void process(WatchedEvent event) {
if(event.getType()==EventType.NodeChildrenChanged){
System.out.println("节点数据发生变化");
cdl.countDown();
}
}
});
cdl.await();
}
}
@Test//永久监听节点的穿件删除事件
public void testCreate_delete() throws Exception{
//exists方法如果该路径存在将返回信息封装到stat对象中,反之返回null
for(;;){
final CountDownLatch cdl = new CountDownLatch(1);
Stat exists = zk.exists("/park05", new Watcher() {
public void process(WatchedEvent event) {
if(event.getType()==EventType.NodeCreated){
System.out.println("节点被创建");
cdl.countDown();
}else if (event.getType()==EventType.NodeDeleted) {
System.out.println("节点被删除");
cdl.countDown();
}
}
});
cdl.await();
}
}
/*
* 利用回调接口拿到创建的实际路径节点名
* 应用场景:如果我们创建的Xxx顺序节点,这个节点我们需要想办法拿到。
*/
@Test
public void testCreateStringCallBack() throws Exception{
zk.create("/park06","".getBytes(),Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL,
new StringCallback() {
//rc:状态码,成功返回0,不成功-110
//path 用户指定的路径
//ctx 构造参数里传递的数据
//name 实际创建的路径节点名
public void processResult(int rc, String path, Object ctx, String name) {
System.out.println(rc+":"+path+":"+ctx+":"+name);
}
},"hello");
while(true);
}
}
Zookeeper观察者状态
Leader 或Follower 或Observer
比如针对事务决议的表决,通过投票表决。过半同意事务就更新。
如果从性能角度来看,参与投票的机器越少越好。(但至少满足过半投票人数)
所以zookeeper引入了observer观察者的状态。
observer观察者的特点:
1.不参与投票
2.观察和监听投票结果,然后去更新状态。
如何来设置观察者:
实现步骤:
1.在要变成观察者的机器的zk的配置文件
加:peerType=observer
server.3=ip:2888:3888:observer
2.更改其他机器的配置文件,加:
server.3=ip:2888:3888:observer
引入观察者,一是通过减少投票机器数量,从而提高性能。
此外,也可以从降低每一台zk服务器的负载压力角度来看,能够降低负载压力。
因为观察者如果宕机,影响也不大,因为不是核心人员。
但是注意,引入观察者,从性能角度来看,是好东西。但是如果从集群高可用的角度来看,要 慎用。
此外再注意,比如3台机器,把两台配置成观察者,这么做是不行的,因为不满足过半机制, 所以选不出leader,导致集群启动不了。
Zookeeper应用场景
实现数据的订阅和发布
案例:
比如一台机器发布数据,其他的机器都能够收到发布的最新数据
实现思路:
1.创建一个节点,这个节点是数据发布方的节点,存储数据(1mb)
2.其他机器通过监听这个节点的数据变化,拿到最新数据
实现配置信息的统一管理
实现思路参考数据的订阅发布
实现集群管理
实现思路:
1.客户端注册一个临时节点(临时顺序节点)
2.当宕机或失去连接后,临时节点消失
3.通过API可以监听到这个事件的变化,从而做后续的业务处理
实现分布式协调效果(屏障效果barriar)
案例:比如,成绩排名,排名的前提是前两台机器都把活做完了,才能做统计排名。
这相当于是分布式环境下的线程同步协调
实现思路:
1.可以创建一个/score节点,然后成绩统计节点把活做完后,在/score创建一个子节点
2.做最后统计排名的节点,去监听/socre子节点的状态变化以及统计子节点的数量,如果是2 个,就可以开始干活了。
实现分布式锁的效果(共享锁)
实现思路:
1.创建一个/lock路径
2.三个机器争抢去在/lock路径下去抢注Xxx顺序节点,最先抢注的节点的顺序号肯定是最小的
3.然后通过比较,去决定哪个机器去用这个共享资源
Zookeeper特点
1.数据一致性
无论从哪一台zk服务器读取数据,数据都是一样的。(也叫单一视图)。是zookeeper最重要 的特性。
2.可靠性
当一个事务被成功更新后,不会因为网络波动或其他原因而回滚
3.原子性
一个事务,要么都更新成功,要么都更新失败。不会有中间状态
4.实时性
能够在很短的时间内,将事件的状态变化反映出来。(前提是网络状态良好,没有网络波动)
5.顺序一致性
比如客户端A,发出三个指令:x=a,x=b,x=c。但是在网络波动的情况下,可能到达顺序是 x=b,x=c,x=a。Zk能够确保发送的顺序和最后执行的顺序是一致的,从而确保最后的结果是 正确的