com.talis.platform.sequencing.zookeeper.ZkClock.java Source code

Java tutorial

Introduction

Here is the source code for com.talis.platform.sequencing.zookeeper.ZkClock.java

Source

/*
 *    Copyright 2010 Talis Systems Ltd
 * 
 *    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.talis.platform.sequencing.zookeeper;

import java.nio.ByteBuffer;
import java.util.List;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Inject;
import com.talis.platform.sequencing.Clock;
import com.talis.platform.sequencing.NoSuchSequenceException;
import com.talis.platform.sequencing.SequencingException;
import com.talis.platform.sequencing.zookeeper.metrics.ZooKeeperMetrics;

public class ZkClock implements Clock {

    static final String NOT_FOUND_FORMAT = "Sequence with key %s not found";

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

    static final byte[] DEFAULT_DATA = ByteBuffer.allocate(8).putLong(-1).array();
    static final List<ACL> DEFAULT_ACL = ZooDefs.Ids.OPEN_ACL_UNSAFE;

    static final String RETRY_DELAY_PROPERTY = "com.talis.platform.sequencing.zookeeper.retrydelay";
    static final String RETRY_COUNT_PROPERTY = "com.talis.platform.sequencing.zookeeper.retrycount";

    private final ZooKeeper myZooKeeper;
    private final ZooKeeperMetrics myMetrics;

    private final long retryDelay = Long.getLong(RETRY_DELAY_PROPERTY, 100l);
    private final int retryCount = Integer.getInteger(RETRY_COUNT_PROPERTY, 10);

    @Inject
    public ZkClock(ZooKeeperProvider zooKeeperProvider, ZooKeeperMetrics metrics) throws SequencingException {
        LOG.info("Initialising ZooKeeper backed Clock instance");
        myZooKeeper = zooKeeperProvider.get();
        myMetrics = metrics;
    }

    @Override
    public long getNextSequence(String key) throws SequencingException {
        KeeperException mostRecentException = null;

        for (int i = 0; i < retryCount; i++) {
            try {
                return getAndIncrement(key);
            } catch (KeeperException.SessionExpiredException e) {
                myMetrics.incrementSessionExpiredEvents();
                LOG.warn("Session expired for: " + myZooKeeper + " so reconnecting due to: " + e, e);
                throw new SequencingException("Session expired", e);
            } catch (KeeperException.ConnectionLossException e) {
                myMetrics.incrementConnectionLossEvents();
                mostRecentException = e;
                LOG.debug("Attempt " + i + " failed with connection loss so " + "attempting to reconnect: " + e, e);
                retryWithDelay(i);
            } catch (KeeperException e) {
                myMetrics.incrementKeeperExceptions();
                LOG.error(
                        String.format("Caught an unexpected error when " + "incrementing sequence for key %s", key),
                        e);
                mostRecentException = e;
            }
        }
        throw new SequencingException(String.format("Failed to obtain next sequence for key %s", key),
                mostRecentException);
    }

    private long getAndIncrement(String key) throws KeeperException {
        LOG.debug(String.format("Incrementing sequence for key %s", key));
        Stat stat = new Stat();
        boolean committed = false;
        long id = 0;
        while (!committed) {
            try {
                byte[] data = myZooKeeper.getData(key, false, stat);
                ByteBuffer buf = ByteBuffer.wrap(data);
                id = buf.getLong();
                buf.rewind();
                buf.putLong(++id);
                myZooKeeper.setData(key, buf.array(), stat.getVersion());
                committed = true;
            } catch (KeeperException.NoNodeException e) {
                createKey(key);
                committed = false;
            } catch (KeeperException.BadVersionException e) {
                myMetrics.incrementKeyCollisions();
                LOG.debug(String.format("Another client updated key %s, retrying", key));
                committed = false;
            } catch (InterruptedException e) {
                // at this point, we don't know that our update happened.
                // we will err on the side of caution and assume that it
                // didn't. In the worst case, we'll end up with a wasted
                // sequence number that we'll have to apply compensating
                // measures to deal with
                myMetrics.incrementInterruptedExceptions();
                LOG.error(String.format("Unable to determine status counter increment for key %s. "
                        + "This may result in unused sequences", key), e);
                committed = false;
            }
        }
        LOG.debug(String.format("Key:Seq => %s, %s", key, id));
        return id;
    }

    private void createKey(String key) throws KeeperException {
        LOG.debug(String.format("Creating new node for key %s", key));
        try {
            myZooKeeper.create(key, DEFAULT_DATA, DEFAULT_ACL, CreateMode.PERSISTENT);
            myMetrics.incrementKeyCreations();
        } catch (InterruptedException e) {
            myMetrics.incrementInterruptedExceptions();
            LOG.error(String.format("Caught InterruptedException when creating key %s", key), e);
        } catch (KeeperException e) {
            if (e.code().equals(KeeperException.Code.NODEEXISTS)) {
                LOG.info(String.format(
                        "Tried to create %s, but it already exists. " + "Probably a (harmless) race condition",
                        key));
            } else {
                myMetrics.incrementKeeperExceptions();
                throw e;
            }
        }
    }

    private void retryWithDelay(int attemptCount) {
        if (attemptCount > 0) {
            try {
                Thread.sleep(attemptCount * retryDelay);
            } catch (InterruptedException e) {
                LOG.debug("Failed to sleep: " + e, e);
            }
        }
    }

    @Override
    public long getSequence(String key) throws SequencingException {
        LOG.debug(String.format("Get current sequence for key %s", key));
        Stat stat = new Stat();
        byte[] data;
        try {
            data = myZooKeeper.getData(key, false, stat);
            ByteBuffer buf = ByteBuffer.wrap(data);
            return buf.getLong();
        } catch (KeeperException.NoNodeException e) {
            String msg = String.format(NOT_FOUND_FORMAT, key);
            LOG.debug(msg);
            throw new NoSuchSequenceException(msg, e);
        } catch (KeeperException e) {
            String msg = String.format("KeeperException while getting sequence for key: %s", key);
            LOG.debug(msg);
            throw new SequencingException(msg, e);
        } catch (InterruptedException e) {
            String msg = String.format("InterruptedException while getting sequence for key: %s", key);
            LOG.debug(msg);
            throw new SequencingException(msg, e);
        }
    }
}