本文主要讲解Zookeeper经典使用场景以及Zookeeper原生的客户端和Apache Curator开源客户端使用,以及Zookeeper集群的配置。
Zookeeper的使用主要包括:
配置中心原理就是对某个节点监听,只要节点有变化就会通知客户端,获取变化的数据。
效果:
我直接把我的pom文件贴出来了,其实只需要一个zookeeper即可。其余是属于项目补充。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入zookeeper-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.3</version>
</dependency>
<!--引入lombok 方便处理类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入Junit测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!--引入Jackson用于序列化类-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!--springboot-test的包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
创建一个配置类:
@Data //注在类上,提供类的get、set、equals、hashCode、canEqual、toString方法
@NoArgsConstructor //注在类上,提供类的无参构造
public class MyConfig {
private String key;
private String name;
}
创建Zookeeper客户端(感觉不好理解的代码都加了注释)
@Slf4j
public class ZookeeperClient {
@Test
public void test01() throws IOException, InterruptedException, KeeperException {
final String CONNECT_STRING = "10.10.2.21:2181";
final int SESSION_TIME_OUT = 30*1000;
/*
倒计数器:多个线程之间的同步工具类。
主要是用来控制线程等待,可以让某一个线程等待直到倒计数 结束,再开始执行。
下面主线程就是用countDownLatch.await();
来阻塞主线程的执行,直到Zookeeper线程创建完毕才会继续执行主线程之后的代码
*/
final CountDownLatch countDownLatch = new CountDownLatch(1);
final ZooKeeper zooKeeper = new ZooKeeper(CONNECT_STRING, SESSION_TIME_OUT, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
if(watchedEvent.getType() == (Event.EventType.None) && watchedEvent.getState() == Event.KeeperState.SyncConnected){
log.info("连接已建立");
countDownLatch.countDown();//递减锁的个数,当计数达到零时释放所有等待的线程
}
}
});
/**
* 为什么要用CountDownLatch?因为创建Zookeeper客户端是异步线程并且是守护线程
* (this.createConnection()方法中new了两个线程new ClientCnxn.SendThread()和 new ClientCnxn.EventThread()
* 点进去会发现都是守护线程 this.setDaemon(true);)
* 也就意味着当主线程结束的时候,zookeeper的线程也会随之结束,这个时候zookeeper可能还没链接成功那
*/
countDownLatch.await();//阻塞当前线程,直到计数器值为零。
final MyConfig myConfig = new MyConfig();
myConfig.setKey("anyKey");
myConfig.setName("anyName");
//使用的是jackson序列化工具类
final ObjectMapper objectMapper = new ObjectMapper();
final byte[] bytes = objectMapper.writeValueAsBytes(myConfig);
//创建节点
final String s = zooKeeper.create("/myconfig", bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
//这个watcher提出来写是因为,zookeeper对节点的监听是一次性的,如果想要实现循环监听的话,需要循环调用。即在watcher中zooKeeper.getData()
final Watcher watcher = new Watcher() {
@SneakyThrows
@Override
public void process(WatchedEvent event) {
log.info(".......");
if(event.getType() == Event.EventType.NodeDataChanged && event.getPath() != null && event.getPath().equals("/myconfig")){
log.info("path:{}发生了变化",event.getPath());
final byte[] data = zooKeeper.getData("/myconfig", this, null);
final MyConfig newConfig = objectMapper.readValue(new String(data), MyConfig.class);
log.info("数据发生变化:{}",newConfig);
}
}
};
//获取数据并创建监听
byte[] data = zooKeeper.getData("/myconfig", watcher, null);//如果不需要监听节点变化,watcher直接复制为null就好了
final MyConfig originalMyConfig = objectMapper.readValue(new String(data), MyConfig.class);
log.info("原始数据:{}",originalMyConfig);
//之所以要一直睡眠是因为zookeeper客户端的线程是守护线程,所以主线程结束,zookeeper客户端就会结束,所以为了达到客户端一直能够连接上服务端所以主线程必须一直睡眠
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}
}
Zookeeper这么多实例化方法中主要参数详解:
对于每个参数的含义对应的构造方法上面都有备注
比如:connectString
使用一个抽象类用来创建Zookeeper连接
这是getData的方法用来获取指定节点的数据。
setData方法用来设置节点数据
pom文件添curator-recipes对应的maven即可。
然后连接zookeeper的主要代码是
<<1:>> 递归创建节点
<<2:>> protection模式创建节点
Zookeeper集群模式有三种不同类型的角色;
Leader: 处理所有的事务请求(写请求),可以处理读请求,集群中只能有一个Leader
Follower: 只能处理读请求,同时作为Leader的候选节点,即如果Leader宕机,Follower节点要参与薪的Leader选举中,有可能称为薪的Leader节点。
Observer: 只能处理读请求。不能参与选举。
目前先搭建一个伪集群:
第一步:先配置事务数据存储路径,每个myid里面的数字都代表着它的服务对应的ID
第二步:
配置对应的配置文件
第一个端口2001是Leader和Follower节点之间通信的端口
第二个端口3001是进行集群选举的时候需要的端口
总共配置四个服务,第四个配置成Observer节点