概述
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
ZooKeeper包含一个简单的原语集,提供Java和C的接口。
ZooKeeper代码版本中,提供了分布式独享锁、选举、队列的接口,代码在$zookeeper_home\src\recipes。其中分布锁和队列有Java和C两个版本,选举只有Java版本。
提供的服务包括:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等。
特点
Zookeeper是由一个leader,多个follower组成的集群。
- Leader是由follower选举产生,Leader负载投票发起和决议,更新系统状态。
- Follower用于接收客户端的请求并返回结果,在leader的选举中参与投票。
- 集群中只有半数以上的机器存活,集群才可以使用。
- 整合集群中数据一致,每个server会保存一个副本,无论客户端连接到哪个server,数据都是一致的。
- 更新请求是按顺序执行。
- 保证数据的原子性,一次更新成功要么是成功,要么是失败。
- 数据的实时性,客户端在更新数据时,在一定的时间范围内,数据是一致的。
安装部署
将下载好的Zookeeper安装包上传到hadoop101 /opt/software/ 并解压。
//解压
[hadoop@hadoop101 software]$ tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz -C /opt/module/
//重命名
[hadoop@hadoop101 software]$ cd /opt/module/
[hadoop@hadoop101 module]$ mv apache-zookeeper-3.5.7-bin/ zookeeper-3.5.7/
//创建zkData文件夹
[hadoop@hadoop101 module]$ cd zookeeper-3.5.7
[hadoop@hadoop101 zookeeper-3.5.7]$ mkdir zkData
//在zkData文件夹中创建myid文件
[hadoop@hadoop101 zookeeper-3.5.7]$ cd zkData
[hadoop@hadoop101 zkData]$ vim myid
myid文件中,hadoop101的内容为1,hadoop102的内容为2,以此类推。 只要三台客户机的id不一样即可。
//重命名
[hadoop@hadoop101 zkData]$ cd ..
[hadoop@hadoop101 zookeeper-3.5.7]$ cd conf/
[hadoop@hadoop101 conf]$ mv zoo_sample.cfg zoo.cfg
//编辑
[hadoop@hadoop101 conf]$ vim zoo.cfg
找到dataDir,修改路径为我们刚刚创建的zkData文件夹。zookeeper集群中server.x的x为myid中的id,后面为对应的主机名。
dataDir=/opt/module/zookeeper-3.5.7/zkData
###############zookeeper集群#################
server.1=hadoop101:2888:3888
server.2=hadoop102:2888:3888
server.3=hadoop103:2888:2888
//分发
[hadoop@hadoop101 conf]$ cd /opt/module/
[hadoop@hadoop101 module]$ xsync zookeeper-3.5.7/
分发成功后,到hadoop102和hadoop103中修改myid。
集群的启动和停止
在三台客户机下分别执行下列命令。
//启动
[hadoop@hadoop101 zookeeper-3.5.7]$ bin/zkServer.sh start
//查看状态
[hadoop@hadoop101 zookeeper-3.5.7]$ bin/zkServer.sh status
//停止
[hadoop@hadoop101 zookeeper-3.5.7]$ bin/zkServer.sh stop
查看状态,我们可以看到,hadoop101和hadoop103都是follower,hadoop102是leader。这是因为,在本例中,超过半数为2。hadoop101启动时,试图成为leader,但其他两台客户机未响应,没有达到超过半数的要求。hadoop102启动时,试图成为leader,由于它的id比hadoop101大,因此101与102两台客户机都支持102成为leader,超过半数,102被选举为leader。hadoop103启动时,虽然它的id最大,但102已经成为leader,它只能成为follower。
像hadoop集群的启动和停止那样,在每台客户机中操作太繁琐。我们可以编写脚本zk.sh,方便集群的启动、停止和查看状态。
[hadoop@hadoop101 zookeeper-3.5.7]$ cd ~
[hadoop@hadoop101 ~]$ cd bin/
[hadoop@hadoop101 bin]$ sudo vim zk.sh
#!/bin/bash
case $1 in
"start"){
for i in hadoop101 hadoop102 hadoop103
do
echo "****************$i*************"
ssh $i "source /etc/profile;/opt/module/zookeeper-3.5.7/bin/zkServer.sh start"
done
};;
"stop"){
for i in hadoop101 hadoop102 hadoop103
do
echo "****************$i*************"
ssh $i "source /etc/profile;/opt/module/zookeeper-3.5.7/bin/zkServer.sh stop"
done
};;
"status"){
for i in hadoop101 hadoop102 hadoop103
do
echo "****************$i*************"
ssh $i "source /etc/profile;/opt/module/zookeeper-3.5.7/bin/zkServer.sh status"
done
};;
esac
//修改权限
[hadoop@hadoop101 bin]$ sudo chmod 777 zk.sh
[hadoop@hadoop101 bin]$ cd ..
//分发
[hadoop@hadoop101 ~]$ xsync bin/
//启动
[hadoop@hadoop101 ~]$ zk.sh start
//查看状态
[hadoop@hadoop101 ~]$ zk.sh status
//停止
[hadoop@hadoop101 ~]$ zk.sh stop
客户端操作
客户端命令
在启动客户端之前要保证集群正常启动。
//启动客户端
[hadoop@hadoop101 zookeeper-3.5.7]$ bin/zkCli.sh
//显示所有操作命令
[zk: localhost:2181(CONNECTED) 0] help
//查看当前znode中所包含的内容
[zk: localhost:2181(CONNECTED) 1] ls /
//查看当前节点详细数据
[zk: localhost:2181(CONNECTED) 2] ls2 /
//创建两个普通节点
[zk: localhost:2181(CONNECTED) 3] create /mydir "mydir"
[zk: localhost:2181(CONNECTED) 4] ls /
[zk: localhost:2181(CONNECTED) 5] create /mydir/cs "cs"
[zk: localhost:2181(CONNECTED) 6] ls /mydir
//获取节点
[zk: localhost:2181(CONNECTED) 7] get /mydir
[zk: localhost:2181(CONNECTED) 8] get /mydir/cs
//创建短暂节点
[zk: localhost:2181(CONNECTED) 9] create -e /mydir/temp "temp"
//这时候查看应该还是有节点temp的
[zk: localhost:2181(CONNECTED) 10] ls /mydir
//退出
[zk: localhost:2181(CONNECTED) 11] quit
//启动客户端
[hadoop@hadoop101 zookeeper-3.5.7]$ bin/zkCli.sh
//查看,短暂节点temp已经删除
[zk: localhost:2181(CONNECTED) 0] ls /mydir
//创建带序号的节点
[zk: localhost:2181(CONNECTED) 1] create -s /mydir/software "software"
[zk: localhost:2181(CONNECTED) 2] create -s /mydir/software "software2"
[zk: localhost:2181(CONNECTED) 3] ls /mydir
//在hadoop103中增加监听
[zk: localhost:2181(CONNECTED) 0] get /mydir watch
//在hadoop102中修改节点信息
[zk: localhost:2181(CONNECTED) 1] set /mydir "mydir1"
在hadoop103中,会弹出如下提示,说明监听成功。
//在hadoop101中
//查看节点状态
[zk: localhost:2181(CONNECTED) 4] stat /mydir
//删除节点
[zk: localhost:2181(CONNECTED) 5] delete /mydir/cs
//递归删除节点
[zk: localhost:2181(CONNECTED) 6] deleteall /mydir
API应用
创建zookeeper_demo的maven工程,并导入pom.xml依赖。
<!-- 自定义属性设置版本号 -->
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<hadoop-version>2.7.2</hadoop-version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.10</version>
</dependency>
</dependencies>
创建log4j.properties日志,配置如下:
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
编写代码如下:
public class TestZookeeper {
private Logger logger = Logger.getLogger(this.getClass());
//中间不能有空格!!!
private String connect = "hadoop101:2181,hadoop102:2181,hadoop103:2181";
private int sessionTimeout = 2000; //超时
private ZooKeeper zkClient;
@Before
public void init() throws IOException {
//创建客户端对象
zkClient = new ZooKeeper(connect, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
logger.info("------------启动-----------");
try {
List<String> children = zkClient.getChildren("/", true);
for(String child: children) {
logger.info(child);
}
logger.info("------------停止-----------");
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
});
}
//创建永久节点
@Test
public void createNode() throws KeeperException, InterruptedException {
String path = zkClient.create("/sanguo", "sg".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
logger.info(path);
}
//获取子节点变化
@Test
public void getChileNode() throws KeeperException, InterruptedException {
List<String> children = zkClient.getChildren("/", true);
for(String child: children) {
logger.info(child);
}
//写一个线程,2s获取一次子节点的变化
Thread.sleep(2000);
}
}
运行后,到集群中查看,可以看到我们创建的sanguo节点。
输出日志如下:
案例: 监听节点上下线
需求:分布式系统中,主节点可以有多台,可以动态上下线,要求任意一台客户端都能事实感知到主节点服务器的上下线。
代码实现
//服务端
public class DistributeServer {
private Logger logger = Logger.getLogger(this.getClass());
private String connect = "hadoop101:2181,hadoop102:2181,hadoop103:2181";
private int sessionTimeout = 2000; //超时
private ZooKeeper zkClient;
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
//创建对象
DistributeServer server = new DistributeServer();
//连接
server.getConnect();
//注册临时节点
server.register(args[0]);
//业务逻辑处理
server.business();
}
private void getConnect() throws IOException {
zkClient = new ZooKeeper(connect, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
}
});
}
private void register(String hostname) throws KeeperException, InterruptedException {
String host = zkClient.create("/servers/"+hostname, hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
logger.info(host + "上线");
}
private void business() throws InterruptedException {
Thread.sleep(Long.MAX_VALUE);
}
}
//客户端
public class DistributeClient {
private Logger logger = Logger.getLogger(this.getClass());
private String connect = "hadoop101:2181,hadoop102:2181,hadoop103:2181";
private int sessionTimeout = 2000; //超时
private ZooKeeper zkClient;
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
//创建对象
DistributeClient client = new DistributeClient();
//连接
client.getConnect();
//获取子节点信息
client.getChildrenNode();
//业务逻辑处理
client.business();
}
private void getConnect() throws IOException {
zkClient = new ZooKeeper(connect, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
try {
getChildrenNode();
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
});
}
private void getChildrenNode() throws KeeperException, InterruptedException {
List<String> children = zkClient.getChildren("/servers", true);
List<String> hosts = new ArrayList<>();
for(String child: children) {
//获取servers数据
byte[] data = zkClient.getData("/servers/"+child, false, null);
hosts.add(new String(data));
}
logger.info(hosts);
}
private void business() throws InterruptedException {
//休眠设置成最大值,否则一下就结束程序了
Thread.sleep(Long.MAX_VALUE);
}
}
实现效果
运行之前,需要在集群中创建servers节点。
[zk: localhost:2181(CONNECTED) 0] create /servers/
运行客户端,然后在集群中创建节点。
[zk: localhost:2181(CONNECTED) 1] create -e -s /servers/hadoop101 "hadoop101"
[zk: localhost:2181(CONNECTED) 2] create -e -s /servers/hadoop102 "hadoop102"
[zk: localhost:2181(CONNECTED) 3] create -e -s /servers/hadoop103 "hadoop103"
[zk: localhost:2181(CONNECTED) 4] ls /servers
集群中可以看到我们创建了三个节点,同时我们的客户端也在同步监听。
删除三个节点,客户端也能同步监听。
[zk: localhost:2181(CONNECTED) 5] delete /servers/hadoop1010000000000
[zk: localhost:2181(CONNECTED) 6] delete /servers/hadoop1020000000001
[zk: localhost:2181(CONNECTED) 7] delete /servers/hadoop1030000000002
运行服务端,报错停止后,我们点击Edit Configurations,将Program aruguments依次设置为hadoop101, hadoop102和hadoop103。分别运行,我们能看到服务端提示hadoop101上线,客户端也在同步监听。