言于头: 前几天在学习Flink的时候,看到了flink利用zookeeper实现jobmanager的高可用,所以下来我就在思考,我们得业务是否也可以利用这个来提高核心功能的稳定性呢?想到平常接触到的kfk集群啥的,在加上在网上找的相关资料,所以本次尝试利用zk的封装模块Curator来实现业务的HA。下面为本次记录过程。
1 下载Zookeeper
下载地址:http://archive.apache.org/dist/zookeeper/,本次选择版本 apache-zookeeper-3.6.0-bin.tar.gz (如图)。
2 安装Zookeeper
将下载后的apache-zookeeper-3.6.0-bin.tar.gz放到服务器上相关目录
执行解压:
$ tar zxvf apache-zookeeper-3.6.0-bin.tar.gz
解压后进入,创建数据目录
$ cd apache-zookeeper-3.6.0-bin
$ mkdir data
进入配置文件目录,复制一个配置文件
$ cp zoo_sample.cfg zoo.cfg
修改配置文件 (修改部分如图)
$ vim zoo.cfg
修改完毕退回上层启动zookeeper
$ ./bin/zkServer.sh start
修改配置截图:
启动Zookeeper截图:
启动Zookeeper后状态检查截图: 执行状态查询命令显示如下则启动成功.
3 通过Curator实现业务HA
3.1 环境准备
1.项目结构如下所示,所需jar可在仓库https://search.maven.org/ 里面下载,主要为Zookeeper相关三个jar(可在上述安装包lib中获取)、Curator所需的jar、日志以及单元测试jar。
2.代码部分为一个业务集成类 + 单元测试类(模拟多机部署)。
3.此处的HA实现是使用Curator的LeaderLatch类,听说它还有LeaderSelector类可实现。两者区别为:LeaderLatch是一旦选举出Leader,除非有客户端挂掉重新触发选举,否则不会交出领导权,而LeaderSelector是所有存活的客户端不间断的轮流做Leader,机会均等。
3.2 BKZkLeaderLatch类实现
package com.bkdemo.zk;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.leader.LeaderLatch;
import org.apache.curator.framework.recipes.leader.LeaderLatchListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
/**
* @Description
* @date: 2020/11/27 14:54
*/
public class BKZkLeaderLatch {
private static final Logger logger = LoggerFactory.getLogger(BKZkLeaderLatch.class);
private static final Integer INT_3=3;
private static final Integer INT_1000=1000;
private static final Integer INT_6000=6000;
private CuratorFramework zkClient;
private LeaderLatch leaderLatch;
private String connectString;
private String masterKey;
private String zkId;
/**
* @Description
* connectString:zk连接串
* masterKey:节点目录,多机部署时此处需要相同
* zkId:机器ID
* @date: 2020/11/27 14:54
*/
public BKZkLeaderLatch(String connectString, String masterKey, String zkId) {
this.connectString = connectString;
this.masterKey = masterKey;
this.zkId = zkId;
}
public void start() {
try {
String id = String.format("BKZkLeaderLatch#%s-#%s", InetAddress.getLocalHost().getHostAddress(), zkId);
logger.info("zk {} 客户端初始化... server:{}, masterKey:{}",id,connectString,masterKey);
RetryPolicy retryPolicy = new ExponentialBackoffRetry(INT_1000, INT_3);
zkClient = CuratorFrameworkFactory.builder().connectString(connectString)
.sessionTimeoutMs(INT_6000).retryPolicy(retryPolicy).build();
logger.info("zk 客户端启动....");
zkClient.start();
leaderLatch = new LeaderLatch(zkClient, masterKey,id);
LeaderLatchListener leaderLatchListener = new LeaderLatchListener() {
@Override
public void notLeader() {
logger.info("客户端: {} 不是主节点. ",id);
}
@Override
public void isLeader() {
logger.info("客户端: {} 成为主节点. YEAH!",id);
}
};
leaderLatch.addListener(leaderLatchListener);
logger.info("leaderLatch启动....");
leaderLatch.start();
// 设置数据标志(无具体作用、只为了测试其API)
zkClient.setData().forPath(masterKey,id.getBytes());
} catch(Exception e) {
logger.error("客户端初始化异常. "+e.getMessage(),e);
}
}
/**
* @Description 确认当前是否是Leader
* @date: 2020/11/27 14:54
*/
public boolean isLeader() {
return leaderLatch.hasLeadership();
}
public CuratorFramework getClient(){
return zkClient;
}
public LeaderLatch getLatch(){
return leaderLatch;
}
}
3.3 ZKTestHA类实现
package com.bkdemo.zk;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @Description 测试利用zk实现高可用样例 主从选择
* @date: 2020/11/27 14:35
*/
public class ZKTestHA {
private static final Logger LOGGER = LoggerFactory.getLogger(ZKTestHA.class);
// ZK地址
private static final String ZK_CONNECT_STR="24.**.**.66:2181";
// 存储路径
private static final String ZK_MASTER_PATH="/ZKTestHA01";
@Test
public void testZkInstantce1() throws Exception{
BKZkLeaderLatch bkZkLeaderLatch = new BKZkLeaderLatch(ZK_CONNECT_STR, ZK_MASTER_PATH, "1");
bkZkLeaderLatch.start();
while (true){
Thread.sleep(1000l);
}
}
@Test
public void testZkInstantce2() throws Exception{
BKZkLeaderLatch bkZkLeaderLatch = new BKZkLeaderLatch(ZK_CONNECT_STR, ZK_MASTER_PATH, "2");
bkZkLeaderLatch.start();
while (true){
Thread.sleep(1000l);
}
}
@Test
public void testZkInstantce3() throws Exception{
BKZkLeaderLatch bkZkLeaderLatch = new BKZkLeaderLatch(ZK_CONNECT_STR, ZK_MASTER_PATH, "3");
bkZkLeaderLatch.start();
while (true){
Thread.sleep(1000l);
}
}
}
3.4 启动结果
启动 ZKTestHA 类里面的单元测试后可以在服务器上的zk安装目录下执行相关命令观察到我们保存的节点信息 /ZKTestHA01(如图)。
登录zk
$ ./bin/zkCli.sh -server 24.X.X.66:2181
在单元测试 ZKTestHA类启动三个后,可以在 /ZKTestHA01节点下看到三个子节点,这代表着有三个客户端共同使用该节点,并且观察每个节点里面的数据就是我们在BKZkLeaderLatch 中设置的值(如图)。
在单元测试 ZKTestHA类关闭1节点后,可以发现日志显示重新选择了3节点作为Leader(3.1中日志显示1节点最初为Leader),并且在服务器上看到的节点信息会相应的减少1节点(如图),表明此时在程序崩溃后ZK重新选择了Leader。
尽于尾:最后,本次测试基本完毕,但是却有一个小疑问:上述单元测试在全部停止后一段时间(据观察是将近5、6秒左右)在服务器上查看相关节点信息就全部没有了,查询资料显示Zookeeper的节点创建模式分为四种:
1.PERSISTENT:持久化
2.PERSISTENT_SEQUENTIAL:持久化并且带序列号
3.EPHEMERAL:临时
4.EPHEMERAL_SEQUENTIAL:临时并且带序列号
那么此处通过LeaderLatch创建的节点属于哪一类呢?是临时么?