com.couchbase.client.CouchbaseConnection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of couchbase-client Show documentation
Show all versions of couchbase-client Show documentation
The official Couchbase Java SDK
/**
* Copyright (C) 2009-2013 Couchbase, 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 OR COPYRIGHT HOLDERS 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 DEALING
* IN THE SOFTWARE.
*/
package com.couchbase.client;
import com.couchbase.client.internal.AdaptiveThrottler;
import com.couchbase.client.internal.ThrottleManager;
import com.couchbase.client.vbucket.ConfigurationProvider;
import com.couchbase.client.vbucket.Reconfigurable;
import com.couchbase.client.vbucket.VBucketNodeLocator;
import com.couchbase.client.vbucket.config.Bucket;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.Selector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.spy.memcached.ConnectionObserver;
import net.spy.memcached.FailureMode;
import net.spy.memcached.MemcachedConnection;
import net.spy.memcached.MemcachedNode;
import net.spy.memcached.OperationFactory;
import net.spy.memcached.ops.KeyedOperation;
import net.spy.memcached.ops.Operation;
import net.spy.memcached.ops.ReplicaGetOperation;
import net.spy.memcached.ops.VBucketAware;
/**
* Maintains connections to each node in a cluster of Couchbase Nodes.
*
*/
public class CouchbaseConnection extends MemcachedConnection implements
Reconfigurable {
protected volatile boolean reconfiguring = false;
private final CouchbaseConnectionFactory cf;
private final ThrottleManager throttleManager;
private final boolean enableThrottling;
public CouchbaseConnection(int bufSize, CouchbaseConnectionFactory f,
List a, Collection obs,
FailureMode fm, OperationFactory opfactory) throws IOException {
super(bufSize, f, a, obs, fm, opfactory);
this.cf = f;
enableThrottling = Boolean.parseBoolean(
CouchbaseProperties.getProperty("enable_throttle", false));
if(enableThrottling) {
this.throttleManager = new ThrottleManager(
a, AdaptiveThrottler.class, this, opfactory);
} else {
this.throttleManager = null;
}
}
public void reconfigure(Bucket bucket) {
if(reconfiguring) {
getLogger().debug("Suppressing attempt to reconfigure again while "
+ "reconfiguring.");
return;
}
reconfiguring = true;
try {
// get a new collection of addresses from the received config
List servers = bucket.getConfig().getServers();
HashSet newServerAddresses = new HashSet();
ArrayList newServers =
new ArrayList();
for (String server : servers) {
int finalColon = server.lastIndexOf(':');
if (finalColon < 1) {
throw new IllegalArgumentException("Invalid server ``" + server
+ "'' in vbucket's server list");
}
String hostPart = server.substring(0, finalColon);
String portNum = server.substring(finalColon + 1);
InetSocketAddress address =
new InetSocketAddress(hostPart, Integer.parseInt(portNum));
// add parsed address to our collections
newServerAddresses.add(address);
newServers.add(address);
}
// split current nodes to "odd nodes" and "stay nodes"
ArrayList oddNodes = new ArrayList();
ArrayList stayNodes = new ArrayList();
ArrayList stayServers =
new ArrayList();
for (MemcachedNode current : locator.getAll()) {
if (newServerAddresses.contains(current.getSocketAddress())) {
stayNodes.add(current);
stayServers.add((InetSocketAddress) current.getSocketAddress());
} else {
oddNodes.add(current);
}
}
// prepare a collection of addresses for new nodes
newServers.removeAll(stayServers);
// create a collection of new nodes
List newNodes = createConnections(newServers);
// merge stay nodes with new nodes
List mergedNodes = new ArrayList();
mergedNodes.addAll(stayNodes);
mergedNodes.addAll(newNodes);
for(MemcachedNode keepingNode : mergedNodes) {
getLogger().debug("Node " + keepingNode.getSocketAddress()
+ " will stay in cluster config after reconfiguration.");
}
// call update locator with new nodes list and vbucket config
if (locator instanceof VBucketNodeLocator) {
((VBucketNodeLocator)locator).updateLocator(mergedNodes,
bucket.getConfig());
} else {
locator.updateLocator(mergedNodes);
}
if(enableThrottling) {
for(MemcachedNode node : newNodes) {
throttleManager.setThrottler(
(InetSocketAddress)node.getSocketAddress());
}
for(MemcachedNode node : oddNodes) {
throttleManager.removeThrottler(
(InetSocketAddress)node.getSocketAddress());
}
}
// schedule shutdown for the oddNodes
for(MemcachedNode shutDownNode : oddNodes) {
getLogger().info("Scheduling Node "
+ shutDownNode.getSocketAddress() + " for shutdown.");
}
nodesToShutdown.addAll(oddNodes);
} catch (IOException e) {
getLogger().error("Connection reconfiguration failed", e);
} finally {
reconfiguring = false;
}
}
/**
* Add an operation to the given connection.
*
* @param key the key the operation is operating upon
* @param o the operation
*/
@Override
public void addOperation(final String key, final Operation o) {
MemcachedNode placeIn = null;
MemcachedNode primary;
if(o instanceof ReplicaGetOperation
&& locator instanceof VBucketNodeLocator) {
primary = ((VBucketNodeLocator)locator).getReplica(key,
((ReplicaGetOperation)o).getReplicaIndex());
} else {
primary = locator.getPrimary(key);
}
if (primary == null) {
o.cancel();
cf.checkConfigUpdate();
return;
}
if (primary.isActive() || failureMode == FailureMode.Retry) {
placeIn = primary;
} else if (failureMode == FailureMode.Cancel) {
o.cancel();
} else {
// Look for another node in sequence that is ready.
for (Iterator i = locator.getSequence(key); placeIn == null
&& i.hasNext();) {
MemcachedNode n = i.next();
if (n.isActive()) {
placeIn = n;
}
}
// If we didn't find an active node, queue it in the primary node
// and wait for it to come back online.
if (placeIn == null) {
placeIn = primary;
getLogger().warn(
"Node expected to receive data is inactive. This could be due to "
+ "a failure within the cluster. Will check for updated "
+ "configuration. Key without a configured node is: %s.", key);
cf.checkConfigUpdate();
}
}
assert o.isCancelled() || placeIn != null : "No node found for key " + key;
if (placeIn != null) {
// add the vbucketIndex to the operation
if (locator instanceof VBucketNodeLocator) {
VBucketNodeLocator vbucketLocator = (VBucketNodeLocator) locator;
short vbucketIndex = (short) vbucketLocator.getVBucketIndex(key);
if (o instanceof VBucketAware) {
VBucketAware vbucketAwareOp = (VBucketAware) o;
vbucketAwareOp.setVBucket(key, vbucketIndex);
Collection notMyVbucketNodes =
vbucketAwareOp.getNotMyVbucketNodes();
if (!notMyVbucketNodes.isEmpty()) {
cf.checkConfigUpdate();
MemcachedNode alternative = vbucketLocator.getAlternative(key,
notMyVbucketNodes);
if (alternative == null) {
notMyVbucketNodes.clear();
} else {
placeIn = alternative;
}
}
}
}
if(enableThrottling) {
throttleManager.getThrottler(
(InetSocketAddress)placeIn.getSocketAddress()).throttle();
}
addOperation(placeIn, o);
} else {
assert o.isCancelled() : "No node found for " + key
+ " (and not immediately cancelled)";
}
}
@Override
public void addOperations(final Map ops) {
for (Map.Entry me : ops.entrySet()) {
final MemcachedNode node = me.getKey();
if (!node.isActive()) {
cf.checkConfigUpdate();
}
Operation o = me.getValue();
// add the vbucketIndex to the operation
if (locator instanceof VBucketNodeLocator) {
if (o instanceof KeyedOperation && o instanceof VBucketAware) {
Collection keys = ((KeyedOperation) o).getKeys();
VBucketNodeLocator vbucketLocator = (VBucketNodeLocator) locator;
for (String key : keys) {
short vbucketIndex = (short) vbucketLocator.getVBucketIndex(key);
VBucketAware vbucketAwareOp = (VBucketAware) o;
vbucketAwareOp.setVBucket(key, vbucketIndex);
}
}
}
o.setHandlingNode(node);
o.initialize();
node.addOp(o);
addedQueue.offer(node);
}
Selector s = selector.wakeup();
assert s == selector : "Wakeup returned the wrong selector.";
}
/**
* Infinitely loop processing IO.
*/
@Override
public void run() {
while (running) {
if (!reconfiguring) {
try {
handleIO();
} catch (IOException e) {
logRunException(e);
} catch (CancelledKeyException e) {
logRunException(e);
} catch (ClosedSelectorException e) {
logRunException(e);
} catch (IllegalStateException e) {
logRunException(e);
} catch (ConcurrentModificationException e) {
logRunException(e);
}
}
}
getLogger().info("Shut down Couchbase client");
}
@Override
protected void handleRetryInformation(byte[] retryMessage) {
String message = new String(retryMessage).trim();
if (message.startsWith("{")) {
cf.getConfigurationProvider().setConfig(
replaceConfigWildcards(message)
);
}
}
/**
* Only queue for reconnect if the given node is still part of the cluster.
*
* @param node the node to check.
*/
@Override
protected void queueReconnect(final MemcachedNode node) {
if (isShutDown() || !locator.getAll().contains(node)) {
getLogger().debug("Preventing reconnect for node " + node + " because it"
+ "is either not part of the cluster anymore or the connection is "
+ "shutting down.");
return;
}
super.queueReconnect(node);
}
/**
* Helper method to parse config wildcards into their actual representations.
*
* Currently, this method parses the $HOST wildcard and finds the first node
* in the locator to replace it with.
*
* @param original the raw new config string.
* @return the potentially changed config string.
*/
public String replaceConfigWildcards(String original) {
if (original.contains("$HOST")) {
ArrayList nodes =
new ArrayList(getLocator().getAll());
if (nodes.size() > 0) {
InetSocketAddress addr = (InetSocketAddress) nodes.get(0)
.getSocketAddress();
return original.replaceAll("\\$HOST", addr.getHostName());
} else {
throw new IllegalStateException("Locator has no nodes attached, "
+ "this is not allowed.");
}
}
return original;
}
private void logRunException(Exception e) {
if (shutDown) {
// There are a couple types of errors that occur during the
// shutdown sequence that are considered OK. Log at debug.
getLogger().debug("Exception occurred during shutdown", e);
} else {
getLogger().warn("Problem handling Couchbase IO", e);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy