zookeeper的学习笔记

一、简介

①主从示例说明

(1)主节点失效

主节点失效时,我们需要有一个备份主节点(backup master)。当

主要主节点(primary master)崩溃时,备份主节点接管主要主节点的角

色,进行故障转移,然而,这并不是简单开始处理进入主节点的请求。新的主要主节点需要能够恢复到旧的主要主节点崩溃时的状态。对于主节点状态的可恢复性,我们不能依靠从已经崩溃的主节点来获取这些信

息,而需要从其他地方获取,也就是通过ZooKeeper来获取。

状态恢复并不是唯一的重要问题。假如主节点有效,备份主节点却

认为主节点已经崩溃。这种错误的假设可能发生在以下情况,例如主节

点负载很高,导致消息任意延迟(关于这部分内容请参见1.1.4节),备

份主节点将会接管成为主节点的角色,执行所有必需的程序,最终可能

以主节点的角色开始执行,成为第二个主要主节点。更糟的是,如果一

些从节点无法与主要主节点通信,如由于网络分区(network partition)

错误导致,这些从节点可能会停止与主要主节点的通信,而与第二个主

要主节点建立主-从关系。针对这个场景中导致的问题,我们一般称之

为脑裂(split-brain):系统中两个或者多个部分开始独立工作,导致整

体行为不一致性。我们需要找出一种方法来处理主节点失效的情况,关

键是我们需要避免发生脑裂的情况。

(2)从节点失效

客户端向主节点提交任务,之后主节点将任务派发到有效的从节点

中。从节点接收到派发的任务,执行完这些任务后会向主节点报告执行

状态。主节点下一步会将执行结果通知给客户端。如果从节点崩溃了,所有已派发给这个从节点且尚未完成的任务需

要重新派发。其中首要需求是让主节点具有检测从节点的崩溃的能力。

主节点必须能够检测到从节点的崩溃,并确定哪些从节点是否有效以便

派发崩溃节点的任务。一个从节点崩溃时,从节点也许执行了部分任

务,也许全部执行完,但没有报告结果。如果整个运算过程产生了其他

作用,我们还有必要执行某些恢复过程来清除之前的状态

(3)通信故障

如果一个从节点与主节点的网络连接断开,比如网络分区

(network partition)导致,重新分配一个任务可能会导致两个从节点执

行相同的任务。如果一个任务允许多次执行,我们在进行任务再分配时

可以不用验证第一个从节点是否完成了该任务。如果一个任务不允许,

那么我们的应用需要适应多个从节点执行相同任务的可能性

通信故障导致的另一个重要问题是对锁等同步原语的影响。因为节

点可能崩溃,而系统也可能网络分区(network partition),锁机制也会

阻止任务的继续执行。因此ZooKeeper也需要实现处理这些情况的机

制。首先,客户端可以告诉ZooKeeper某些数据的状态是临时状态

(ephemeral);其次,同时ZooKeeper需要客户端定时发送是否存活的

通知,如果一个客户端未能及时发送通知,那么所有从属于这个客户端

的临时状态的数据将全部被删除。通过这两个机制,在崩溃或通信故障

发生时,我们就可以预防客户端独立运行而发生的应用宕机。

②任务总结

根据之前描述的这些,我们可以得到以下主-从架构的需求:

主节点选举

这是关键的一步,使得主节点可以给从节点分配任务。

崩溃检测

主节点必须具有检测从节点崩溃或失去连接的能力。

组成员关系管理

主节点必须具有知道哪一个从节点可以执行任务的能力。

元数据管理

主节点和从节点必须具有通过某种可靠的方式来保存分配状态和执

行状态的能力。

二、zookeeper基础

①znode概念

菜谱包括ZooKeeper操作和维护一个小型的数据

节点,这些节点被称为znode,采用类似于文件系统的层级树状结构进

行管理。图2-1描述了一个znode树的结构,根节点包含4个子节点,其

中三个子节点拥有下一级节点,叶子节点存储了数据信息

②ZooKeeper的API暴露了以下方法

create/path data

创建一个名为/path的znode节点,并包含数据data。

delete/path

删除名为/path的znode。

exists/path检查是否存在名为/path的节点。

setData/path data

设置名为/path的znode的数据为data。

getData/path

返回名为/path节点的数据信息。

getChildren/path

返回所有/path节点的所有子节点列表。‘

ls /

查看所有节点

stat /master true

为主节点设置监视点

ls /tasks true

为从节点设置监视点

create -e /workers/worker1.example.com  "worker1.example.com:2224"

添加临时节点

create /workers ""

添加主节点

③znode的不同类型

持久节点和临时节点

znode节点可以是持久(persistent)节点,还可以是临时

(ephemeral)节点。持久的znode,如/path,只能通过调用delete来进行

删除。临时的znode与之相反,当创建该节点的客户端崩溃或关闭了与

ZooKeeper的连接时,这个节点就会被删除。

一个临时znode,在以下两种情况下将会被删除:

1.当创建该znode的客户端的会话因超时或主动关闭而中止时。

2.当某个客户端(不一定是创建者)主动删除该节点时。

有序节点

个znode还可以设置为有序(sequential)节点。一个有序znode节

点被分配唯一个单调递增的整数。当创建有序节点时,一个序号会被追

加到路径之后。例如,如果一个客户端创建了一个有序znode节点,其

路径为/tasks/task-,那么ZooKeeper将会分配一个序号,如1,并将这个

数字追加到路径之后,最后该znode节点为/tasks/task-1。有序znode通过

提供了创建具有唯一名称的znode的简单方式。同时也通过这种方式可

以直观地查看znode的创建顺序。

总之,znode一共有4种类型:持久的(persistent)、临时的

(ephemeral)、持久有序的(persistent_sequential)和临时有序的

(ephemeral_sequential)。

④监视与通知

通知(notification)的机制:客户端向ZooKeeper注册需要接收通知的

znode,通过对znode设置监视点(watch)来接收通知。监视点是一个单

次触发的操作,意即监视点会触发一个通知。为了接收多个通知,客户端必须在每次通知后设置一个新的监视点。在图2-3阐述的情况下,当

节点/tasks发生变化时,客户端会收到一个通知,并从ZooKeeper读取一

个新值。

⑤版本

每一个znode都有一个版本号,它随着每次数据变化而自增。两个

API操作可以有条件地执行:setData和delete。这两个调用以版本号作为

转入参数,只有当转入参数的版本号与服务器上的版本号一致时调用才

会成功。当多个ZooKeeper客户端对同一个znode进行操作时,版本的使

用就会显得尤为重要。

三、使用zookeeper进行开发

①建立ZooKeeper会话

(1)简述

ZooKeeper的API围绕ZooKeeper的句柄(handle)而构建,每个API

调用都需要传递这个句柄。这个句柄代表与ZooKeeper之间的一个会

话。在图3-1中,与ZooKeeper服务器已经建立的一个会话如果断开,这

个会话就会迁移到另一台ZooKeeper服务器上。只要会话还存活着,这

个句柄就仍然有效,ZooKeeper客户端库会持续保持这个活跃连接,以

保证与ZooKeeper服务器之间的会话存活。如果句柄关闭,ZooKeeper客

户端库会告知ZooKeeper服务器终止这个会话。如果ZooKeeper发现客户

端已经死掉,就会使这个会话无效。如果客户端之后尝试重新连接到

ZooKeeper服务器,使用之前无效会话对应的那个句柄进行连接,那么

ZooKeeper服务器会通知客户端库,这个会话已失效,使用这个句柄进

行的任何操作都会返回错误。

创建ZooKeeper句柄的构造函数如下所示:

Zoo Keeper(

    String connectString,

    int sessionTimeout,

    Watcher watcher)

其中:

connectString

包含主机名和ZooKeeper服务器的端口。我们之前通过zkCli连接

ZooKeeper服务时,已经列出过这些服务器。

sessionTimeout

以毫秒为单位,表示ZooKeeper等待客户端通信的最长时间,之后

会声明会话已死亡。目前我们使用15000,即15秒。这就是说如果

ZooKeeper与客户端有15秒的时间无法进行通信,ZooKeeper就会终止客

户端的会话。需要注意,这个值比较高,但对于我们后续的实验会非常

有用。ZooKeeper会话一般设置超时时间为5~10秒。

watcher

用于接收会话事件的一个对象,这个对象需要我们自己创建。因为Wacher定义为接口,所以我们需要自己实现一个类,然后初始化这个类

的实例并传入ZooKeeper的构造函数中。客户端使用Watcher接口来监控

与ZooKeeper之间会话的健康情况。与ZooKeeper服务器之间建立或失去

连接时就会产生事件。它们同样还能用于监控ZooKeeper数据的变化。

最终,如果与ZooKeeper的会话过期,也会通过Watcher接口传递事件来

通知客户端的应用。

(2)实现一个Watcher

import org.apache.zookeeper.ZooKeeper;

import org.apache.zookeeper.Watcher;

public class Master implements Watcher {

    ZooKeeper zk;

    String hostPort;

    Master(String hostPort) {

        this.hostPort = hostPort;①

    }

    void startZK() { zk = new ZooKeeper(hostPort, 15000, this);②

    }

    public void process(WatchedEvent e) {

        System.out.println(e);③

    }

    public static void main(String args[])

        throws Exception {

        Master m = new Master(args[0]);

        m.startZK();

        // wait for a bit

        Thread.sleep(60000);④

    }

}

两个运维命令:

stat:查看客户端连接信息

dump:查看会话连接信息

(3)创建主节点以同步和异步的方式

获取管理权

String serverId = Integer.toString(Random.nextLong());

    boolean isLeader = false;

    // returns true if there is a master

    boolean checkMaster() {

        while (true) {

            try {

                Stat stat = new Stat();

                byte data[] = zk.getData("/master", false, stat);①

                isLeader = new String(data).equals(serverId));②

                return true;

            } catch (NoNodeException e) {

                // no master, so try create again

                return false;

            } catch (ConnectionLossException e) {

            }

        }

    }

    void runForMaster() throws InterruptedException {③

        while (true) { try {④

                zk.create("/master", serverId.getBytes(),

                          OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);⑤

                isLeader = true;

                break;

            } catch (NodeExistsException e) {

                isLeader = false;

                break;

            } catch (ConnectionLossException e) {⑥

            }

            if (checkMaster()) break;⑦

        }

    }

 public static void main(String args[]) throws Exception {

        Master m = new Master(args[0]);

        m.startZK();

        m.runForMaster();①

        if (isLeader) {

            System.out.println("I'm the leader");② // wait for a bit

            Thread.sleep(60000);

        } else {

            System.out.println("Someone else is the leader");

        }

        m.stopZK();

    }

异步获取管理权

  static boolean isLeader;

    static StringCallback masterCreateCallback = new StringCallback() {

        void processResult(int rc, String path, Object ctx, String name) {

            switch(Code.get(rc)) {①

            case CONNECTIONLOSS:②

                checkMaster();

                return;

            case OK:③

                isLeader = true;

                break;

            default:④

                isLeader = false;

            }

            System.out.println("I'm " + (isLeader ? "" : "not ") +

                               "the leader");

        }

    };

    void runForMaster() {

        zk.create("/master", serverId.getBytes(), OPEN_ACL_UNSAFE,

                  CreateMode.EPHEMERAL, masterCreateCallback, null);⑤ }

  DataCallback masterCheckCallback = new DataCallback() {

        void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {

            switch(Code.get(rc)) {

            case CONNECTIONLOSS:

                checkMaster();

                return;

            case NONODE:

                runForMaster();

                return;

            }

        }

    }

    void checkMaster() {

        zk.getData("/master", false, masterCheckCallback, null);

    }

(4)创建主节点java代码

public void bootstrap() {

    createParent("/workers", new byte[0]);①

    createParent("/assign", new byte[0]);

    createParent("/tasks", new byte[0]);

    createParent("/status", new byte[0]);

}

void createParent(String path, byte[] data) {

    zk.create(path,

              data,

              Ids.OPEN_ACL_UNSAFE,

              CreateMode.PERSISTENT,

              createParentCallback,

              data);②

}

String Callback createParentCallback = new StringCallback() {

    public void processResult(int rc, String path, Object ctx, String name) {

        switch (Code.get(rc)) {

        case CONNECTIONLOSS:

            createParent(path, (byte[]) ctx);③

            break;

        case OK:

            LOG.info("Parent created");

            break;

        case NODEEXISTS:

            LOG.warn("Parent already registered: " + path);

            break;

        default:

            LOG.error("Something went wrong: ",

                      KeeperException.create(Code.get(rc), path));

        }

    }

};

(5)创建从节点java代码

import java.util.*;

import org.apache.zookeeper.AsyncCallback.DataCallback;

import org.apache.zookeeper.AsyncCallback.StringCallback;

import org.apache.zookeeper.AsyncCallback.VoidCallback;

import org.apache.zookeeper.*;

import org.apache.zookeeper.ZooDefs.Ids;

import org.apache.zookeeper.AsyncCallback.ChildrenCallback;

import org.apache.zookeeper.KeeperException.Code;

import org.apache.zookeeper.data.Stat;

import org.slf4j.*;

public class Worker implements Watcher {

    private static final Logger LOG = LoggerFactory.getLogger(Worker.class);

    ZooKeeper zk;

    String hostPort;

    String serverId = Integer.toHexString(random.nextInt());

    Worker(String hostPort) {

        this.hostPort = hostPort;

    }

    void startZK() throws IOException {

        zk = new ZooKeeper(hostPort, 15000, this);

    }

    public void process(WatchedEvent e) {

        LOG.info(e.toString() + ", " + hostPort);

    }

    void register() {

        zk.create("/workers/worker-" + serverId,

                  "Idle".getBytes(),①

                  Ids.OPEN_ACL_UNSAFE,

                  CreateMode.EPHEMERAL,②

                  createWorkerCallback, null);

    } StringCallback createWorkerCallback = new StringCallback() {

        public void processResult(int rc, String path, Object ctx,

                                  String name) {

            switch (Code.get(rc)) {

            case CONNECTIONLOSS:

                register();③

                break;

            case OK:

                LOG.info("Registered successfully: " + serverId);

                break;

            case NODEEXISTS:

                LOG.warn("Already registered: " + serverId);

                break;

            default:

                LOG.error("Something went wrong: "

                + KeeperException.create(Code.get(rc), path));

            }

        }

    };

    public static void main(String args[]) throws Exception {

        Worker w = new Worker(args[0]);

        w.startZK();

        w.register();

        Thread.sleep(30000);

    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿的十万个为什么

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值