docker 搭建zookper集群,快照虚拟机多机模拟
网上有很多zk集群搭建的,发现踩了很多坑,记录一下自己的搭建:
虚拟机配置
快照克隆:
关闭防火墙。
zk集群正式搭建
查询虚拟机ip地址:
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 {@Autowiredprivate 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 即可,不需要 SEQUENTIALString 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 {@Autowiredprivate ZookeeperServiceRegistry serviceRegistry;@Overridepublic void run(String... args) throws Exception {serviceRegistry.registerService("my-service", "127.0.0.1:8080");System.out.println("Service registered successfully.");}
}