Zookeeper集群配置详解及Curator客户端高可用连接测试

Zookeeper 集群模式一共有三种类型的角色

Leader: 处理所有的事务请求(写请求),可以处理读请求,集群中只能有一个Leader

Follower: 只能处理读请求,同时作为 Leader的候选节点,即如果Leader宕机,Follower节点要参与到新的Leader选举中,有可能成为新的Leader节点。Folllower节点接收到写请求后会将请求转发到Leader节点上。

Observer只能处理读请求。不能参与选举

在这里插入图片描述
在这里插入图片描述

observer节点存在的意义:和ZK的一致性协议有关系,写数据的时候并不是只写到leader节点上就说明写成功了,存在一个过半机制。即Leader节点数据写成功后,需要将数据同步到其他follower节点上,只有集群中过半的机器上都保存了这个数据,ZK才会通知客户端数据保存成功。

注意,Observer节点不参与过半机制,只提供读请求。

一、配置zookeeper集群

1、集群搭建

本例搭建的是伪集群模式,即一台机器上启动四个zookeeper实例组成集群,真正的集群模式无非就是实例IP地址不同,搭建方法没有区别。

Step1:配置JAVA环境,检验环境:保证是jdk7 及以上即可

java -version

Step2:下载并解压zookeeper

wget https://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.5.9/apache-zookeeper-3.5.9-bin.tar.gz
tar -zxvf apache-zookeeper-3.5.9-bin.tar.gz
cd  apache-zookeeper-3.5.9-bin

Step3:重命名 zoo_sample.cfg文件

cp conf/zoo_sample.cfg conf/zoo-1.cfg

Step4:修改配置文件zoo-1.cfg,原配置文件里有的,修改成下面的值,没有的则加上

# vim conf/zoo-1.cfg
dataDir=/usr/local/zookeeper/apache-zookeeper-3.5.9-bin/data/zookeeper-1
clientPort=2181

# // participant 可以不用写,默认就是participant
# 第一个端口是通信端口
server.1=192.168.131.171:2001:3001 
server.2=192.168.131.171:2002:3002
server.3=192.168.131.171:2003:3003
server.4=192.168.131.171:2004:3004:observer

配置完成后,将zoo-1.cfg拷贝三份,分别是zoo-2.cfg、zoo-3.cfg、zoo-4.cfg, 修改里面的dataDir目录和端口号:

dataDir=/usr/local/zookeeper/apache-zookeeper-3.5.9-bin/data/zookeeper-2
clientPort=2182

dataDir=/usr/local/zookeeper/apache-zookeeper-3.5.9-bin/data/zookeeper-2
clientPort=2183

dataDir=/usr/local/zookeeper/apache-zookeeper-3.5.9-bin/data/zookeeper-4
clientPort=2184

注意,在配置的ip和端口号后面不能加空格!!!否则可能出错说无法解析IP地址!

配置说明

  • tickTime:用于配置Zookeeper中最小时间单位的长度,很多运行时的时间间隔都是使用tickTime的倍数来表示的。
  • initLimit:该参数用于配置Leader服务器等待Follower启动,并完成数据同步的时间。Follower服务器再启动过程中,会与Leader建立连接并
  • 完成数据的同步,从而确定自己对外提供服务的起始状态。Leader服务器允许Follower再initLimit 时间内完成这个工作。
  • syncLimit:Leader 与Follower心跳检测的最大延时时间
  • dataDir:顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。
  • clientPort:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。
  • server.A=B:C:D:E 其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C
    表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader
    服务器挂了,需要一个端口来重新进行选举,选出一个新的
    Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper
    实例通信端口号不能一样,所以要给它们分配不同的端口号。如果需要通过添加不参与集群选举以及事务请求的过半机制的
    Observer节点,可以在E的位置,添加observer标识。

Step5:标识Server ID

创建四个文件夹/usr/local/zookeeper/apache-zookeeper-3.5.9-bin/data/zookeeper-1,/usr/local/zookeeper/apache-zookeeper-3.5.9-bin/data/zookeeper-2,/usr/local/zookeeper/apache-zookeeper-3.5.9-bin/data/zookeeper-3,/usr/local/zookeeper/apache-zookeeper-3.5.9-bin/data/zookeeper-4,在每个目录中创建文件myid 文件,写入当前实例的server id,即1,2,3,4

echo 1 > zookeeper-1/myid
echo 2 > zookeeper-2/myid
echo 3 > zookeeper-3/myid
echo 4	 > zookeeper-4/myid
// ===========或者使用下面的方法创建===========================
cd  /usr/local/zookeeper/apache-zookeeper-3.5.9-bin/data/zookeeper-1
vim myid
 1 
cd /usr/local/zookeeper/apache-zookeeper-3.5.9-bin/data/zookeeper-2
vim myid
 2 
cd /usr/local/zookeeper/apache-zookeeper-3.5.9-bin/data/zookeeper-3
vim myid
 3 
cd /usr/local/zookeeper/apache-zookeeper-3.5.9-bin/data/zookeeper-4
vim myid
4

Step6:启动四个zookeeper实例

/usr/local/zookeeper/apache-zookeeper-3.5.9-bin/bin/zkServer.sh  start /usr/local/zookeeper/apache-zookeeper-3.5.9-bin/conf/zoo-1.cfg
 
/usr/local/zookeeper/apache-zookeeper-3.5.9-bin/bin/zkServer.sh  start /usr/local/zookeeper/apache-zookeeper-3.5.9-bin/conf/zoo-2.cfg

/usr/local/zookeeper/apache-zookeeper-3.5.9-bin/bin/zkServer.sh  start /usr/local/zookeeper/apache-zookeeper-3.5.9-bin/conf/zoo-3.cfg

/usr/local/zookeeper/apache-zookeeper-3.5.9-bin/bin/zkServer.sh  start /usr/local/zookeeper/apache-zookeeper-3.5.9-bin/conf/zoo-4.cfg

Step7:检测集群状态,也可以直接用命令 zkServer.sh status conf/zoo1.cfg 进行每台服务的状态查询

/usr/local/zookeeper/apache-zookeeper-3.5.9-bin/bin/zkServer.sh  status /usr/local/zookeeper/apache-zookeeper-3.5.9-bin/conf/zoo-1.cfg

......

/usr/local/zookeeper/apache-zookeeper-3.5.9-bin/bin/zkServer.sh  status /usr/local/zookeeper/apache-zookeeper-3.5.9-bin/conf/zoo-4.cfg

在这里插入图片描述

可以看到节点的信息,2181是leader节点,2182和2183是follower节点,2184是observer节点。

因为ZK是使用java写的,也可以使用jps命令查看进程:

在这里插入图片描述
也可以通过 查看/zookeeper/config 节点数据来查看集群配置。

zk: 192.168.131.171:2181,192.168.131.171:2182,192.168.131.171:2183(CONNECTED) 1] get /zookeeper/config

在这里插入图片描述

2、登录集群客户端

可以连接集群或者只连接某一个服务。连接某一个服务的缺点是一旦这个服务宕机,客户端连接也将不可用。使用集群模式连接的话,只要leader和follower服务个数过半,就都可以连接。

过半指的是可以参与选举的服务个数要超过半数。如果此时leader加follower节点个数是3, 3除以2是1,没有达到过半,所以还需要加上1,即为2. 所以需要有2个服务是正常的,集群客户端才可用。

所以再集群中,推荐使用奇数台服务。因为偶数台服务会导致服务不可用的风险加高。比如3台服务,根据过半原则,只需要有2台服务正常就可用。如果是4台服务,根据过半原则,必须有3台服务都正常,服务才可用。但是如果是5台机器的话,也是只需要3台服务可用,就可以保证可用性。而且,服务个数越多,需要同步的节点也就越多,这样会影响性能。

bin/zkCli.sh -server ip1:port1,ip2:port2,ip3:port3 

// 实例
/usr/local/zookeeper/apache-zookeeper-3.5.9-bin/bin/zkCli.sh -server 192.168.131.171:2181,192.168.131.171:2182,192.168.131.171:2183

二、zookeeper客户端连接集群

zk客户端支持连接集群和服务宕机后的重连机制。但是对于重连,需要在捕获异常后自处理:

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@Slf4j
public class ZkClusterTest {

    private static final String CLUSTER_CONNECT_STR = "192.168.131.171:2181,192.168.131.171:2182,192.168.131.171:2183,192.168.131.171:2184";

    private static final Integer SESSION_TIMEOUT = 60 * 1000;

    private static ZooKeeper zooKeeper = null;

    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    private static ObjectMapper objectMapper = new ObjectMapper();

    // 初始化zk
    @Before
    public void init() throws IOException {
        log.info("try to connect to zk server");
        zooKeeper = new ZooKeeper(CLUSTER_CONNECT_STR, SESSION_TIMEOUT, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                // 根据事件类型判断是否连接成功
                if (event != null && event.getType().equals(Event.EventType.None)
                        && event.getState().equals(Event.KeeperState.SyncConnected)) {
                    // 当连接建立好之后
                    countDownLatch.countDown();
                    log.info("zk 连接成功...");
                }
            }
        });
    }


    @Test
    public void testReconnect() throws InterruptedException {
        // 不断的去拿这个节点的数据
        while (true) {
            Stat stat = new Stat();

            try {
                byte[] data = zooKeeper.getData("/testReconnect", false, stat);
                log.info("get data: {}", new String(data));
                // 每隔5s拉取一次数据
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
                log.error("数据获取异常: {}", e.getMessage());
                log.info("开始重连...");
                while (true) {
                    log.info("zookeeper status: {}", zooKeeper.getState().name());
                    if (zooKeeper.getState().isConnected()) {
                        break;
                    }
                    TimeUnit.SECONDS.sleep(3);
                }
            }
        }
    }


    @After
    public void holdOnzk() throws InterruptedException {
        // 不要让程序结束. 然后我们在客户端中去修改数据
        TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
    }
}

在这里插入图片描述

启动上面的测试方法testReconnect后,zk客户端会不断的去查询数据。这时候我们先确定出此时客户端连接的server是哪一个:

在这里插入图片描述
从上面的端口号2181这个连接,我们推测现在客户端连接的应该是2182的服务。其实从IDEA的日志中我们也看出来了,连接的是2181.

[main-SendThread(192.168.131.171:2181)]

那我们此时将2181这个服务停掉,然后再看IDEA中的日志:

// 2181对应的配置文件是zoo-1.cfg
 /usr/local/zookeeper/apache-zookeeper-3.5.9-bin/bin/zkServer.sh  stop /usr/local/zookeeper/apache-zookeeper-3.5.9-bin/conf/zoo-1.cfg

在这里插入图片描述
可以看到,当2181被管的时候,ZK客户端马上进行了重连,连接到了2182这个节点上!!!

此时我们再来看看这些服务的状态:
在这里插入图片描述
发现,2183已经被选举为新的leader节点了。

注意,并不是说客户端连接的一定是leader节点!连接到follower节点之后的写操作,会由这个follower节点转发到leader节点去执行,leader节点执行后,将数据同步到follower节点,并判断是否已经有半数的服务同步了这个数据。如果是,则将保存成功的消息发动到客户端。

ZK是基于sessionId连接的,只有sessin过期后,才无法访问访问临时数据。

三、Curator连接集群

ZK客户端连接集群后,需要自己的异常中去判断连接状态,代码不是很优雅。

不过我们可以使用Curator去连接集群。

import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
public class CuratorClusterTest {
    private static final String CLUSTER_CONNECT_STR = "192.168.131.171:2181,192.168.131.171:2182,192.168.131.171:2183,192.168.131.171:2184";

    private static final Integer CONNECTION_TIMEOUT_MS = 5000;

    private static CuratorFramework curatorFramework;


    @Before
    public void init () {
        ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(5000, 30);

        curatorFramework = CuratorFrameworkFactory.builder()
                .connectString(CLUSTER_CONNECT_STR)
                .sessionTimeoutMs(CONNECTION_TIMEOUT_MS)
                .canBeReadOnly(true)
                .retryPolicy(retryPolicy)
                .build();

        curatorFramework.getConnectionStateListenable().addListener((client, newState) -> {
            if (newState == ConnectionState.CONNECTED) {
                log.info("连接成功!");
            }
        });

        log.info("连接中...");

        curatorFramework.start();
    }

    @Test
    public void testCluster() throws Exception {
        String path = "/test-curator-cluster";
        byte[] data  = curatorFramework.getData().forPath(path);
        log.info("get data: {}", new String(data));

        // 不断的获取数据
        while (true) {
            try {
                byte[] bytes = curatorFramework.getData().forPath(path);
                log.info("get data 2 {}", new String(bytes));

                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
                log.error("获取数据异常: {}", e.getMessage());
                testCluster();
            }
        }
    }

    @After
    public void holdOnzk() throws InterruptedException {
        // 不要让程序结束. 然后我们在客户端中去修改数据
        TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
    }

}

在这里插入图片描述

此时可以看到curator已经连接到了zk集群,目前连接到2184这个节点上。为了测试高可用,我们现在停掉2184.

/usr/local/zookeeper/apache-zookeeper-3.5.9-bin/bin/zkServer.sh  stop /usr/local/zookeeper/apache-zookeeper-3.5.9-bin/conf/zoo-4.cfg

在这里插入图片描述
可以看到,curator客户端已经帮我们处理了重试,发现连接异常后重新连接到了2182这个节点上获取数据。

四、Zookeeper 3.5.0 新特性: 集群动态配置

Zookeeper 3.5.0 以前,Zookeeper集群角色要发生改变的话,只能通过停掉所有的Zookeeper服务,修改集群配置,重启服务来完成,这样集群服务将有一段不可用的状态,为了应对高可用需求,Zookeeper 3.5.0 提供了支持动态扩容/缩容的 新特性

但是通过客户端API可以变更服务端集群状态是件很危险的事情,所以在zookeeper 3.5.3 版本要用动态配置,需要开启超级管理员身份验证模式 ACLs。如果是在一个安全的环境也可以通过配置 系统参数 -Dzookeeper.skipACL=yes 来避免配置维护acl 权限配置

1、配置超级管理员

第一步,先配置一个超级管理员(如果不配管理员,也可以设置系统参数 -Dzookeeper.skipACL=yes):如:

在zookeeper启动脚本中添加 超级管理员授权模式:

// 生成一个加密的用户名为admin, 密码为admin的账号信息
	// ======= 首先新加一个用户,并生成加密后的密文
[root@localhost bin]# echo -n user2:pass2 | openssl dgst -binary -sha1 | openssl base64
0RphDIvIZHLcNxLVT/hhi2uckEs=

// 将下面这个信息配置到zk的zkServer.sh文件中
-Dzookeeper.DigestAuthenticationProvider.superDigest=user2:0RphDIvIZHLcNxLVT/hhi2uckEs= 

修改bin目录下的zkServer.sh文件:
使用上面生成的密文,将用户user2设置成一个super用户:

在这里插入图片描述

2、配置动态文件

修改配置 zoo1.cfg
注意这里去除了端口号,添加了
reconfigEnabled : 设置为true 开启动态配置
dynamicConfigFile : 指定动态配置文件的路径
在这里插入图片描述

3、创建文件 zoo_replicated1.cfg.dynamic

动态配置文件,加入集群信息
server.A=B.C.D.E;F
A: 服务的唯一标识
B: 服务对应的IP地址,
C: 集群通信端口
D: 集群选举端口
E: 角色, 默认是 participant,即参与过半机制的角色,选举,事务请求过半提交,还有一个是observer, 观察者,不参与选举以及过半机制。
之后是一个分号,一定是分号,
F:服务IP:端口

server.1=192.168.131.171:2001:3001:participant;192.168.131.171:2181
server.2=192.168.131.171:2002:3002:participant;192.168.131.171:2182
server.3=192.168.131.171:2003:3003:participant;192.168.131.171:2183
server.4=192.168.131.171:2004:3004:participant;192.168.131.171:2184

依次配置其他服务 zoo2.cfg ,zoo3.cfg注意数据文件的路径。

依次启动所有服务:

如: ./bin/zkServer.sh  start   conf/zoo1.cfg  
查看集群状态:
     ./bin/zkServer.sh  status  conf/zoo1.cfg    

连上任意一台服务器:

查看集群配置
 config  // 将会把动态配置打印出来
 也可以直接查看目录
 /zookeeper/config 
 该节点存储了集群信息

 如果要修改集群状态,需要授权登录
 addauth digest gj:123  

 reconfig -remove 3  // 移除serverId为 3 的机器
 // 把对应的机器加进来
 reconfig -add server.3=192.168.109.200:2003:3003:participant;192.168.109.200:2183

如果要变更/或者添加新的服务需要将服务加到配置文件 zoo_replicated1.cfg.dynamic 中,启动服务。

然后通过reconfig 命令进行添加或者变更服务角色,但是需要保证服务列表中 participant 角色能够形成集群(过半机制)。

客户端可以通过监听 /zookeeper/confg 节点,来感知集群的变化。从而实现集群的动态变更。

Zookeeper 类提供了对应的API 用来更新服务列表 : updateServerList

 Watcher watcher = new Watcher() {
    @Override
    public void process(WatchedEvent event) {
                 if (event.getType() == Event.EventType.None
                         && event.getState() == Event.KeeperState.SyncConnected){
                           countDownLatch.countDown();
                           log.info(" 连接建立");
                           // start to watch config
                     try {
                         log.info(" 开始监听:{}",ZooDefs.CONFIG_NODE);
                         zookeeper.getConfig(true,null);
                     } catch (KeeperException e) {
                         e.printStackTrace();
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }else if( event.getPath()!=null  &&  event.getPath().equals(ZooDefs.CONFIG_NODE)){
                     try {
                         byte[] config = zookeeper.getConfig(this, null);
                         String clientConfigStr = ConfigUtils.getClientConfigStr(new String(config));
                         log.info(" 配置发生变更: {}",clientConfigStr);
                         zookeeper.updateServerList(clientConfigStr.split(" ")[1]);
                     } catch (KeeperException e) {
                         e.printStackTrace();
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     } catch (IOException e) {
                         e.printStackTrace();
                     }

                 }
    }
};

Curator 也自带了动态配置的监听,不需要额外的配置和代码实现监听更新;

五、问题

1、ZK没有实现分片,所有的数据在各个节点中都是一致的;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值