com.splicemachine.hbase.ZkUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.splicemachine.hbase.ZkUtils.java

Source

/*
 * Copyright 2012 - 2016 Splice Machine, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the
 * License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

package com.splicemachine.hbase;

import java.io.IOException;
import java.util.List;

import com.splicemachine.access.configuration.HBaseConfiguration;
import org.apache.hadoop.hbase.ZooKeeperConnectionException;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.zookeeper.RecoverableZooKeeper;
import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
import org.apache.log4j.Logger;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
import com.splicemachine.access.HConfiguration;

/**
 * Utilities related to ZooKeeper.
 *
 * @author Scott Fines
 *         Created: 2/2/13 9:38 AM
 */
public class ZkUtils {
    private static final Logger LOG = Logger.getLogger(ZkUtils.class);
    private static final SpliceZooKeeperManager zkManager = new SpliceZooKeeperManager();

    /**
     * Gets a direct interface to a ZooKeeper instance.
     *
     * @return a direct interface to ZooKeeper.
     */
    public static RecoverableZooKeeper getRecoverableZooKeeper() {
        try {
            return zkManager.getRecoverableZooKeeper();
        } catch (ZooKeeperConnectionException e) {
            LOG.error("Unable to connect to zookeeper, aborting", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * @return direct interface to a ZooKeeperWatcher
     */
    public static ZooKeeperWatcher getZooKeeperWatcher() {
        try {
            return zkManager.getZooKeeperWatcher();
        } catch (ZooKeeperConnectionException e) {
            LOG.error("Unable to connect to zookeeper, aborting", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * Creates a node in ZooKeeper if it does not already exist. If it already exists, this does nothing.
     *
     * @param path       the path to create
     * @param bytes      the bytes to set
     * @param acls       the privileges for the new node
     * @param createMode the create mode for that node
     * @throws IOException if something goes wrong and the node can't be added.
     */
    public static boolean safeCreate(String path, byte[] bytes, List<ACL> acls, CreateMode createMode)
            throws KeeperException, InterruptedException {
        try {
            getRecoverableZooKeeper().create(path, bytes, acls, createMode);
            return true;
        } catch (KeeperException e) {
            if (e.code() == KeeperException.Code.NODEEXISTS) {
                //it's already been created, so nothing to do
                return true;
            }
            throw e;
        }
    }

    public static boolean safeDelete(String path, int version) throws KeeperException, InterruptedException {
        try {
            getRecoverableZooKeeper().delete(path, version);
            return true;
        } catch (KeeperException e) {
            if (e.code() != KeeperException.Code.NONODE)
                throw e;
            else
                return false;
        }
    }

    public static boolean recursiveSafeCreate(String path, byte[] bytes, List<ACL> acls, CreateMode createMode)
            throws InterruptedException, KeeperException {
        if (path == null || path.length() <= 0)
            return true; //nothing to do, we've gone all the way to the root
        RecoverableZooKeeper rzk = getRecoverableZooKeeper();
        try {
            return safeCreate(path, bytes, acls, createMode, rzk);
        } catch (KeeperException e) {
            if (e.code() == KeeperException.Code.NONODE) {
                //parent node doesn't exist, so recursively create it, and then try and create your node again
                String parent = path.substring(0, path.lastIndexOf('/'));
                recursiveSafeCreate(parent, new byte[] {}, acls, CreateMode.PERSISTENT);
                return safeCreate(path, bytes, acls, createMode);
            } else
                throw e;
        }
    }

    public static boolean safeCreate(String path, byte[] bytes, List<ACL> acls, CreateMode createMode,
            RecoverableZooKeeper zooKeeper) throws KeeperException, InterruptedException {
        try {
            zooKeeper.create(path, bytes, acls, createMode);
            return true;
        } catch (KeeperException ke) {
            if (ke.code() != KeeperException.Code.NODEEXISTS)
                throw ke;
            else
                return true;
        }
    }

    /**
     *
     * Attempts to grab a squence numbered 1 - 1024 as an ephemeral node in zookeeper.  This is used to keep
     * the unqiue key number of bytes to a minimum.
     *
     * @return
     * @throws Exception
     */
    public static short assignSnowFlakeSequence() throws Exception {
        for (short i = 1; i <= 1024; i++) {
            try {
                // Notice the ephemeral node, will be gone once the process exits...
                // TODO -JL Can be more sophisticated when we get closer, multiple pass as well.
                ZkUtils.create(HConfiguration.getConfiguration().getSpliceRootPath()
                        + HBaseConfiguration.SNOWFLAKE_PATH + "/" + i, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,
                        CreateMode.EPHEMERAL);
                return i;
            } catch (KeeperException e) {
                if (e.code() != KeeperException.Code.NODEEXISTS)
                    throw new Exception("Error assigning snowflake sequence, catastrophic", e);
            }
        }
        throw new Exception("Cannot assign snowflake sequence, catastrophic");
    }

    public static String create(String path, byte[] bytes, List<ACL> acls, CreateMode createMode)
            throws KeeperException, InterruptedException {
        return getRecoverableZooKeeper().create(path, bytes, acls, createMode);
    }

    public static void recursiveDelete(String path) throws InterruptedException, KeeperException, IOException {
        List<String> children = getChildren(path, false);
        for (String child : children) {
            recursiveDelete(path + "/" + child);
        }
        delete(path);
    }

    /**
     * Sets the data onto ZooKeeper.
     * <p/>
     * This is essentially a wrapper method to clean up exception handling code.
     *
     * @param path    the path to set data on
     * @param data    the data to set
     * @param version the version to set (-1 if you don't care).
     * @throws IOException if something goes wrong and the data can't be set.
     */
    public static void setData(String path, byte[] data, int version) throws IOException {
        try {
            getRecoverableZooKeeper().setData(path, data, version);
        } catch (KeeperException | InterruptedException e) {
            throw new IOException(e);
        }
    }

    /**
     * Gets data from ZooKeeper.
     * <p/>
     * This is essentially a wrapper method to clean up exception handling code.
     *
     * @param path the path to get data from
     * @return the data contained in that path
     * @throws IOException if something goes wrong and the data can't be set.
     */
    public static byte[] getData(String path) throws IOException {
        try {
            return getRecoverableZooKeeper().getData(path, false, null);
        } catch (InterruptedException | KeeperException e) {
            throw new IOException(e);
        }
    }

    public static byte[] getData(String path, Watcher watcher, Stat stat) throws IOException {
        try {
            return getRecoverableZooKeeper().getData(path, watcher, stat);
        } catch (InterruptedException | KeeperException e) {
            throw new IOException(e);
        }
    }

    public static List<String> getChildren(String path, boolean watch) throws IOException {
        try {
            return getRecoverableZooKeeper().getChildren(path, watch);
        } catch (InterruptedException | KeeperException e) {
            throw new IOException(e);
        }
    }

    public static List<String> getChildren(String path, Watcher watcher) throws IOException {
        try {
            return getRecoverableZooKeeper().getChildren(path, watcher);
        } catch (InterruptedException | KeeperException e) {
            throw new IOException(e);
        }
    }

    /**
     * Increments a counter stored in ZooKeeper
     *
     * @param counterNode the node to store the counter in
     * @return the next counter number
     * @throws IOException if something goes wrong and the counter can't be incremented.
     */
    public static long nextSequenceId(String counterNode) throws IOException {
        /*
        * When generating a new, Monotonically increasing identifier
        * based off of zookeeper, you have essentially two choices:
        *
        * 1. Create a new sequential znode under a base znode, then
        * read off the assigned sequential identifier.
        * 2. Use optimistic versioning to push increments to a single znode
        * atomically.
        *
        * Case 1 is the simplest to implement, but it has some notable issues:
        *  1. There is no contiguity of numbers. ZooKeeper guarantees
        *  monotonically increasing sequence numbers(Up to Integer.MAX_VALUE
        *  at any rate), but does not guarantee contiguiity of those numbers.
        *  Thus, it's possible to see 1, then the next be 10
        * 2. Lots of znodes. If you have a few thousand conglomerates, then
        * you'll have a few thousand znodes, whose only purpose was to
        * grab the sequential identifier. Not only does this add to the
        * conceptual difficulty in understanding the zookeeper layout, but it
        * also adds additional load to the ZooKeeper cluster itself.
        * 3. Rollovers. The sequential identifier supplied by zookeeper is
        * limited to integers, which can cause a rollover eventually.
        * 4. Expensive to read. To get the highest sequence element (without
        * updating), you must get *all* the znode children, then sort them
        * by sequence number, and then pick out the highest (or lowest). This
        * is a large network operation when there are a few thousand znodes.
        * 5. It's difficult to watch. You can watch on children, but in
        * order to act on them, you must first
        *
        * this implementation uses the second option
        */

        RecoverableZooKeeper rzk = ZkUtils.getRecoverableZooKeeper();
        int maxTries = 10;
        int tries = 0;
        while (tries <= maxTries) {
            tries++;
            //get the current state of the counter
            Stat version = new Stat();
            long currentCount;
            byte[] data;
            try {
                data = rzk.getData(counterNode, false, version);
            } catch (KeeperException e) {
                //if we timed out trying to deal with ZooKeeper, retry
                switch (e.code()) {
                case CONNECTIONLOSS:
                case OPERATIONTIMEOUT:
                    continue;
                default:
                    throw new IOException(e);
                }
            } catch (InterruptedException e) {
                throw new IOException(e);
            }
            currentCount = Bytes.toLong(data) + 1;

            //try and write the next entry, but only if the versions haven't changed
            try {
                rzk.setData(counterNode, Bytes.toBytes(currentCount), version.getVersion());
                return currentCount;
            } catch (KeeperException e) {
                //if we timed out talking, or if the version didn't match, retry
                switch (e.code()) {
                case CONNECTIONLOSS:
                case OPERATIONTIMEOUT:
                case BADVERSION:
                    continue;
                default:
                    throw new IOException(e);
                }
            } catch (InterruptedException e) {
                //we were interrupted, which means we need to bail
                throw new IOException(e);
            }
        }
        /*
         * We've tried to get the latest sequence number a whole bunch of times,
         * without success. That means either a problem with ZooKeeper, or a LOT
         * of contention in getting the sequence. Need to back off and deal with it
         * at a higher level
         *
         * TODO -sf- we could fix this by putting in a non-optimistic lock at this point, but
         * it's probably best to see if it poses a problem as written first.
         */
        throw new IOException(
                "Unable to get next conglomerate sequence, there is an issue " + "speaking with ZooKeeper");
    }

    /**
     * Sets a counter stored in ZooKeeper
     *
     * @param counterNode the node to store the counter in
     * @throws IOException if something goes wrong and the counter can't be incremented.
     */
    public static void setSequenceId(String counterNode, long count) throws IOException {
        RecoverableZooKeeper rzk = ZkUtils.getRecoverableZooKeeper();

        int maxTries = 10;
        int tries = 0;
        while (tries <= maxTries) {
            tries++;
            try {
                rzk.setData(counterNode, Bytes.toBytes(count), -1 /* any version */);
            } catch (KeeperException e) {
                //if we timed out trying to deal with ZooKeeper, retry
                switch (e.code()) {
                case CONNECTIONLOSS:
                case OPERATIONTIMEOUT:
                    continue;
                default:
                    throw new IOException(e);
                }
            } catch (InterruptedException e) {
                throw new IOException(e);
            }
            return;
        }
        /*
         * We've tried to set the sequence number a whole bunch of times,
         * without success. That means either a problem with ZooKeeper
         * Need to back off and deal with it
         * at a higher level
         */
        throw new IOException(
                "Unable to set next conglomerate sequence, there is an issue " + "speaking with ZooKeeper");
    }

    /**
     * Deletes just the splice-specific paths in zookeeper.  Does not delete hbase paths.
     */
    public static void cleanZookeeper() throws InterruptedException, KeeperException {
        RecoverableZooKeeper rzk = getRecoverableZooKeeper();
        String rootPath = HConfiguration.getConfiguration().getSpliceRootPath();
        for (String path : HConfiguration.zookeeperPaths) {
            path = rootPath + path;
            if (rzk.exists(path, false) != null) {
                for (String child : rzk.getChildren(path, false)) {
                    for (String grandChild : rzk.getChildren(path + "/" + child, false)) {
                        rzk.delete(path + "/" + child + "/" + grandChild, -1);
                    }
                    rzk.delete(path + "/" + child, -1);
                }
                rzk.delete(path, -1);
            }
        }
    }

    public static void delete(String path) throws InterruptedException, KeeperException {
        RecoverableZooKeeper rzk = getRecoverableZooKeeper();
        rzk.delete(path, -1);
    }

    public static void initializeZookeeper() throws InterruptedException, KeeperException {
        safeInitializeZooKeeper();

        initializeTransactions();
    }

    public static void safeInitializeZooKeeper() throws InterruptedException, KeeperException {
        String rootPath = HConfiguration.getConfiguration().getSpliceRootPath();
        for (String path : HConfiguration.zookeeperPaths) {
            recursiveSafeCreate(rootPath + path, Bytes.toBytes(0L), ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.PERSISTENT);
        }
    }

    public static void refreshZookeeper() throws InterruptedException, KeeperException {
        cleanZookeeper();
        initializeZookeeper();
    }

    public static boolean validZookeeper() throws InterruptedException, KeeperException {
        RecoverableZooKeeper rzk = getRecoverableZooKeeper();

        String rootPath = HConfiguration.getConfiguration().getSpliceRootPath();
        for (String path : HConfiguration.zookeeperPaths) {
            if (rzk.exists(rootPath + path, false) == null)
                return false;
        }
        return true;
    }

    public static boolean isSpliceLoaded() throws InterruptedException, KeeperException {
        RecoverableZooKeeper rzk = getRecoverableZooKeeper();
        String path = HConfiguration.getConfiguration().getSpliceRootPath() + HConfiguration.STARTUP_PATH;
        return rzk.exists(path, false) != null;
    }

    public static void spliceFinishedLoading() throws InterruptedException, KeeperException {
        RecoverableZooKeeper rzk = getRecoverableZooKeeper();
        String path = HConfiguration.getConfiguration().getSpliceRootPath() + HConfiguration.STARTUP_PATH;
        rzk.create(path, Bytes.toBytes(0L), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

    public static SpliceZooKeeperManager getZkManager() {
        return zkManager;
    }

    private static void initializeTransactions() throws KeeperException, InterruptedException {
        //add the transaction node setup
        String txnPath = HConfiguration.getConfiguration().getSpliceRootPath() + HConfiguration.TRANSACTION_PATH;
        recursiveSafeCreate(txnPath, new byte[] {}, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

        String counterPath = create(txnPath + "/counter-", new byte[] {}, ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT_SEQUENTIAL);
        getRecoverableZooKeeper().setData(txnPath, Bytes.toBytes(counterPath), -1);
    }
}