Permalink
Browse files

Added MembaseClient Object

The MembaseClient object should be used when making connections
to Membase clusters. This commit also includes a new
MembaseConnectionFactory and adds adds the coresponding builder
code to ConnectionFactoryBuilder. Test support is also added
for connections specific to Membase clusters

Change-Id: I7209adf74c871a9ad6d7cf3e46a9c2c377b588ec
Reviewed-on: http://review.couchbase.org/7969
Tested-by: Michael Wiederhold <mike@couchbase.com>
Reviewed-by: Michael Wiederhold <mike@couchbase.com>
  • Loading branch information...
Mike Wiederhold authored and mikewied committed Jul 10, 2011
1 parent 5f01535 commit b27a441ee76f00e49fe3383f68dfa8cf641952dd
@@ -1,5 +1,7 @@
package net.spy.memcached;
+import java.io.IOException;
+import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -366,6 +368,134 @@ public int getTimeoutExceptionThreshold() {
}
+ /**
+ * Get the MembaseConnectionFactory set up with the provided parameters.
+ * Note that a MembaseConnectionFactory requires the failure mode is set
+ * to retry, and the locator type is discovered dynamically based on the
+ * cluster you are connecting to. As a result, these values will be
+ * overridden upon calling this function.
+ *
+ * @param baseList a list of URI's that will be used to connect to the cluster
+ * @param bucketName the name of the bucket to connect to
+ * @param usr the username for the bucket
+ * @param pass the password for the bucket
+ * @return a MembaseConnectionFactory object
+ * @throws IOException
+ */
+ public MembaseConnectionFactory buildMembaseConnection(final List<URI> baseList,
+ final String bucketName, final String usr, final String pwd) throws IOException {
+ return new MembaseConnectionFactory(baseList, bucketName, usr, pwd) {
+
+ @Override
+ public BlockingQueue<Operation> createOperationQueue() {
+ return opQueueFactory == null ?
+ super.createOperationQueue() : opQueueFactory.create();
+ }
+
+ @Override
+ public BlockingQueue<Operation> createReadOperationQueue() {
+ return readQueueFactory == null ?
+ super.createReadOperationQueue()
+ : readQueueFactory.create();
+ }
+
+ @Override
+ public BlockingQueue<Operation> createWriteOperationQueue() {
+ return writeQueueFactory == null ?
+ super.createReadOperationQueue()
+ : writeQueueFactory.create();
+ }
+
+ @Override
+ public NodeLocator createLocator(List<MemcachedNode> nodes) {
+ switch(getLocator()) {
+ case CONSISTENT:
+ return new KetamaNodeLocator(nodes, getHashAlg());
+ case VBUCKET:
+ return new VBucketNodeLocator(nodes, getVBucketConfig());
+ default: throw new IllegalStateException(
+ "Unhandled locator type: " + locator);
+ }
+ }
+
+ @Override
+ public Transcoder<Object> getDefaultTranscoder() {
+ return transcoder == null ?
+ super.getDefaultTranscoder() : transcoder;
+ }
+
+ @Override
+ public FailureMode getFailureMode() {
+ return failureMode;
+ }
+
+ @Override
+ public HashAlgorithm getHashAlg() {
+ return hashAlg;
+ }
+
+ @Override
+ public Collection<ConnectionObserver> getInitialObservers() {
+ return initialObservers;
+ }
+
+ @Override
+ public OperationFactory getOperationFactory() {
+ return opFact == null ? super.getOperationFactory() : opFact;
+ }
+
+ @Override
+ public long getOperationTimeout() {
+ return opTimeout == -1 ?
+ super.getOperationTimeout() : opTimeout;
+ }
+
+ @Override
+ public int getReadBufSize() {
+ return readBufSize == -1 ?
+ super.getReadBufSize() : readBufSize;
+ }
+
+ @Override
+ public boolean isDaemon() {
+ return isDaemon;
+ }
+
+ @Override
+ public boolean shouldOptimize() {
+ return shouldOptimize;
+ }
+
+ @Override
+ public boolean useNagleAlgorithm() {
+ return useNagle;
+ }
+
+ @Override
+ public long getMaxReconnectDelay() {
+ return maxReconnectDelay;
+ }
+
+ @Override
+ public AuthDescriptor getAuthDescriptor() {
+ return authDescriptor;
+ }
+
+ @Override
+ public long getOpQueueMaxBlockTime() {
+ return opQueueMaxBlockTime > -1 ? opQueueMaxBlockTime
+ : super.getOpQueueMaxBlockTime();
+ }
+
+ @Override
+ public int getTimeoutExceptionThreshold() {
+ return timeoutExceptionThreshold;
+ }
+
+ };
+
+ }
+
/**
* Type of protocol to use for connections.
*/
@@ -0,0 +1,242 @@
+package net.spy.memcached;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.ClosedSelectorException;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import net.spy.memcached.internal.OperationFuture;
+import net.spy.memcached.ops.GetlOperation;
+import net.spy.memcached.ops.Operation;
+import net.spy.memcached.ops.OperationStatus;
+import net.spy.memcached.transcoders.Transcoder;
+import net.spy.memcached.vbucket.ConfigurationException;
+import net.spy.memcached.vbucket.Reconfigurable;
+import net.spy.memcached.vbucket.config.Bucket;
+
+public class MembaseClient extends MemcachedClient implements MembaseClientIF, Reconfigurable {
+
+ private volatile boolean reconfiguring = false;
+
+ /**
+ * Get a MemcachedClient based on the REST response from a Membase server.
+ *
+ * This constructor is merely a convenience for situations where the bucket
+ * name is the same as the user name. This is commonly the case.
+ *
+ * To connect to the "default" special bucket for a given cluster, use an
+ * empty string as the password.
+ *
+ * If a password has not been assigned to the bucket, it is typically an
+ * empty string.
+ *
+ * @param baseList the URI list of one or more servers from the cluster
+ * @param bucketName the bucket name in the cluster you wish to use
+ * @param pwd the password for the bucket
+ * @throws IOException if connections could not be made
+ * @throws ConfigurationException if the configuration provided by the
+ * server has issues or is not compatible
+ */
+ public MembaseClient(List<URI> baseList, String bucketName, String pwd)
+ throws IOException, ConfigurationException {
+ this(new MembaseConnectionFactory(baseList, bucketName, bucketName, pwd));
+ }
+
+ /**
+ * Get a MemcachedClient based on the REST response from a Membase server
+ * where the username is different than the bucket name.
+ *
+ * To connect to the "default" special bucket for a given cluster, use an
+ * empty string as the password.
+ *
+ * If a password has not been assigned to the bucket, it is typically an
+ * empty string.
+ *
+ * @param baseList the URI list of one or more servers from the cluster
+ * @param bucketName the bucket name in the cluster you wish to use
+ * @param usr the username for the bucket; this nearly always be the same
+ * as the bucket name
+ * @param pwd the password for the bucket
+ * @throws IOException if connections could not be made
+ * @throws ConfigurationException if the configuration provided by the
+ * server has issues or is not compatible
+ */
+ public MembaseClient(final List<URI> baseList, final String bucketName,
+ final String usr, final String pwd) throws IOException, ConfigurationException {
+ this(new MembaseConnectionFactory(baseList, bucketName, usr, pwd));
+ }
+
+ /**
+ * Get a MemcachedClient based on the REST response from a Membase server
+ * where the username is different than the bucket name.
+ *
+ * Note that when specifying a ConnectionFactory you must specify a
+ * BinaryConnectionFactory. Also the ConnectionFactory's protocol
+ * and locator values are always overwritten. The protocol will always
+ * be binary and the locator will be chosen based on the bucket type you
+ * are connecting to.
+ *
+ * To connect to the "default" special bucket for a given cluster, use an
+ * empty string as the password.
+ *
+ * If a password has not been assigned to the bucket, it is typically an
+ * empty string.
+ *
+ * @param cf the ConnectionFactory to use to create connections
+ * @param baseList the URI list of one or more servers from the cluster
+ * @param bucketName the bucket name in the cluster you wish to use
+ * @param usr the username for the bucket; this nearly always be the same
+ * as the bucket name
+ * @param pwd the password for the bucket
+ * @throws IOException if connections could not be made
+ * @throws ConfigurationException if the configuration provided by the
+ * server has issues or is not compatible
+ */
+ public MembaseClient(MembaseConnectionFactory cf) throws IOException, ConfigurationException {
+ super(cf, AddrUtil.getAddresses(cf.getVBucketConfig().getServers()), false);
+ start();
+ }
+
+ public void reconfigure(Bucket bucket) {
+ reconfiguring = true;
+ try {
+ conn.reconfigure(bucket);
+ } catch (IllegalArgumentException ex) {
+ getLogger().warn("Failed to reconfigure client, staying with previous configuration.", ex);
+ } finally {
+ reconfiguring = false;
+ }
+ }
+
+ /**
+ * Gets and locks the given key asynchronously. By default the maximum allowed
+ * timeout is 30 seconds. Timeouts greater than this will be set to 30 seconds.
+ *
+ * @param key the key to fetch and lock
+ * @param exp the amount of time the lock should be valid for in seconds.
+ * @param tc the transcoder to serialize and unserialize value
+ * @return a future that will hold the return value of the fetch
+ * @throws IllegalStateException in the rare circumstance where queue
+ * is too full to accept any more requests
+ */
+ public <T> OperationFuture<CASValue<T>> asyncGetAndLock(final String key, int exp,
+ final Transcoder<T> tc) {
+ final CountDownLatch latch=new CountDownLatch(1);
+ final OperationFuture<CASValue<T>> rv=
+ new OperationFuture<CASValue<T>>(key, latch, operationTimeout);
+
+ Operation op=opFact.getl(key, exp,
+ new GetlOperation.Callback() {
+ private CASValue<T> val=null;
+ public void receivedStatus(OperationStatus status) {
+ rv.set(val, status);
+ }
+ public void gotData(String k, int flags, long cas, byte[] data) {
+ assert key.equals(k) : "Wrong key returned";
+ assert cas > 0 : "CAS was less than zero: " + cas;
+ val=new CASValue<T>(cas, tc.decode(
+ new CachedData(flags, data, tc.getMaxSize())));
+ }
+ public void complete() {
+ latch.countDown();
+ }});
+ rv.setOperation(op);
+ addOp(key, op);
+ return rv;
+ }
+
+ /**
+ * Get and lock the given key asynchronously and decode with the default
+ * transcoder. By default the maximum allowed timeout is 30 seconds.
+ * Timeouts greater than this will be set to 30 seconds.
+ *
+ * @param key the key to fetch and lock
+ * @param exp the amount of time the lock should be valid for in seconds.
+ * @return a future that will hold the return value of the fetch
+ * @throws IllegalStateException in the rare circumstance where queue
+ * is too full to accept any more requests
+ */
+ public OperationFuture<CASValue<Object>> asyncGetAndLock(final String key, int exp) {
+ return asyncGetAndLock(key, exp, transcoder);
+ }
+
+ /**
+ * Getl with a single key. By default the maximum allowed timeout is 30
+ * seconds. Timeouts greater than this will be set to 30 seconds.
+ *
+ * @param key the key to get and lock
+ * @param exp the amount of time the lock should be valid for in seconds.
+ * @param tc the transcoder to serialize and unserialize value
+ * @return the result from the cache (null if there is none)
+ * @throws OperationTimeoutException if the global operation timeout is
+ * exceeded
+ * @throws IllegalStateException in the rare circumstance where queue
+ * is too full to accept any more requests
+ */
+ public <T> CASValue<T> getAndLock(String key, int exp, Transcoder<T> tc) {
+ try {
+ return asyncGetAndLock(key, exp, tc).get(
+ operationTimeout, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted waiting for value", e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException("Exception waiting for value", e);
+ } catch (TimeoutException e) {
+ throw new OperationTimeoutException("Timeout waiting for value", e);
+ }
+ }
+
+ /**
+ * Get and lock with a single key and decode using the default transcoder.
+ * By default the maximum allowed timeout is 30 seconds. Timeouts greater
+ * than this will be set to 30 seconds.
+ * @param key the key to get and lock
+ * @param exp the amount of time the lock should be valid for in seconds.
+ * @return the result from the cache (null if there is none)
+ * @throws OperationTimeoutException if the global operation timeout is
+ * exceeded
+ * @throws IllegalStateException in the rare circumstance where queue
+ * is too full to accept any more requests
+ */
+ public CASValue<Object> getAndLock(String key, int exp) {
+ return getAndLock(key, exp, transcoder);
+ }
+
+ /**
+ * Infinitely loop processing IO.
+ */
+ @Override
+ public void run() {
+ while(running) {
+ if (!reconfiguring) {
+ try {
+ conn.handleIO();
+ } catch (IOException e) {
+ logRunException(e);
+ } catch (CancelledKeyException e) {
+ logRunException(e);
+ } catch (ClosedSelectorException e) {
+ logRunException(e);
+ } catch (IllegalStateException e) {
+ logRunException(e);
+ }
+ }
+ }
+ getLogger().info("Shut down memcached client");
+ }
+
+ @Override
+ public boolean shutdown(long timeout, TimeUnit unit) {
+ boolean shutdownResult = super.shutdown(timeout, unit);
+ MembaseConnectionFactory cf = (MembaseConnectionFactory)connFactory;
+ if (cf.getConfigurationProvider() != null) {
+ cf.getConfigurationProvider().shutdown();
+ }
+ return shutdownResult;
+ }
+}
Oops, something went wrong.

0 comments on commit b27a441

Please sign in to comment.