Zookeeper
docker部署zk集群
docker-componse文件
version: '3.1'
services:
zoo1:
image: zookeeper
restart: always
hostname: zoo1
ports:
- 2181:2181
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
zoo2:
image: zookeeper
restart: always
hostname: zoo2
ports:
- 2182:2181
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
zoo3:
image: zookeeper
restart: always
hostname: zoo3
ports:
- 2183:2181
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
启动
docker-compose -f xxx.yml up -d
查看
docker ps
进入到容器中,查看启动后对应的角色
# 进入容器
docker exec -it 容器id /bin/bash
cd /bin
zkServer.sh status
客户端连接
PrettyZoo客户端 下载地址
springboot整合zk
Curator 功能分两大类,一类是对 ZooKeeper 的一些基本命令的封装,比如增删改查,即 Framework 模块;另一类是他的高级特性,即 Recipes 模块。
Curator Recipes的使用
Recipes 模块主要有 Elections (选举)、Locks (锁)、Barriers (关卡)、Atomic (原子量)、Caches、Queues 等
Electtions 选举
选举主要依赖于 LeaderSelector 和 LeaderLatch 两个类。前者是所有存活的客户端不间断的轮流做 Leader。后者是一旦选举出 Leader,除非有客户端挂掉重新触发选举,否则不会交出领导权。这两者在实现上是可以切换的。
Locks(分布式锁)
curator 提供了InterProcessMutex来实现zk的分布式锁,他用acquire获取锁 release释放锁。
ZooKeeper分布式锁:
优点
ZooKeeper分布式锁(如InterProcessMutex),能有效地解决分布式问题,不可重入问题,使用起来也较为简单
缺点
ZooKeeper实现的分布式锁,性能并不太高。
因为每次在创建锁和释放锁的过程中,都要动态创建、销毁暂时节点来实现锁功能,
Zk中创建和删除节点只能通过Leader(主)服务器来执行,然后Leader服务器还需要将数据同步到所有的Follower(从)服务器上,这样频繁的网络通信,系统性能会下降。
总之,在高性能、高并发的应用场景下,不建议使用ZooKeeper的分布式锁,如果在并发量不是太高的应用场景中,还是推荐使用ZooKeeper的分布式锁。
目前分布式锁,比较成熟、主流的方案有两种:
基于Redis的分布式锁。适用于并发量很大、性能要求很高而可靠性问题可以通过其他方案去弥补的场景。
基于ZooKeeper的分布式锁。适用于高可靠,而并发量不是太高的场景
在选型时,选择适合于自己业务场景的方案即可。
<dependencies>
<!-- SpringBoot 核心包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<!-- curator-framework-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.7.1</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.7.1</version>
<exclusions>
<exclusion>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
application.yml
zk:
url: 192.168.88.141:2181,192.168.88.141:2182,192.168.88.141:2183
timeOut: 60000
baseSleepTimeMs: 5000
retryCount: 5
ZKConfig
package com.example.config;
import com.google.common.annotations.Beta;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class ZKConfig {
@Value("${zk.url}")
private String zkUrl;
// session 超时时间
@Value("${zk.timeOut}")
private int timeOut;
// zkclient 重试间隔时间
@Value("${zk.baseSleepTimeMs}")
private int baseSleepTimeMs;
//zkclient 重试次数
@Value("${zk.retryCount}")
private int retryCount;
@Bean
public CuratorFramework init() {
CuratorFramework zkCuratorFramework = CuratorFrameworkFactory.builder()
.connectString(zkUrl)
.sessionTimeoutMs(timeOut)
.retryPolicy(new ExponentialBackoffRetry(baseSleepTimeMs, retryCount))
.namespace("zk").build();
zkCuratorFramework.start();
CuratorFrameworkState state = zkCuratorFramework.getState();
log.info("zk collection status",state);
return zkCuratorFramework;
}
}
ZKUtils
package com.example.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.*;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
@Component
@Slf4j
public class ZKUtils {
@Autowired
private CuratorFramework curatorFramework;
/**
* @param path
* @param value
* @Description: 创建路径
* @Date: 2020-08-22 15:15
*/
public String createNode(String path, String value) throws Exception {
return createNode(path, value, true);
}
public String createNode(String path, String value, Boolean isEphemeral) throws Exception {
if (null == curatorFramework) {
throw new RuntimeException("there is not connect to zkServer...");
}
String node = curatorFramework
.create()
.creatingParentsIfNeeded()
.withMode(isEphemeral.equals(true) ? CreateMode.EPHEMERAL_SEQUENTIAL : CreateMode.PERSISTENT_SEQUENTIAL) // 临时顺序节点/持久顺序节点
.forPath(path, value.getBytes());
log.info("create node : {}", node);
return node;
}
/**
* @param path
* @Description: 删除节点信息
* @Date: 2020-08-22 16:01
*/
public void deleteNode(String path) throws Exception {
if (null == curatorFramework) {
throw new RuntimeException("there is not connect to zkServer...");
}
curatorFramework.delete()
.guaranteed() // 保障机制,若未删除成功,只要会话有效会在后台一直尝试删除
.deletingChildrenIfNeeded() // 若当前节点包含子节点,子节点也删除
.forPath(path);
log.info("{} is deleted ", path);
}
/**
* 判断znode是否存在,Stat就是对znode所有属性的一个映射,stat=null表示节点不存在
*
* @param path
* @return
*/
public Stat isExists(String path) throws Exception {
if (null == curatorFramework) {
throw new RuntimeException("there is not connect to zkServer...");
}
return curatorFramework.checkExists().forPath(path);
}
/**
* 查询子节点
*
* @param path
* @return
* @throws Exception
*/
public List<String> getChildren(String path) throws Exception {
if (null == curatorFramework) {
throw new RuntimeException("there is not connect to zkServer...");
}
return curatorFramework.getChildren()
.forPath(path);
}
/**
* @param path
* @Description: 获取节点存储的值
* @Date: 2020-08-22 16:11
*/
public String getNodeData(String path) throws Exception {
if (null == curatorFramework) {
throw new RuntimeException("there is not connect to zkServer...");
}
Stat stat = new Stat();
byte[] bytes = curatorFramework.getData().storingStatIn(stat).forPath(path);
log.info("{} data is : {}", path, new String(bytes));
log.info("current stat version is {}, createTime is {}", stat.getVersion(), stat.getCtime());
return new String(bytes);
}
/**
* @param path
* @param value
* @Description: 设置节点 数据
* @Date: 2020-08-22 16:17
*/
public void setNodeData(String path, String value) throws Exception {
if (null == curatorFramework) {
throw new RuntimeException("there is not connect to zkServer...");
}
Stat stat = curatorFramework.checkExists().forPath(path);
if (null == stat) {
log.info(String.format("{} Znode is not exists", path));
throw new RuntimeException(String.format("{} Znode is not exists", path));
}
String nodeData = getNodeData(path);
curatorFramework.setData().withVersion(stat.getVersion()).forPath(path, value.getBytes());
log.info("{} Znode data is set. old vaule is {}, new data is {}", path, nodeData, value);
}
/**
* @param path
* @Description: 创建 给定节点的监听事件 监听一个节点的更新和创建事件(不包括删除)
* @Date: 2020-08-22 16:47
*/
public void addWatcherWithNodeCache(String path) throws Exception {
if (null == curatorFramework) {
throw new RuntimeException("there is not connect to zkServer...");
}
// dataIsCompressed if true, data in the path is compressed
NodeCache nodeCache = new NodeCache(curatorFramework, path, false);
NodeCacheListener listener = () -> {
ChildData currentData = nodeCache.getCurrentData();
log.info("{} Znode data is chagnge,new data is --- {}", currentData.getPath(), new String(currentData.getData()));
};
nodeCache.getListenable().addListener(listener);
nodeCache.start();
}
/**
* @param path 给定节点
* @Description: 监听给定节点下的子节点的创建、删除、更新
* @Date: 2020-08-22 17:14
*/
public void addWatcherWithChildCache(String path) throws Exception {
if (null == curatorFramework) {
throw new RuntimeException("there is not connect to zkServer...");
}
//cacheData if true, node contents are cached in addition to the stat
PathChildrenCache pathChildrenCache = new PathChildrenCache(curatorFramework, path, false);
PathChildrenCacheListener listener = (curatorFramework, event) -> {
log.info("event path is --{} ,event type is {}", event.getData().getPath(), event.getType());
};
pathChildrenCache.getListenable().addListener(listener);
// StartMode : NORMAL BUILD_INITIAL_CACHE POST_INITIALIZED_EVENT
// NORMAL:异步初始化, BUILD_INITIAL_CACHE:同步初始化, POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发事件
pathChildrenCache.start(PathChildrenCache.StartMode.NORMAL);
}
/**
* @param path
* @Description: 监听 给定节点的创建、更新(不包括删除) 以及 该节点下的子节点的创建、删除、更新动作。
* @Date: 2020-08-22 17:35
*/
public void addWatcherWithTreeCache(String path) throws Exception {
if (null == curatorFramework) {
throw new RuntimeException("there is not connect to zkServer...");
}
TreeCache treeCache = new TreeCache(curatorFramework, path);
TreeCacheListener listener = (curatorFramework, event) -> {
log.info("节点路径 --{} ,节点事件类型: {} , 节点值为: {}", Objects.nonNull(event.getData()) ? event.getData().getPath() : "无数据", event.getType());
};
treeCache.getListenable().addListener(listener);
treeCache.start();
}
}