您的位置:首页 > 编程语言 > Go语言

Zookeeper-托管我的mongo集群

2020-01-15 08:37 363 查看

Zookeeper-托管我的mongo集群(一)

zookeeper作为一个分布式服务框架,主要为分布式应用提供一致性服务,其功能包括:分布式应用的配置维护、分布式节点的状态同步管理、集群管理等。简而言之,zookeeper就是用来管理分布式应用的一个框架。

zookeeper = 文件系统 + 监听通知机制

文章目录

  • 2.测试zookeeper
  • 三. 使用java编写一个zookeeper的客户端
  • 四、使用zookeeper托管我的mongodb配置
  • 五、zookeeper集群
  • 一、zookeeper的文件系统

    zookeeper中的子节点与主节点的关系就类似于Linux文件系统,主节点作为根目录,而每一个次级目录就是一个子节点(znode),这些子节点可以用来存储不同的数据,像图中这样层层递进。


    znode一共有四种类型,分别对应不同的需求

    1)PERSISTENT-持久化目录节点

    ​ 客户端与zookeeper断开连接后,该节点依旧存在

    2)PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点

    ​ 客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号

    3)EPHEMERAL-临时目录节点

    ​ 客户端与zookeeper断开连接后,该节点被删除

    4)EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点

    ​ 客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

    zookeeper节点结构如上图所示,图中展示了这样的一个过程:

    (1)我们在zookeeper服务器上往zookeeper中的configuration子节点的下级节点存入一个文件

    (2)装有分布式应用的服务器都会来这个节点中读取配置文件

    说到这里,我就想到我在部署mongodb集群时,去每个服务器上手动添加配置文件的经历了。如果有zookeeper,那么我让每个mongodb节点服务器去找zookeeper配置文件就行,改配置文件也方便,只要去configuration里改就行。那么怎么去实现呢?

    二、安装及使用zookeeper(单节点)

    1、安装

    1.1. 下载zookeeper
    wget http://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz

    zookeeper-3.5的版本解压后缺少部分配置文件,还是使用老版本

    1.2. 解压后进入zookeeper文件夹
    tar -zxvf zookeeper-3.4.14.tar.gz
    cd  cd zookeeper-3.4.14/
    1.3. 修改zookeeper启动文件
    cd conf/
    cp zoo_sample.cfg zoo.cfg
    1.4. 在bin目录下启动zookeeper
    zkServer.sh start
    #zookeeper在启动成功后命令行会打印
    #Starting zookeeper ... STARTED

    2.测试zookeeper

    2.1 客户端接入服务器
    ./zkCli.sh
    #zookeeper默认使用2181端口
    2.2 创建、查看znode
    #查看zookeeper节点
    ls /
    #zookeeper有一个默认znode
    [zookeeper]
    # 创建一个znode,无法创建一个空节点,创建时需要插入任意数据,默认是一个持久化节点
    create /configuration hello
    #查看是否创建
    ls /
    [zookeeper, configuration]

    此时,我通过类似于命令行的指令创建了一个zookeeper的znode,并往该znode中存入了一条叫做hello的数据。

    2.3 查看znode中数据
    #在configuration中创建一个znode,插入"test"并查看数据
    create /configuration/node test
    ls /configuration
    #此时显示[node]
    get /configuration/node
    #此时显示
    test
    cZxid = 0x5
    ctime = Sat Oct 19 21:26:04 CST 2019
    mZxid = 0x5
    mtime = Sat Oct 19 21:26:04 CST 2019
    pZxid = 0x5
    cversion = 0
    dataVersion = 0
    aclVersion = 0
    ephemeralOwner = 0x0
    dataLength = 4
    numChildren = 0
    2.4 删除znode
    delete /configuration/node

    此时,一套zookeeper的基本流程就走完了,这只是一个简单的客户端测试,在真正的使用环境中,我们可以使用java或者c来编写一个zookeeper的客户端,进行测试。

    三. 使用java编写一个zookeeper的客户端

    zookeeper提供了java的开发接口,只需要去pom文件中添加zookeeper依赖即可

    <dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.14</version>
    </dependency>

    编写一个zookeeper连接的客户端程序

    3.1 Main方法
    public class ZookeeperMain {
    public static void main(String[] args) throws Exception {
    ZookeeperServiceImpl zookeeperService = new ZookeeperServiceImpl();
    //新建一个新的客户端连接
    zookeeperService.connectZookeeper(HOST);
    //读取/configuration节点下数据
    System.out.println(zookeeperService.getData(PATH));
    Thread.sleep(Integer.MAX_VALUE);
    }
    }
    3.2 service层
    @Service
    public interface ZookeeperService {
    /**
    * 连接zookeeper
    * @param
    */
    void connectZookeeper(String host) throws Exception;
    /**
    * 获取节点上的数据
    * @param path
    * @return
    */
    String getData(String path) throws KeeperException, InterruptedException;
    }
    3.3 implement 层
    @Service
    public class ZookeeperServiceImpl implements ZookeeperService, Watcher {
    
    private ZooKeeper zookeeper;
    private static CountDownLatch countDownLatch = new CountDownLatch(1);
    private static Stat stat = new Stat();
    
    @Override
    public void connectZookeeper(String host) throws Exception {
    zookeeper = new ZooKeeper(host, SESSION_TIME_OUT, new ZookeeperServiceImpl());
    countDownLatch.await();
    }
    
    @Override
    public String getData(String path) throws KeeperException, InterruptedException {
    byte[] data = zookeeper.getData(path, new ZookeeperServiceImpl(), stat);
    if (data == null) {
    return "";
    }
    return new String(data);
    }
    
    @Override
    public void process(WatchedEvent event) {
    if (Event.KeeperState.SyncConnected == event.getState()) {
    if (Event.EventType.None == event.getType() && null == event.getPath()) {
    countDownLatch.countDown();
    } else if (event.getType() == Event.EventType.NodeDataChanged) {
    try {
    System.out.println("配置已修改,新值为:" + new String(zookeeper.getData(event.getPath(), true, stat)));
    } catch (Exception e) {
    }
    }
    }
    }
    }

    可以看到,在客户端中读取到了/configuration节点存储的数据”hello“,并且当我们在服务器上修改这个数据时,客户端可以收到修改的数据。

    hello
    00:00:27.102 [main-SendThread(magic-machine:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Got ping response for sessionid: 0x10078cd74b1002a after 33ms
    配置已修改,新值为:hell
    00:00:28.102 [main-SendThread(magic-machine:2181)] DEBUG org.apache.zookeeper.ClientCnxn - Got ping response for sessionid: 0x10078cd74b1002a after 33ms
    配置已修改,新值为:hel
    3.4 zookeeper 中对于数据操作的一些概念

    在上面的步骤中,我们已经成功的在java的客户端读取到了znode中的数据,这些操作分别是增、删、改、查,在zookeeper中这些操作就是一个个事务,对于每一个事务请求,zookeeper都会为其分配一个唯一的事务ID,从这些ID可以看到zookeeper处理事务的顺序。更重要的是,为什么每次更新数据,客户端都可以收到通知呢?

    3.5 zookeeper中的watcher

    仔细看以下代码中的implement层,可以看到这一句

    public class ZookeeperServiceImpl implements Watcher() {}

    在zookeeper中有一个很重要的东西,叫做watcher(事件监视器),这个功能使得客户端可以增加一个watcher,当某些特定的事件触发时,这个watcher就会执行,在上面注册watcher中,就是监听znode中的数据有无变化,当数据变化时,watcher能捕捉到这个变化,并按照我们需要的方式去处理。

    客户端向zookeeper中注册一个watcher的同时会将watcher的对象存储在WatchManage中,每当zookeeper服务器触发watch事务时就会告知注册了watch的客户端。在这里着重说明的watcher只能收到事件而不能确切的知道事件内容,如果想知道具体内容,则需要在对应方法中处理。

    我看到这样一张图片,这里面列举了不同的watch事务

    那么我们在哪里注册了watcher呢?

    //这个new ZookeeperServiceImpl()是一个wacher的实现类,我们就是通过这个方式来注册watcher的
    zookeeper = new ZooKeeper(host, SESSION_TIME_OUT, new ZookeeperServiceImpl());
    byte[] data = zookeeper.getData(path, new ZookeeperServiceImpl(), stat);

    在测试watcher时,将watcher注册好之后发现,watcher只生效了一次—即第二次修改数据时,客户端只能收到事件,而不再执行watcher方法了。这是因为,zookeeper为了减轻服务器压力,watcher执行完一次之后就会被移除,这样使得客户端要多次注册watcher。

    当然如果你想要一直监听的话,你可以在你watch处理的程序中添加一个注册watcher的部分

    //new ZookeeperServiceImpl()可以使得客户端注册watcher
    zookeeper = new ZooKeeper(host, SESSION_TIME_OUT, new ZookeeperServiceImpl());
    zookeeper = new ZooKeeper(host, SESSION_TIME_OUT, this);
    new String(zookeeper.getData(event.getPath(), true, stat))

    这样无论你使用哪个方法都可以注册一个对应的watcher

    四、使用zookeeper托管我的mongodb配置

    此时,我是不是就可以试着使用zookeeper托管我的mongodb的配置文件了呢?

    那么我们需要处理以下几点问题:

    1)mongodb节点的服务器都需要一个zookeeper的客户端吧?不然怎么连接zookeeper服务器

    2)我们怎么在zookeeper的znode中添加配置文件

    3)当zookeeper的客户端接入服务器拿到配置文件,我们怎么存到客户端所在的服务器呢?

    第一个问题好处理,磨刀不误砍柴工,先在每台服务器上部署一下zookeeper客户端,就直接使用我写的java打的jar包。

    第二个问题,往zookeeper中添加配置文件。第一,既然zookeeper中的节点既然类似于一个目录,那么我们能否将配置文件直接放进这个目录?答案是不行,因为这个“目录”只是一个数据结构,没办法做到直接添加文件,那么我们只能把这个目录作为文件,往里面写配置信息了

    调用zookeeper中暴露给java的写数据接口,写入配置

    String configuration =
    "dbpath = /home/liujian/mongodb/data\n" +
    "port = 9001\n" +
    "bind_ip = 0.0.0.0\n" +
    "logpath =/home/liujian/mongodb/log/logs.log\n" +
    "smallfiles=true\n" +
    "logappend=true\n" +
    "fork =true\n" +
    "replSet=\"test\"";
    zookeeperService.setData(PATH, data);
    //setData具体实现
    public Stat setData(String path, String data) throws KeeperException, InterruptedException {
    Stat stat = zookeeper.setData(path, data.getBytes(), -1);
    return stat;
    }

    此时,查看configuration节点,就可以看到已经成功插入了配置信息。

    第一步第二步都解决了,现在解决第三步:客户端一旦发现配置文件更新,则重新加载配置文件

    同样这些处理过程都需要在客户端中处理,首先写一个读取配置文件的方法,每次启动zookeeper客户端时,都初始化一下配置文件

    public class SavaConfiguration {
    
    public void saveConfiguration(String data) throws IOException {
    File writePath = new File("CONFIG_PATH");
    if (!writePath.exists()) {
    writePath.createNewFile();
    } else {
    writePath.delete();
    }
    FileWriter file = new FileWriter(writePath.getAbsoluteFile(), true);
    BufferedWriter bw = new BufferedWriter(file);
    bw.write(data);
    bw.close();
    }
    }

    使用zookeeper的getData方法获取配置信息的同时,通过上面的方法将配置文件保存到指定路径

    //保存配置文件
    SavaConfiguration savaConfiguration = new SavaConfiguration();
    savaConfiguration.saveConfiguration(zookeeperService.getData(PATH));
    //当配置文件变化时,本地需要及时同步,在处理wacher中修改
    try {
    savaConfiguration.saveConfiguration(new String(zookeeper.getData(event.getPath(), true, stat)));
    } catch (Exception e) {
    }

    那么这样我们就可以实现mongodb的每个节点都收到相同配置文件。当然,不是热部署的服务器应用还需要我们去每个服务器根据脚本重新启动服务,但是这样也极大简化了我们的操作。

    此外,我们也可以使用zookeeper来监管我们的每个mongodb连接程序,每个使用mongodb的程序都可以在zookeeper中注册一个临时节点,每当有节点退出时我们可以及时收到通知等等,下次可以试一下。

    五、zookeeper集群

    话说回来,zookeeper这么好用,那么有没有什么问题呢?诶,单节点的zookeeper要是挂掉那岂不是全线崩盘?这时候zookeeper就推出了一个集群。无论是zookeeper集群还是mongodb集群亦或是其它集群,其中最重要的问题就是如何保证数据一致性,那么和Raft算法、mongodb底层的Bully算法一样,zookeeper中的算法叫做Paxos算法,其中也涉及到了leader选举,二阶段提交、CAP定理的一些部分。这些算法在应用上的表现都有一个有趣的地方,那就是过半选举的特性将影响节点的个数:

    假设我有2台装有zookeeper节点的服务器,那么挂掉其中一台服务器后我们就会失去服务,此时容忍度为0

    假设我有3台zookeeper服务器,那么挂掉一台时,我还有2台可以用,集群功能没有丧失,此时容忍度为1

    其实可以看到,2N-1台服务器的容忍地与2N台服务器容忍度一样,这与mongo副本集配置时的设置时一样的,但是由于mongodb副本集多有一个仲裁节点,而zookeeper没有,使得zookeeper的集群节点的个数大多是单数。

    最后

    zookeeper集群的搭建我下次会去尝试一下,目前就先写到这里…

    参考文章

    Zookeeper入门看这篇就够了

    Zookeeper系列(3)–Paxos算法的原理及过程透彻理解

    Zookeeper到底是干嘛的

    • 点赞
    • 收藏
    • 分享
    • 文章举报
    咱也不敢问 发布了9 篇原创文章 · 获赞 6 · 访问量 509 私信 关注
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: