Zookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。
Zookeeper是基于观察者设计模式设计的分布式服务管理框架,它负责存储和管理大家关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper就将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应。
启动Zookeeper服务
zkServer.sh start
关闭Zookeeper服务
zkServer.sh stop
查看Zookeeper状态
zkServer.sh status
当搭建的是完全分布式时,集群的Zookeeper服务必须启动半数以上才能正常工作,且myid最大的为leader[即当启动个数超过半数的那一刻myid最大的为leader,此后及时更大的myid节点加入集群为不会更改leader]
启动Zookeeper客户端
zkCli.sh
查看帮助
help
查看指定路径的节点
ls path [watch]
ls2 path //查看详细信息
在指定路径创建节点
create [-s] [-e] path data
-e 创建的节点是临时的 客户端退出该节点自动删除
-s 创建带序号的节点,原节点下没有节点从0开始,若已经有n个节点则从n+1开始
data 为该节点存储的信息,一定要有否则无法创建
这个是实现服务器上下线功能的基础
获取指定节点上的信息
get path [watch]
注意到ls
和get
有watch可选项,这是实现服务器上下线功能的核心,其中ls
为监视指定路径的节点个数变化,get
为监视指定节点的信息变化
ls /file watch //监视file节点的节点变化
delete /file/idea //删除file节点下的idea节点
//客户端发出节点变化信息
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/file
这时候我们已经具备实现此功能的所有知识
服务器上线向Zookeeper集群的servers节点下注册信息,即在servers节点下创建临时的带序号的存有服务器ip,hostname等信息的节点,当servers节点下有新增节点客户端通过ls /servers watch
即可实现监听功能,并且通过get /servers/server000000x
获取节点的信息便可得知是哪台服务器上线,服务器下线临时节点自动删除客户端监听原理相同。因此大致流程如下:
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>RELEASEversion>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.8.2version>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.4.10version>
dependency>
dependencies>
关于IDEA2020.1的Maven找不到包的问题,可以再pom.xml文件目录下进入cmd执行mvn idea:idea
实测可以解决。
解决log4j打印日志问题,可以再resources下新建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
private void getConnection() throws IOException {
zkClient = new ZooKeeper(
ResourceBundle.getBundle("zookeeper").getString("zookeeperName"),
2000,
watch -> {
}
);
}
Zookeeper构造器如下
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)
//connectionString 连接对象
//sessionTimeout 最大连接时间
//watcher 监听逻辑
我的zoo.cfg内容如下
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/usr/local/soft/zookeeper-3.4.10/zkData
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
server.1=master:2888:3888
server.2=slave01:2888:3888
server.3=slave02:2888:3888
配置文件规定了端口号即2181
,因此连接对象为ip:2181
,通过配置文件读取
private void registered() throws KeeperException, InterruptedException {
zkClient.create(
"/servers/server", //创建节点的路径
dealHostName().getBytes(), //存储到节点上的信息
ZooDefs.Ids.OPEN_ACL_UNSAFE, //类似一种权限
CreateMode.EPHEMERAL_SEQUENTIAL //即-e -s
);
}
private void business() throws InterruptedException {
System.out.println(hostname + " is online");
Thread.sleep(Long.MAX_VALUE);
}
为了让服务器注册完信息后保持存活,方便控制服务器上下线实时监测客户端是否发生变化。
package zookeeper.project.driver;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.util.ResourceBundle;
/*
* Server端,维护一些服务器的上下线通知
* 当服务器上线时,向zookeeper集群servers节点上注册短暂的server节点并存储服务器名
* */
public class Server {
private String hostname;
private ZooKeeper zkClient;
//获取连接
private void getConnection() throws IOException {
zkClient = new ZooKeeper(
ResourceBundle.getBundle("zookeeper").getString("zookeeperName"),
2000,
watch -> {
}
);
}
//注册信息
private void registered() throws KeeperException, InterruptedException {
zkClient.create(
"/servers/server",
dealHostName().getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL
);
}
//业务逻辑
private void business() throws InterruptedException {
System.out.println(hostname + " is online");
Thread.sleep(Long.MAX_VALUE);
}
//处理hostname
private String dealHostName() {
try {
hostname = Inet4Address.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
hostname = "0.0.0.0";
}
return hostname;
}
//服务器上线
public void start() {
try {
getConnection();
registered();
business();
} catch (IOException | InterruptedException | KeeperException e) {
e.printStackTrace();
}
}
}
private void getConnection() throws IOException {
zkClient = new ZooKeeper(
ResourceBundle.getBundle("zookeeper").getString("zookeeperName"),
2000,
watch -> {
try {
//回滚,持续监听
String format = simpleDateFormat.format(new Date());
System.out.println("\n=========== " + format + " ===========");
registered();
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
);
}
private void registered() throws KeeperException, InterruptedException {
List<String> children = zkClient.getChildren("/servers", true);
ArrayList<String> list = new ArrayList<>();
for (String path : children) {
list.add(new String(zkClient.getData("/servers/" + path, false, null)));
}
for (String ip : list) {
try {
ip += (" | " + ResourceBundle.getBundle("ip").getString(ip));
} catch (MissingResourceException e) {
ip += " | 未知IP";
}
System.out.println(" \t " + ip);
}
}
private void business() throws InterruptedException {
Thread.sleep(Long.MAX_VALUE);
}
package zookeeper.project.driver;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
/*
* Client需要实时监测zookeeper集群servers下的节点变化,并获取节点存储的信息,即可得知服务器的上下线通知
* */
public class Client {
private ZooKeeper zkClient;
private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//获取连接
private void getConnection() throws IOException {
zkClient = new ZooKeeper(
ResourceBundle.getBundle("zookeeper").getString("zookeeperName"),
2000,
watch -> {
try {
//回滚,持续监听
String format = simpleDateFormat.format(new Date());
System.out.println("\n=========== " + format + " ===========");
registered();
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
);
}
//注册监听
private void registered() throws KeeperException, InterruptedException {
List<String> children = zkClient.getChildren("/servers", true);
ArrayList<String> list = new ArrayList<>();
for (String path : children) {
list.add(new String(zkClient.getData("/servers/" + path, false, null)));
}
for (String ip : list) {
try {
ip += (" | " + ResourceBundle.getBundle("ip").getString(ip));
} catch (MissingResourceException e) {
ip += " | 未知IP";
}
System.out.println(" \t " + ip);
}
}
//业务逻辑
private void business() throws InterruptedException {
Thread.sleep(Long.MAX_VALUE);
}
//启动客户端监听
public void start() {
try {
getConnection();
//registered();
business();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
package zookeeper.project.client;
import zookeeper.project.driver.Client;
public class Demo {
public static void main(String[] args) {
new Client().start();
}
}
package zookeeper.project.server;
import zookeeper.project.driver.Server;
public class ServerDemo1 {
public static void main(String[] args) {
new Server().start();
}
}
控制台输出
=========== 2020-06-24 18:21:28 ===========
192.168.42.201 | 未知IP
192.168.2.128 | WangJun
192.168.2.129 | LiYuHang
ip后面的备注通过配置文件读取,相当于ip白名单,将合法的ip写入配置文件中即可