1 Zookeeper的基本概述
Zookeeper是一个高性能,开源分布式应用协调的服务,保证分布式系统高效,稳定的运行。它提供了简单原始的功能,分布式应用可以基于它实现更高级 的服务,比如同步,配置管理,集群管理,名空间。
如下:如果所有学生都要吃饭,那么一个食堂是不能同时容纳多个班级的学生的,此时,学校的领导机构可以按一定的决策来分配学生的进餐时间,这里学校领导协调机构,就是我们所说的Zookeeper.
Zookeeper本身具有集群功能,本身也是一个分布式程序(只需要节点存活过半数即可).
为了保证机构运行的可靠性(不容易散掉或者不干),我们给机构分配了多个成员。而成员中必须有个领导者用来做主要的决策,称之为leader,其他普通成员称之为follower.
2 Zookeeper的应用场景
2.1 主节点选取
在分布式系统中,为了保证主服务器的稳定性,部署了多台备用机器,当主机挂掉后,需要通过决策来选举一台新的主机,
2.2 分布式共享锁
分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
如下多个业务模块共享同一的交易数据接口,各个模块分布在不同的机器中,需要通过Zookeeper来决定由哪个模块先执行请求。
上面的案例中,Zookeeper主要通过协调各个机器之间的冲突来达到顺利完成分布式系统中各个子机有序的完成任务,而Zookeeper内部则提供了如下服务:
为用户程序提供数据的存储
对文件结构/节点 的监听。
3 zookeeper的安装
zookeeper的安装对机器有一定的软件要求,每台机器必须具备如下要求:
测试的话我们需要自己去创建虚拟机。
每台机器必须相互ping通,并且能够连接外网。
安装java环境。
配置scp命令。
克隆2台新虚拟机并配置网络。
安装Zookeeper服务。
3.1 创建虚拟机
创建第一台虚拟机,为虚拟机配置主机名。如下:
[root@mini3 bin]# vi /etc/sysconfig/network
##配置自己的主机名和网关
NETWORKING=yes
HOSTNAME=mini1
GATEWAY=192.168.7.1
##配置自己的主机名和网关-完
[root@mini3 bin]# reboot
重新配置网络,这里我使用了桥接模式配置,接着在linux内部配置静态IP:
[root@mini3 bin]# vi /etc/sysconfig/network-scripts/ifcfg-eth0
##配置自己的网卡
DEVICE=eth0 ##网卡是eth0
TYPE=Ethernet
ONBOOT=yes ##开机启动
NM_CONTROLLED=yes
BOOTPROTO=static ##配置静态IP
IPADDR=192.168.7.104 ##IP号码
NETMASK=255.255.255.0 ##子网掩码
GATEWAY=192.168.7.1 ##网关配置
##配置自己的网卡-完
[root@mini3 bin]# service network restart
[root@mini3 bin]# ping www.baidu.com
如果使用的是mini版linux,并且ping不通外网,可以使用如下配置:
[root@mini3 bin]# cd /etc/sysconfig/network-scripts
[root@mini3 bin]# touch route-eth0
添加你要配置的网关内容 via 192.168.7.1
[root@mini3 bin]# reboot
[root@mini3 bin]# ping www.baidu.com
3.2 安装java环境
因为zookeeper是需要java运行环境的,需要先安装好java环境:
上传jdk压缩包,通过sftp工具上传即可
解压jdk压缩包
tar -zxvf jdk-7u67-linux-x64.gz -C /usr/local/
修改环境变量PATH
vi /etc/profile
## 在文件最后加两行:
export JAVA_HOME=/usr/local/jdk1.7.0_67
export PATH=$PATH:$JAVA_HOME/bin
3.3 配置SSH
这里配置openssh-client主要是用来对集群内部不同的机器进行数据传输用的。
yum install openssh-clients
1
3.4 克隆2台新虚拟机并配置网络
上面的操作是每台机器都需要的,于是我们又重新克隆了两台虚拟机,但是新的虚拟机如果不做修改是还是无法集群。修改如下:
修改Hotstname.参考上面的操作。
修改IP地址,参考上面,记得不要填写物理地址和UUID.
由于克隆后的网卡配置有问题,这里需要先修改网卡信息:
vi /etc/udev/rules.d/70-persistent-net.rules
1.将eth0的网卡删除
2.将eth1该名为eth0
重新启动虚拟机。
3.5 安装zookeeper服务
上传安装包到linux中。
解压zookeeper并删除压缩包。(-C 输出到指定目录)
tar -zxvf zookeeper-3.4.5.tar.gz -C apps/
rm -f zookeeper-3.4.5.tar.gz
删除没用到的源代码,剩下如下目录:
[root@mini3 ~]# cd /root/apps/zookeeper-3.4.5/
[root@mini3 zookeeper-3.4.5]# ll
total 1300
drwxr-xr-x. 2 root root 4096 May 9 23:36 bin
drwxr-xr-x. 2 root root 4096 May 9 23:25 conf
drwxr-xr-x. 4 root root 4096 May 9 23:21 lib
-rw-r--r--. 1 root root 1315806 May 9 23:21 zookeeper-3.4.5.jar
bin : 执行服务端/客户端的命令工具
conf:配置文件,修改zoom_sample.cfg===>zoom.cfg
lib:到时候写java代码会用到
zookeeper-3.4.5.jar: 核心开发包。
打开zoom.cfg:
# ZK中的一个时间单元。ZK中所有时间都是以这个时间单元为基础,进行整数倍配置的。
tickTime=2000
# Follower在启动过程中,会从Leader同步所有最新数据,然后确定自己能够对外服务的起始状态。Leader允许F在initLimit时间内完成这个工作。通常情况下,我们不用太在意这个参数的设置。如果ZK集群的数据量确实很大了,F在启动的时候,从Leader上同步数据的时间也会相应变长,因此在这种情况下,有必要适当调大这个参数了。
initLimit=10
# 在运行过程中,Leader负责与ZK集群中所有机器进行通信,例如通过一些心跳检测机制,来检测机器的存活状态。如果L发出心跳包在syncLimit之后,还没有从F那里收到响应,那么就认为这个F已经不在线了。
syncLimit=5
# ZK集群内部的数据一般存储在内存中,而该目录是存储内存快照的目录。默认情况下,事务日志也会存储在这里。
dataDir=/root/zkdata
# 连接ZK客户端的端口号
clientPort=2181
# 给集群的机器配置编号 1 2 3 (当然这里可以随便写数字) 它映射到/root/zkdata/myid文件
# 2888端口号是领导者用来监听跟随者连接。 3888端口是用来在选举领导者阶段, 用来监听其他服务器的连接.
server.1=192.168.7.102:2888:3888
server.2=192.168.7.103:2888:3888
server.3=192.168.7.104:2888:3888
通过scp命令将软件发给另外两台虚拟机。
scp -r sourcePath destPath
1
为每个虚拟机环境添加/root/zkdata文件夹。并在文件夹内使用命令:
echo 编号 > myid
1
4 zookeeper服务启动
启动zookeeper
# 启动zkServer
[root@mini3 bin]# /app/zookeeper-3.4.5/bin/zkServer.sh start
JMX enabled by default
Using config: /app/zookeeper-3.4.5/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
#jps 查看java进程 QuorumPeerMain 是代表zkServer的进程
[root@mini3 bin]# jps
1896 QuorumPeerMain
1913 Jps
服务的信息保存在当前文件夹中的zookeeper.out文件中。在zk集群内部有2种角色follower和leader.
[root@mini3 bin]# ./zkServer.sh status
JMX enabled by default
Using config: /root/apps/zookeeper-3.4.5/bin/../conf/zoo.cfg
Mode: follower
上面我们通过手动为每一台机器启动服务。如果有100台的话,就需要通过创建脚本的方式来运行脚本来实现了:
#!/bin/bash
for host in 192.168.7.102 192.168.7.103 192.168.7.104
do
ssh $host "source /etc/profile;/root/apps/zookeeper-3.4.5/bin/zkServer.sh start"
done
5 zookeeper的结构
5.1 zookeeper的特点
一个zookeeper集群是由一个leader,多个follower组成的高可靠的分布式程序.
全局数据一致,每个服务器中的数据都是相同的副本,无论连接那个服务器都是一样的数据
分布式读写数据具有实时性。在一定时间范围内,客户端可以读取到最新数据
数据更新的原子性,对于一次更新操作,要么成功,要么失败
5.2 zookeeper的集群机制
zookeeper集群需要在配置文件zoo.cnf中进行相应的配置,使用的半数节点在线即可保证正常提供服务
对于奇数个zookeeper服务器和偶数个服务器的节点状态是一样的
5.3 zookeeper的数据结构
对于zookeeper中的数据存储以目录节点和具体的数据组成
对于所有的目录节点是以树状结构组成,访问每个节点都必须以绝对路径进行访问(/开头)
对于每个节点都可以存放一个对应的数据
对于每个节点都有一个父节点和可以有多个子节点(但是对于EPHEMERAL短暂性的节点不允许有子节点)
客户端可以在zookeeper的节点上设置监视器,如果节点的数据发生改变,会通知客户端执行指定的操作
存储的数据不允许存储超过1M的数据文件,一般都是1k大小的文件的存储,保证每个节点的数据都是实时的数据(200ms内)
数据节点类型
对于zkServer中的节点数据,主要有两种类型的Znode
短暂性(ephemeral) 在客户端断开连接是会自动删除该节点
持久性(persistent) 数据会一直存储在zkServer中,知道调用删除命令进行删除
根据业务的需要,有可能对于一个节点下的所有的子节点都是按照指定名称+顺序号的方式去生成,
所以对于目录的生成方式,一共有四种
EPHEMERAL 短暂性的节点, 客户端退出会自动删除该节点 /sub
EPHEMERAL_SEQUENTIAL 短暂性的序列节点,在生成节点的时候会自动添加一串序号,保存了节点名称的不会重复 /sub0000000000
PERSISTENT 持久性的节点,
PERSISTENT_SEQUENTIAL 持久性的序列节点
什么情况使用短暂性节点,什么情况使用持久性节点呢?
如果在客户端退出后需要还保存的节点,我们需要设置为持久性的节点
如果一个节点在客户端异常退出或者说需要在客户端断开连接就需要删除的节点,我们可以使用短暂性的节点
6 客户端操作
客户端连接
# 客户端连接命令, -server指定连接服务器 如果不写的话默认连接本地localhost,
# 192.168.7.102 主机名
# 2181 客户端连接地址,在zoo.cnf中配置 如果不写,默认连接2181端口
[root@mini3 bin]# /app/zookeeper-3.4.5/bin/zkCli.sh -server 192.168.7.102:2181
在zk 客户端中执行 help 查看帮助说明
[zk: 192.168.7.102:2181(CONNECTED) 0] help
ZooKeeper -server host:port cmd args
# 查看某个路径的基本信息,
stat path [watch]
# 给某个节点设置数据信息
set path data [version]
# 查看某个路径下面的子节点列表
ls path [watch]
delquota [-n|-b] path
# 显示子节点信息,并且会显示当前节点的状态信息
ls2 path [watch]
setAcl path acl
setquota -n|-b val path
# 显示历史记录
history
# 重新执行命令
redo cmdno
printwatches on|off
# 删除节点,被删除的节点必须是空节点,不能有子节点,可以有数据
delete path [version]
sync path
listquota path
# 删除节点
rmr path
# 获取节点信息和内容
get path [watch]
# 创建节点信息 -s 创建序列节点 -e 创建短暂性节点
create [-s] [-e] path data acl
addauth scheme auth
# 退出客户端
quit
getAcl path
# 关闭连接
close
# 连接服务端
connect host:port
客户端命令练习
# 列出主目录下所有子节点
[zk: 192.168.7.102:2181(CONNECTED) 2] ls /
[zookeeper]
# 创建/oldboys节点,同时指定该节点存放null数据,也可以存放其他数据
[zk: 192.168.7.102:2181(CONNECTED) 3] create /oldboys null
Created /oldboys
# 创建子节点huangbo,并且该节点设置的数据为38
[zk: 192.168.7.102:2181(CONNECTED) 4] create /oldboys/huangbo 38
Created /oldboys/huangbo
# 显示/目录下的所有子节点
[zk: 192.168.7.102:2181(CONNECTED) 5] ls /
[oldboys, zookeeper]
# 显示/oldboys目录下的所有子节点
[zk: 192.168.7.102:2181(CONNECTED) 6] ls /oldboys
[huangbo]
# 获取/oldboys/huangbo节点存放的数据
[zk: 192.168.7.102:2181(CONNECTED) 7] get /oldboys/huangbo
38
cZxid = 0x200000022 # 节点的创建时间所对应的Zxid格式时间戳
ctime = Sat May 05 02:06:33 CST 2018 # 节点创建时间
mZxid = 0x200000022 # 节点的修改时间所对应的Zxid格式时间戳
mtime = Sat May 05 02:06:33 CST 2018 # 节点的修改时间
pZxid = 0x200000022 # 节点的最新修改时间戳
cversion = 0 # 子节点的版本号
dataVersion = 0 # 数据的版本号
aclVersion = 0 # 访问权限控制版本
ephemeralOwner = 0x0 # 如果是短暂性节点,该值为这个节点拥有者的会话ID,否则为0
dataLength = 2 # 存放的数据长度
numChildren = 0 # 所有的子节点的个数
# 修改数据
[zk: 192.168.7.102:2181(CONNECTED) 8] set /oldboys/huangbo 34
cZxid = 0x200000022
ctime = Sat May 05 02:06:33 CST 2018
mZxid = 0x200000023
mtime = Sat May 05 02:07:14 CST 2018
pZxid = 0x200000022
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 2
numChildren = 0
# 删除子节点, 被删除的节点必须是空节点
[zk: 192.168.7.102:2181(CONNECTED) 10] delete /oldboys/huangbo
[zk: 192.168.7.102:2181(CONNECTED) 11] ls /oldboys
7 Java操作zookeeper
package cn.wolfcode.bigdata.api;
import org.apache.zookeeper.*;
import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;
public class ZkClientDemo {
// 设置连接超时时间
public static final int SESSION_TIME_OUT =3000;
// 设置连接的主机地址 多个zkServer使用逗号隔开
public static final String CONNECT_STRING="192.168.7.102:2181,192.168.7.103:2181,192.168.7.104:2181";
// zk客户端连接会话对象zkClient
private ZooKeeper zkClient;
// 设置程序的等待运行,知道数量变为0
private CountDownLatch latch=new CountDownLatch(1);
@Before
public void init() throws Exception{
// 初始化客户端连接对象zkClient
zkClient=new ZooKeeper(CONNECT_STRING, SESSION_TIME_OUT, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 判断当前的状态为连接状态的时候,把latch的count设置为0,让程序继续运行
if(latch.getCount()>0 && event.getState() == Event.KeeperState.SyncConnected){
latch.countDown();
}
//判断节点的数据改变事件,并且是/servers节点的数据改变
if(event.getType()==Event.EventType.NodeDataChanged && "/servers".equals(event.getPath())){
System.out.println(event.getPath());
try {
byte[] data = zkClient.getData("/servers",true,null);
System.out.println("更新后的数据为:"+new String(data));
}catch (Exception e){
e.printStackTrace();
}
}
}
});
//等待latch.getCount()的值变为0
latch.await();
}
/**
* znode节点的创建
* @throws Exception
*/
@Test
public void create() throws Exception{
//创建一个持久化的一个节点,在客户端退出后数据会清空
// 第一个参数: 创建的节点路径,必须是绝对路径,以/开头
// 第二个参宿: 对应的节点的数据类型,传入的是字节数组对象的数据,可以是null
// 第三个参数: 设置创建节点的访问权限,通常情况都是放开权限 使用OPEN_ACL_UNSAFE
// 第四个参数: 设置节点的类型,可以是短暂的,持久的, 短暂+序列,持久+序列
zkClient.create("/servers","wolf".getBytes("UTF-8"),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
}
/**
* 给节点设置数据(修改节点的数据)
* @throws Exception
*/
@Test
public void setData() throws Exception{
// 修改节点的数据,在修改的时候需要指定节点数据的版本号,必须和dataVersion的版本一致,如果不一致,则会导致报错
// 指定为-1表示修改最后一个版本的节点
zkClient.setData("/servers","wolfcode".getBytes("UTF-8"),-1);
}
/**
* 修改数据
*/
@Test
public void getData() throws Exception{
//获取节点数据
// 第一个参数: 节点路径
// 第二个参数: 设是否使用原来绑定好的模拟器
// 第三个参数 stat状态信息(节点元数据封装 传入null表示默认)
byte[] data = zkClient.getData("/servers", false, null);
System.out.println(new String(data,"utf-8"));
}
@Test
public void getDataWithWatch() throws Exception{
// watch:true,表示使用zkClient中已有的事件监听设置
byte[] data = zkClient.getData("/servers", true,null);
System.out.println(new String(data,"utf-8"));
Thread.sleep(Long.MAX_VALUE);
}
/**
* 删除节点
*/
@Test
public void delZNode() throws Exception{
// 删除的节点不能有子节点
// 删除需要指定对应的节点版本信息,如果不需要版本信息,设置为-1 和dataVersion版本信息相对应
zkClient.delete("/servers",2);
}
}
---------------------
作者:CoderLean
来源:CSDN
原文:https://blog.csdn.net/qq285016127/article/details/80271672