网上有很多zk集群搭建的,发现踩了很多坑,记录一下自己的搭建:
虚拟机配置

快照克隆:
关闭防火墙。
zk集群正式搭建
查询虚拟机ip地址:
bash
ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v 127.0.0.1
挂载文件地址:
mkdir -p /mydata/zookeeper/data # 数据挂载目录
mkdir -p /mydata/zookeeper/conf # 配置挂载目录
mkdir -p /mydata/zookeeper/logs # 日志挂载目录
关键点,修改配置:
echo 1 > /mydata/zookeeper/conf/myid
这里是指定zk id,选举需要使用,单机不需要
vim /mydata/zookeeper/conf/zoo.cfg
zoo.cfg创建zk配置,配置如下:
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/data
dataLogDir=/logs
clientPort=2181
server.1=172.30.123.67:2888:3888
server.2=172.30.123.60:2888:3888
server.3=172.30.115.33:2888:3888
网上很多都是hostname:sudo hostnamectl set-hostname zk2
或者修改/etc/hosts这个文件,通过名称映射ip,这个我也失败了,解析不了。
docker 执行命令:
docker run -d --name zk \
--network host \
--restart=unless-stopped \
-v /mydata/zookeeper/data:/data \
-v /mydata/zookeeper/logs:/logs \
-v /mydata/zookeeper/conf:/conf \
-e ZOO_MY_ID=$(cat /mydata/zookeeper/data/myid) \
zookeeper:3.5.7
这个有点坑,我试了其他博主的博客,大多都是直接-p去映射,但是我这边尝试一直链接不是,采用 --network host。
--network host 让容器直接使用宿主机网络,端口 2181/2888/3888 不用再 -p 映射。
集群验证:
docker exec zk zkServer.sh status
显示:
ZooKeeper JMX enabled by default
Using config: /conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower
follower就是选举角色,到这里就成功了,其他的虚拟机上还有leader。
简单使用java连接集群
pom.xml:
添加依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.9.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.9.0</version>
</dependency>
生产客户端bean:
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
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;
@Configuration
public class ZookeeperConfig {
@Value("${curator.connectString}")
private String connectString;
@Value("${curator.sessionTimeoutMs}")
private int sessionTimeoutMs;
@Value("${curator.connectionTimeoutMs}")
private int connectionTimeoutMs;
@Value("${curator.retryCount}")
private int retryCount;
@Value("${curator.elapsedTimeMs}")
private int elapsedTimeMs;
@Bean(initMethod = "start", destroyMethod = "close")
public CuratorFramework curatorFramework() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(elapsedTimeMs, retryCount);
return CuratorFrameworkFactory.builder()
.connectString(connectString)
.sessionTimeoutMs(sessionTimeoutMs)
.connectionTimeoutMs(connectionTimeoutMs)
.retryPolicy(retryPolicy)
.build();
}
}
注册服务bean:
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.nodes.PersistentNode;
import org.apache.zookeeper.CreateMode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class ZookeeperServiceRegistry {
@Autowired
private CuratorFramework curatorFramework;
/**
* 注册服务节点
* @param serviceName 服务名
* @param serviceAddress 实例地址,如 127.0.0.1:8080
* @return PersistentNode 句柄,方便后续关闭
*/
public PersistentNode registerService(String serviceName, String serviceAddress) throws Exception {
// 1. 先保证父节点存在
String parent = "/services/" + serviceName;
Stat stat = curatorFramework.checkExists().forPath(parent);
if (stat == null) {
curatorFramework.create()
.creatingParentContainersIfNeeded()
.withMode(CreateMode.PERSISTENT)
.forPath(parent);
}
// 2. 子节点路径,使用 EPHEMERAL 即可,不需要 SEQUENTIAL
String childPath = parent + "/" + serviceAddress;
// 3. 创建临时节点
PersistentNode node = new PersistentNode(
curatorFramework,
CreateMode.EPHEMERAL, // 会话断后自动删除
false, // false 表示不监听子节点
childPath,
serviceAddress.getBytes());
node.start();
if (!node.waitForInitialCreate(10, TimeUnit.SECONDS)) {
throw new IllegalStateException("Failed to create node at " + childPath);
}
return node;
}
/**
* 注销服务节点(若仍持有 PersistentNode,先关闭再删除)
*/
public void unregisterService(String serviceName, String serviceAddress) throws Exception {
String path = "/services/" + serviceName + "/" + serviceAddress;
// 如果之前保存了 PersistentNode,先关闭
// node.close();
curatorFramework.delete().forPath(path);
}
}
使用spring CommandLineRunner 钩子,主动注册
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class ServiceRegistrationRunner implements CommandLineRunner {
@Autowired
private ZookeeperServiceRegistry serviceRegistry;
@Override
public void run(String... args) throws Exception {
serviceRegistry.registerService("my-service", "127.0.0.1:8080");
System.out.println("Service registered successfully.");
}
}