voltcache.api.VoltCache.java Source code

Java tutorial

Introduction

Here is the source code for voltcache.api.VoltCache.java

Source

/* This file is part of VoltDB.
 * Copyright (C) 2008-2012 VoltDB Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */
package voltcache.api;

import java.io.IOException;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.lang3.StringUtils;
import org.voltdb.client.Client;
import org.voltdb.client.ClientConfig;
import org.voltdb.client.ClientFactory;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.ClientStats;
import org.voltdb.client.ClientStatsContext;
import org.voltdb.client.NoConnectionsException;
import org.voltdb.client.NullCallback;
import org.voltdb.utils.MiscUtils;

public class VoltCache implements IVoltCache {
    private static final Lock lock = new ReentrantLock();
    // Pool of cleanup tasks: we need to ensure there is one background thread running to scrub out expired
    // items from the cache for a given cluster connection.
    private static final HashMap<String, CleanupTask> cleanupTaskPool = new HashMap<String, CleanupTask>();

    /*
     * Timer task wrapper to scrub out expired items from the cache
     */
    private class CleanupTask {
        private final Client client;
        private final Timer timer;
        protected long users = 1;

        private CleanupTask(String servers) throws Exception {
            client = connect(servers);
            timer = new Timer();
            timer.scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    try {
                        client.callProcedure("Cleanup");
                    } catch (Exception x) {
                    }
                }
            }, 10000, 10000);
        }

        protected void use() {
            this.users++;
        }

        protected void dispose() throws InterruptedException {
            this.users--;
            if (this.users <= 0) {
                this.timer.cancel();
                this.client.close();
            }
        }
    }

    // Client connection to the underlying VoltDB cluster
    private final Client client;
    private final String servers;

    // For internal use: NullCallback for "noreply" operations
    private static final NullCallback nullCallback = new NullCallback();

    /**
     * Creates a new VoltCache instance with a given VoltDB client.
     * Optionally creates a background timer thread to cleanup the
     * underlying cache from obsolete items.
     * @param servers The comma separated list of VoltDB servers in
     * hostname[:port] format that the instance will use.
     */
    public VoltCache(String servers) throws Exception {
        this.servers = servers;

        client = connect(servers);
        // Make sure there is at least one cleanup task for this cluster
        lock.lock();
        try {
            String key = this.servers;
            if (!cleanupTaskPool.containsKey(key))
                cleanupTaskPool.put(key, new CleanupTask(this.servers));
            else
                cleanupTaskPool.get(key).use();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Creates a new VoltCache instance with a given VoltDB client.
     * Optionally creates a background timer thread to cleanup the
     * underlying cache from obsolete items.
     * @param servers The comma separated list of VoltDB servers the
     * instance will use.
     * @param port The client port to connect to on the VoltDB servers.
     */
    @Deprecated
    public VoltCache(String servers, int port) throws Exception {
        // make the ports work
        String[] serverArray = servers.split(",");
        for (int i = 0; i < serverArray.length; ++i) {
            serverArray[i] = MiscUtils.getHostnameFromHostnameColonPort(serverArray[i]);
            serverArray[i] = MiscUtils.getHostnameColonPortString(serverArray[i], port);
        }
        this.servers = StringUtils.join(serverArray, ',');

        client = connect(servers);
        // Make sure there is at least one cleanup task for this cluster
        lock.lock();
        try {
            String key = this.servers;
            if (!cleanupTaskPool.containsKey(key))
                cleanupTaskPool.put(key, new CleanupTask(this.servers));
            else
                cleanupTaskPool.get(key).use();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Connect to a set of servers in parallel. Each will retry until
     * connection. This call will block until all have connected.
     *
     * @param servers A comma separated list of servers using the hostname:port
     * syntax (where :port is optional).
     * @throws InterruptedException if anything bad happens with the threads.
     */
    Client connect(final String servers) throws InterruptedException {
        ClientConfig clientConfig = new ClientConfig();
        final Client client = ClientFactory.createClient(clientConfig);
        String[] serverArray = servers.split(",");
        final CountDownLatch connections = new CountDownLatch(serverArray.length);

        // use a new thread to connect to each server
        for (final String server : serverArray) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    connectToOneServerWithRetry(client, server);
                    connections.countDown();
                }
            }).start();
        }
        // block until all have connected
        connections.await();
        return client;
    }

    /**
     * Connect to a single server with retry. Limited exponential backoff.
     * No timeout. This will run until the process is killed if it's not
     * able to connect.
     *
     * @param server hostname:port or just hostname (hostname can be ip).
     */
    void connectToOneServerWithRetry(Client client, String server) {
        int sleep = 1000;
        while (true) {
            try {
                client.createConnection(server);
                break;
            } catch (Exception e) {
                System.err.printf("Connection failed - retrying in %d second(s).\n", sleep / 1000);
                try {
                    Thread.sleep(sleep);
                } catch (Exception interruted) {
                }
                if (sleep < 8000)
                    sleep += sleep;
            }
        }
    }

    /**
     * Closes the VoltCache connection.
     */
    public void close() {
        try {
            this.client.drain();
            this.client.close();
        } catch (NoConnectionsException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // Deal with cleanup task
        lock.lock();
        try {
            String key = this.servers;
            cleanupTaskPool.get(key).dispose();
            if (cleanupTaskPool.get(key).users <= 0) {
                cleanupTaskPool.remove(key);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Executes an operation
     * @param type Type of response expected
     * @param noreply Flag indicating the client doesn't care about receiving a response - operation will return immediately.
     * @param procedure Name of the VoltProcedure to call on the server
     * @param parameters Ordered list of procedure parameters
     * @returns Result of the operation
     */
    private VoltCacheResult execute(VoltCacheResult.Type type, boolean noreply, String procedure,
            Object... parameters) {
        VoltCacheResult results = null;
        try {
            if (noreply) {
                results = this.client.callProcedure(VoltCache.nullCallback, procedure, parameters)
                        ? VoltCacheResult.SUBMITTED()
                        : VoltCacheResult.ERROR();
            } else {
                results = VoltCacheResult.get(type, this.client.callProcedure(procedure, parameters));
            }
        } catch (Exception x) {
            results = VoltCacheResult.ERROR();
        }
        return results;
    }

    /**
     * Asynchronously Executes an operation
     * @param type Type of response expected
     * @param procedure Name of the VoltProcedure to call on the server
     * @param parameters Ordered list of procedure parameters
     * @returns Future result of the operation
     */
    private Future<VoltCacheResult> asyncExecute(VoltCacheResult.Type type, final String procedure,
            final Object... parameters) {
        try {
            Callable<ClientResponse> callable = new Callable<ClientResponse>() {
                public ClientResponse call() {
                    ClientResponse response = null;
                    try {
                        response = client.callProcedure(procedure, parameters);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return response;
                }
            };

            FutureTask<ClientResponse> future = new FutureTask<ClientResponse>(callable);

            return VoltCacheFuture.cast(type, future);
        } catch (Exception x) {
            return VoltCacheFuture.fail();
        }
    }

    /**
     * Adds a cache item.
     * @param key Key of the cache item to add.
     * @param flags Custom flags to save along with the cache item.
     * @param exptime Expiration time for the item (number of second, up to 30 days), or UNIX time in seconds - use <= 0 for no expiration.
     * @param data Raw byte data for the item.
     * @param noreply Flag indicating the client doesn't care about receiving a response - operation will return immediately.
     * @returns Result of the operation
     */
    public VoltCacheResult add(String key, int flags, int exptime, byte[] data, boolean noreply) {
        return execute(VoltCacheResult.Type.CODE, noreply, "Add", key, flags, exptime, data);
    }

    /**
     * Asynchronously Adds a cache item.
     * @param key Key of the cache item to add.
     * @param flags Custom flags to save along with the cache item.
     * @param exptime Expiration time for the item (number of second, up to 30 days), or UNIX time in seconds - use <= 0 for no expiration.
     * @param data Raw byte data for the item.
     * @returns Future result of the operation.
     */
    public Future<VoltCacheResult> asyncAdd(String key, int flags, int exptime, byte[] data) {
        return asyncExecute(VoltCacheResult.Type.CODE, "Add", key, flags, exptime, data);
    }

    /**
     * Appends data to and existing cache item.
     * @param key Key of the cache item to prepend data to.
     * @param data Raw byte data to append to the current item's data.
     * @param noreply Flag indicating the client doesn't care about receiving a response- SUBMITTED will be returned unless an error occurs.
     * @returns Result of the operation
     */
    public VoltCacheResult append(String key, byte[] data, boolean noreply) {
        return execute(VoltCacheResult.Type.CODE, noreply, "Append", key, data);
    }

    /**
     * Asynchronously Appends data to and existing cache item.
     * @param key Key of the cache item to prepend data to.
     * @param data Raw byte data to append to the current item's data.
     * @returns Future result of the operation.
     */
    public Future<VoltCacheResult> asyncAppend(String key, byte[] data) {
        return asyncExecute(VoltCacheResult.Type.CODE, "Append", key, data);
    }

    /**
     * Checks and Sets a cache item.
     * @param key Key of the cache item to add.
     * @param flags Custom flags to save along with the cache item.
     * @param exptime Expiration time for the item (number of second, up to 30 days), or UNIX time in seconds - use <= 0 for no expiration.
     * @param data Raw byte data for the item.
     * @param casVersion Concurrency check version for the operation: this is the CASVersion value of the item when it was retrieved.
     *        If another thread modified the underlying cache item, the version mismatch will cause a concurrency check failure and the
     *        requested update will not be performed (other thread's value, set earlier, wins).
     * @param noreply Flag indicating the client doesn't care about receiving a response- SUBMITTED will be returned unless an error occurs.
     * @returns Result of the operation
     */
    public VoltCacheResult cas(String key, int flags, int exptime, byte[] data, long casVersion, boolean noreply) {
        return execute(VoltCacheResult.Type.CODE, noreply, "CheckAndSet", key, flags, exptime, data, casVersion);
    }

    /**
     * Asynchronously Checks and Sets a cache item.
     * @param key Key of the cache item to add.
     * @param flags Custom flags to save along with the cache item.
     * @param exptime Expiration time for the item (number of second, up to 30 days), or UNIX time in seconds - use <= 0 for no expiration.
     * @param data Raw byte data for the item.
     * @param casVersion Concurrency check version for the operation: this is the CASVersion value of the item when it was retrieved.
     *        If another thread modified the underlying cache item, the version mismatch will cause a concurrency check failure and the
     *        requested update will not be performed (other thread's value, set earlier, wins).
     * @returns Future result of the operation.
     */
    public Future<VoltCacheResult> asyncCas(String key, int flags, int exptime, byte[] data, long casVersion) {
        return asyncExecute(VoltCacheResult.Type.CODE, "CheckAndSet", key, flags, exptime, data, casVersion);
    }

    /**
     * Cleans up all expired item (effectively deleting them from the cache).
     * @param noreply Flag indicating the client doesn't care about receiving a response- SUBMITTED will be returned unless an error occurs.
     * @returns Result of the operation
     */
    public VoltCacheResult cleanup(boolean noreply) {
        return execute(VoltCacheResult.Type.CODE, noreply, "Cleanup");
    }

    /**
     * Asynchronously Cleans up all expired item (effectively deleting them from the cache).
     * @returns Future result of the operation.
     */
    public Future<VoltCacheResult> asyncCleanup() {
        return asyncExecute(VoltCacheResult.Type.CODE, "Cleanup");
    }

    /**
     * Deletes a cache item.
     * @param key Key of the cache item to delete.
     * @param exptime Time delay before the delete operation (number of second, up to 30 days), or UNIX time in seconds - use <= 0 for immediate deletion.
     * @param noreply Flag indicating the client doesn't care about receiving a response- SUBMITTED will be returned unless an error occurs.
     * @returns Result of the operation
     */
    public VoltCacheResult delete(String key, int exptime, boolean noreply) {
        return execute(VoltCacheResult.Type.CODE, noreply, "Delete", key, exptime);
    }

    /**
     * Asynchronously Deletes a cache item.
     * @param key Key of the cache item to delete.
     * @param exptime Time delay before the delete operation (number of second, up to 30 days), or UNIX time in seconds - use <= 0 for immediate deletion.
     * @returns Future result of the operation.
     */
    public Future<VoltCacheResult> asyncDelete(String key, int exptime) {
        return asyncExecute(VoltCacheResult.Type.CODE, "Delete", key, exptime);
    }

    /**
     * Flushes out all items in the cache.  Items that had already expired are immediately reclaimed.  If a delay is given, the other
     * items are simply queued for deletion (if no delay is provided, everything is effectively deleted).
     * @param exptime Time delay before the delete operation (number of second, up to 30 days), or UNIX time in seconds - use <= 0 for immediate deletion.
     * @param noreply Flag indicating the client doesn't care about receiving a response- SUBMITTED will be returned unless an error occurs.
     * @returns Result of the operation
     */
    public VoltCacheResult flushAll(int exptime, boolean noreply) {
        return execute(VoltCacheResult.Type.CODE, noreply, "FlushAll", exptime);
    }

    /**
     * Asynchronously Flushes out all items in the cache.  Items that had already expired are immediately reclaimed.  If a delay is given, the other
     * items are simply queued for deletion (if no delay is provided, everything is effectively deleted).
     * @param exptime Time delay before the delete operation (number of second, up to 30 days), or UNIX time in seconds - use <= 0 for immediate deletion.
     * @returns Future result of the operation.
     */
    public Future<VoltCacheResult> asyncFlushAll(int exptime) {
        return asyncExecute(VoltCacheResult.Type.CODE, "FlushAll", exptime);
    }

    /**
     * Gets a single cache item.
     * @param key Key of the cache item to retrieve.
     * @returns Result of the operation
     */
    public VoltCacheResult get(String key) {
        return execute(VoltCacheResult.Type.DATA, false, "Get", key);
    }

    /**
     * Asynchronously Gets a single cache item.
     * @param key Key of the cache item to retrieve.
     * @returns Future result of the operation.
     */
    public Future<VoltCacheResult> asyncGet(String key) {
        return asyncExecute(VoltCacheResult.Type.DATA, "Get", key);
    }

    /**
     * Gets a multiple cache items.
     * @param keys Array of key for the cache items to retrieve.
     * @returns Result of the operation
     */
    public VoltCacheResult get(String[] keys) {
        return execute(VoltCacheResult.Type.DATA, false, "Gets", (Object) keys);
    }

    /**
     * Asynchronously Gets a multiple cache items.
     * @param keys Array of key for the cache items to retrieve.
     * @returns Future result of the operation.
     */
    public Future<VoltCacheResult> asyncGet(String[] keys) {
        return asyncExecute(VoltCacheResult.Type.DATA, "Gets", (Object) keys);
    }

    /**
     * Increments or Decrements an Int64 value to a given cache item representing an Int64 value.
     * @param key Key of the cache item to increment/decrement.
     * @param by Long amount by which to increment/decrement.
     * @param increment Flag indicating true for increment, false for decrement.
     * @param noreply Flag indicating the client doesn't care about receiving a response- SUBMITTED will be returned unless an error occurs.
     * @returns Result of the operation
     */
    public VoltCacheResult incrDecr(String key, long by, boolean increment, boolean noreply) {
        return execute(VoltCacheResult.Type.IDOP, noreply, "IncrDecr", key, by, (byte) (increment ? 1 : 0));
    }

    /**
     * Asynchronously Increments or Decrements an Int64 value to a given cache item representing an Int64 value.
     * @param key Key of the cache item to increment/decrement.
     * @param by Long amount by which to increment/decrement.
     * @param increment Flag indicating true for increment, false for decrement.
     * @returns Future result of the operation.
     */
    public Future<VoltCacheResult> asyncIncrDecr(String key, long by, boolean increment) {
        return asyncExecute(VoltCacheResult.Type.IDOP, "IncrDecr", key, by, (byte) (increment ? 1 : 0));
    }

    /**
     * Prepends data to and existing cache item.
     * @param key Key of the cache item to prepend data to.
     * @param data Raw byte data to prepend to the current item's data.
     * @param noreply Flag indicating the client doesn't care about receiving a response- SUBMITTED will be returned unless an error occurs.
     * @returns Result of the operation
     */
    public VoltCacheResult prepend(String key, byte[] data, boolean noreply) {
        return execute(VoltCacheResult.Type.CODE, noreply, "Prepend", key, data);
    }

    /**
     * Asynchronously Prepends data to and existing cache item.
     * @param key Key of the cache item to prepend data to.
     * @param data Raw byte data to prepend to the current item's data.
     * @returns Future result of the operation.
     */
    public Future<VoltCacheResult> asyncPrepend(String key, byte[] data) {
        return asyncExecute(VoltCacheResult.Type.CODE, "Prepend", key, data);
    }

    /**
     * Replaces a cache item.
     * @param key Key of the cache item to replace.
     * @param flags Custom flags to save along with the cache item.
     * @param exptime Expiration time for the item (number of second, up to 30 days), or UNIX time in seconds - use <= 0 for no expiration.
     * @param data Raw byte data for the item.
     * @param noreply Flag indicating the client doesn't care about receiving a response- SUBMITTED will be returned unless an error occurs.
     * @returns Result of the operation
     */
    public VoltCacheResult replace(String key, int flags, int exptime, byte[] data, boolean noreply) {
        return execute(VoltCacheResult.Type.CODE, noreply, "Replace", key, flags, exptime, data);
    }

    /**
     * Asynchronously Replaces a cache item.
     * @param key Key of the cache item to replace.
     * @param flags Custom flags to save along with the cache item.
     * @param exptime Expiration time for the item (number of second, up to 30 days), or UNIX time in seconds - use <= 0 for no expiration.
     * @param data Raw byte data for the item.
     * @returns Future result of the operation.
     */
    public Future<VoltCacheResult> asyncReplace(String key, int flags, int exptime, byte[] data) {
        return asyncExecute(VoltCacheResult.Type.CODE, "Replace", key, flags, exptime, data);
    }

    /**
     * Adds or Replaces a cache item.
     * @param key Key of the cache item to set.
     * @param flags Custom flags to save along with the cache item.
     * @param exptime Expiration time for the item (number of second, up to 30 days), or UNIX time in seconds - use <= 0 for no expiration.
     * @param data Raw byte data for the item.
     * @param noreply Flag indicating the client doesn't care about receiving a response- SUBMITTED will be returned unless an error occurs.
     * @returns Result of the operation
     */
    public VoltCacheResult set(String key, int flags, int exptime, byte[] data, boolean noreply) {
        return execute(VoltCacheResult.Type.CODE, noreply, "Set", key, flags, exptime, data);
    }

    /**
     * Asynchronously Adds or Replaces a cache item.
     * @param key Key of the cache item to set.
     * @param flags Custom flags to save along with the cache item.
     * @param exptime Expiration time for the item (number of second, up to 30 days), or UNIX time in seconds - use <= 0 for no expiration.
     * @param data Raw byte data for the item.
     * @returns Future result of the operation.
     */
    public Future<VoltCacheResult> asyncSet(String key, int flags, int exptime, byte[] data) {
        return asyncExecute(VoltCacheResult.Type.CODE, "Set", key, flags, exptime, data);
    }

    /**
     * Returns underlying performance statistics
     * @returns Full map of performance counters for the underlying VoltDB connection
     */
    public ClientStatsContext getStatistics() {
        return this.client.createStatsContext();
    }

    /**
     * Saves performance statistics to a file
     * @param stats The stats instance used to generate the statistics data
     * @param file The path to the file where statistics will be saved
     */
    public void saveStatistics(ClientStats stats, String file) throws IOException {
        client.writeSummaryCSV(stats, file);
    }

}