All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.basho.riak.client.core.RiakCluster Maven / Gradle / Ivy

There is a newer version: 2.1.1
Show newest version
/*
 * Copyright 2013 Basho Technologies, 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.basho.riak.client.core;


import io.netty.bootstrap.Bootstrap;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * A modeled Riak Cluster.
 * 
 * 

* This class represents a Riak Cluster upon which operations are executed. * Instances are created using the {@link Builder} *

* * @author Brian Roach * @since 2.0 */ public class RiakCluster implements OperationRetrier, NodeStateListener { enum State { CREATED, RUNNING, SHUTTING_DOWN, SHUTDOWN } private final Logger logger = LoggerFactory.getLogger(RiakCluster.class); private final int executionAttempts; private final NodeManager nodeManager; private final AtomicInteger inFlightCount = new AtomicInteger(); private final ScheduledExecutorService executor; private final Bootstrap bootstrap; private final List nodeList; private final ReentrantReadWriteLock nodeListLock = new ReentrantReadWriteLock(); private final LinkedBlockingQueue retryQueue = new LinkedBlockingQueue(); private volatile ScheduledFuture shutdownFuture; private volatile ScheduledFuture retrierFuture; private volatile State state; private final CountDownLatch shutdownLatch = new CountDownLatch(1); private RiakCluster(Builder builder) throws UnknownHostException { this.executionAttempts = builder.executionAttempts; if (null == builder.nodeManager) { nodeManager = new DefaultNodeManager(); } else { this.nodeManager = builder.nodeManager; } if (builder.bootstrap != null) { this.bootstrap = builder.bootstrap.clone(); } else { this.bootstrap = new Bootstrap() .group(new NioEventLoopGroup()) .channel(NioSocketChannel.class); } if (builder.executor != null) { executor = builder.executor; } else { // We still need an executor if none was provided. executor = new ScheduledThreadPoolExecutor(2); } nodeList = new ArrayList(builder.riakNodes.size()); for (RiakNode node : builder.riakNodes) { node.setExecutor(executor); node.setBootstrap(bootstrap); node.addStateListener(nodeManager); nodeList.add(node); } // Pass a *copy* of the list to the NodeManager nodeManager.init(new ArrayList(nodeList)); state = State.CREATED; } private void stateCheck(State... allowedStates) { if (Arrays.binarySearch(allowedStates, state) < 0) { logger.debug("IllegalStateException; required: {} current: {} ", Arrays.toString(allowedStates), state); throw new IllegalStateException("required: " + Arrays.toString(allowedStates) + " current: " + state ); } } public synchronized void start() { stateCheck(State.CREATED); // Completely unneeded *right now* but operating on a copy // of the nodeList defensively prevents a deadlock occuring // if a callback were to try and modify the list. for (RiakNode node : getNodes()) { node.start(); } retrierFuture = executor.schedule(new RetryTask(), 0, TimeUnit.SECONDS); logger.info("RiakCluster is starting."); state = State.RUNNING; } public synchronized Future shutdown() { stateCheck(State.RUNNING); logger.info("RiakCluster is shutting down."); state = State.SHUTTING_DOWN; // Wait for all in-progress operations to drain // then shut down nodes. shutdownFuture = executor.scheduleWithFixedDelay(new ShutdownTask(), 500, 500, TimeUnit.MILLISECONDS); return new Future() { @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } @Override public Boolean get() throws InterruptedException { shutdownLatch.await(); return true; } @Override public Boolean get(long timeout, TimeUnit unit) throws InterruptedException { return shutdownLatch.await(timeout, unit); } @Override public boolean isCancelled() { return false; } @Override public boolean isDone() { return shutdownLatch.getCount() <= 0; } }; } public RiakFuture execute(FutureOperation operation) { stateCheck(State.RUNNING); operation.setRetrier(this, executionAttempts); inFlightCount.incrementAndGet(); this.execute(operation, null); return operation; } private void execute(FutureOperation operation, RiakNode previousNode) { nodeManager.executeOnNode(operation, previousNode); } /** * Adds a {@link RiakNode} to this cluster. * The node can not have been started nor have its Bootstrap or Executor * asSet. * @param node the RiakNode to add * @throws java.net.UnknownHostException if the RiakNode's hostname cannot be resolved * @throws IllegalArgumentException if the node's Bootstrap or Executor are already asSet. */ public void addNode(RiakNode node) throws UnknownHostException { stateCheck(State.CREATED, State.RUNNING); node.setExecutor(executor); node.setBootstrap(bootstrap); try { nodeListLock.writeLock().lock(); nodeList.add(node); } finally { nodeListLock.writeLock().unlock(); } nodeManager.addNode(node); } /** * Removes the provided node from the cluster. * @param node * @return true if the node was in the cluster, false otherwise. */ public boolean removeNode(RiakNode node) { stateCheck(State.CREATED, State.RUNNING); boolean removed = false; try { nodeListLock.writeLock().lock(); removed = nodeList.remove(node); } finally { nodeListLock.writeLock().unlock(); } nodeManager.removeNode(node); return removed; } /** * Returns a copy of the list of nodes in this cluster. * @return A copy of the list of RiakNodes */ public List getNodes() { stateCheck(State.CREATED, State.RUNNING, State.SHUTTING_DOWN); try { nodeListLock.readLock().lock(); return new ArrayList(nodeList); } finally { nodeListLock.readLock().unlock(); } } int inFlightCount() { return inFlightCount.get(); } @Override public void nodeStateChanged(RiakNode node, RiakNode.State state) { // We only listen for state changes after telling all the nodes // to shutdown. if (state == RiakNode.State.SHUTDOWN) { logger.debug("Node state changed to shutdown; {}:{}", node.getRemoteAddress(), node.getPort()); try { nodeListLock.writeLock().lock(); nodeList.remove(node); logger.debug("Active nodes remaining: {}", nodeList.size()); if (nodeList.isEmpty()) { this.state = State.SHUTDOWN; executor.shutdown(); bootstrap.group().shutdownGracefully(); logger.debug("RiakCluster shut down bootstrap"); logger.info("RiakCluster has shut down"); shutdownLatch.countDown(); } } finally { nodeListLock.writeLock().unlock(); } } } @Override public void operationFailed(FutureOperation operation, int remainingRetries) { logger.debug("operation failed; remaining retries: {}", remainingRetries); if (remainingRetries > 0) { retryQueue.add(operation); } else { inFlightCount.decrementAndGet(); } } @Override public void operationComplete(FutureOperation operation, int remainingRetries) { inFlightCount.decrementAndGet(); logger.debug("operation complete; remaining retries: {}", remainingRetries); } private void retryOperation() throws InterruptedException { FutureOperation operation = retryQueue.take(); execute(operation, operation.getLastNode()); } private class RetryTask implements Runnable { @Override public void run() { while (!Thread.interrupted()) { try { retryOperation(); } catch (InterruptedException ex) { break; } } logger.info("Retrier shutting down."); } } private class ShutdownTask implements Runnable { @Override public void run() { if (inFlightCount.get() == 0) { logger.info("All operations have completed"); retrierFuture.cancel(true); // Copying the list avoids any potential deadlocks on the callbacks. for (RiakNode node : getNodes()) { node.addStateListener(RiakCluster.this); logger.debug("calling shutdown on node {}:{}", node.getRemoteAddress(), node.getPort()); node.shutdown(); } shutdownFuture.cancel(false); } } } public static Builder builder(List nodes) { return new Builder(nodes); } public static Builder builder(RiakNode node) { return new Builder(node); } /** * Builder used to create {@link RiakCluster} instances. */ public static class Builder { public final static int DEFAULT_EXECUTION_ATTEMPTS = 3; private final List riakNodes; private int executionAttempts = DEFAULT_EXECUTION_ATTEMPTS; private NodeManager nodeManager; private ScheduledExecutorService executor; private Bootstrap bootstrap; /** * Instantiate a Builder containing the supplied {@link RiakNode}s * @param riakNodes - a List of unstarted RiakNode objects */ public Builder(List riakNodes) { this.riakNodes = new ArrayList(riakNodes); } /** * Instantiate a Builder containing a single {@link RiakNode} * @param node */ public Builder(RiakNode node) { this.riakNodes = new ArrayList(1); this.riakNodes.add(node); } /** * Sets the number of times the {@link RiakCluster} will attempt an * operation before returning it as failed. * @param numberOfAttempts * @return this */ public Builder withExecutionAttempts(int numberOfAttempts) { this.executionAttempts = numberOfAttempts; return this; } /** * Sets the {@link NodeManager} for this {@link RiakCluster} * * If none is provided the {@link DefaultNodeManager} will be used * @param nodeManager * @return this */ public Builder withNodeManager(NodeManager nodeManager) { this.nodeManager = nodeManager; return this; } /** * Sets the Threadpool for this cluster. * * This threadpool is passed down to the {@link RiakNode}s. * At the very least it needs to have * two threads available. It is not necessary to supply your own as the * {@link RiakCluster} will instantiate one upon construction if this is * not asSet. * @param executor * @return this */ public Builder withExecutor(ScheduledExecutorService executor) { this.executor = executor; return this; } /** * The Netty {@link Bootstrap} this cluster will use. * * This Bootstrap is passed down to the {@link RiakNode}s. * It is not necessary to supply your * own as the {@link RiakCluster} will instantiate one upon construction * if this is not asSet. * @param bootstrap * @return this */ public Builder withBootstrap(Bootstrap bootstrap) { this.bootstrap = bootstrap; return this; } /** * Instantiates the {@link RiakCluster} * @return a new RiakCluster * @throws UnknownHostException if a node fails to start due to a DNS lookup */ public RiakCluster build() throws UnknownHostException { return new RiakCluster(this); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy