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

com.basho.riak.client.api.commands.kv.MultiFetch 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.api.commands.kv;

import com.basho.riak.client.core.RiakCluster;
import com.basho.riak.client.api.RiakCommand;
import com.basho.riak.client.core.RiakFuture;
import com.basho.riak.client.core.RiakFutureListener;
import com.basho.riak.client.api.commands.ListenableFuture;
import com.basho.riak.client.api.commands.kv.FetchValue.Option;
import com.basho.riak.client.core.query.Location;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;


import static java.util.Collections.unmodifiableList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Command used to fetch multiple values from Riak.
 * 
 * 

* Riak itself does not support pipelining of requests. MutliFetch addresses this issue by using a thread to * parallelize and manage a set of async fetch operations for a given set of keys. *

*

* The result of executing this command is a {@code List} of {@link RiakFuture} objects, each one representing a single * fetch operation. The returned {@code RiakFuture} that contains that list completes * when all the FetchValue operations contained have finished. *

*

 * {@code
 * MultiFetch multifetch = ...;
 * MultiFetch.Response response = client.execute(multifetch);
 * List myResults = new ArrayList();
 * for (RiakFuture f : response)
 * {
 *     try
 *     {
 *          FetchValue.Response response = f.get();
 *          myResults.add(response.getValue(MyPojo.class));
 *     }
 *     catch (ExecutionException e)
 *     {
 *         // log error, etc.
 *     }
 * }}
*

*

* The maximum number of concurrent requests defaults to 10. This can be changed * when constructing the operation. *

*

* Be aware that because requests are being parallelized performance is also * dependent on the client's underlying connection pool. If there are no connections * available performance will suffer initially as connections will need to be established * or worse they could time out. *

* * @author Dave Rusek * @since 2.0 */ public final class MultiFetch extends RiakCommand> { public static final int DEFAULT_MAX_IN_FLIGHT = 10; private final ArrayList locations = new ArrayList(); private final Map, Object> options = new HashMap, Object>(); private final int maxInFlight; private MultiFetch(Builder builder) { this.locations.addAll(builder.keys); this.options.putAll(builder.options); this.maxInFlight = builder.maxInFlight; } @Override protected RiakFuture> executeAsync(final RiakCluster cluster) { List fetchOperations = buildFetchOperations(); MultiFetchFuture future = new MultiFetchFuture(locations); Submitter submitter = new Submitter(fetchOperations, maxInFlight, cluster, future); Thread t = new Thread(submitter); t.setDaemon(true); t.start(); return future; } @SuppressWarnings("unchecked") private List buildFetchOperations() { List fetchValueOperations = new LinkedList(); for (Location location : locations) { FetchValue.Builder builder = new FetchValue.Builder(location); for (Option option : options.keySet()) { builder.withOption((Option) option, options.get(option)); } fetchValueOperations.add(builder.build()); } return fetchValueOperations; } /** * Used to construct a MutiFetch command. */ public static class Builder { private ArrayList keys = new ArrayList(); private Map, Object> options = new HashMap, Object>(); private int maxInFlight = DEFAULT_MAX_IN_FLIGHT; /** * Add a location to the list of locations to retrieve as part of * this multifetch operation. * * @param location the location to add. * @return this */ public Builder addLocation(Location location) { keys.add(location); return this; } /** * Add a list of Locations to the list of locations to retrieve as part of * this multifetch operation. * * @param location a list of Locations * @return a reference to this object */ public Builder addLocations(Location... location) { keys.addAll(Arrays.asList(location)); return this; } /** * Add a set of keys to the list of Locations to retrieve as part of * this multifetch operation. * * @param location an Iterable set of Locations. * @return a reference to this object */ public Builder addLocations(Iterable location) { for (Location loc : location) { keys.add(loc); } return this; } /** * Set the maximum number of requests to be in progress simultaneously. *

* As noted, Riak does not actually have "MultiFetch" functionality. This * operation simulates it by sending multiple fetch requests. This * parameter controls how many outstanding requests are allowed simultaneously. *

* @param maxInFlight the max number of outstanding requests. * @return a reference to this object. */ public Builder withMaxInFlight(int maxInFlight) { this.maxInFlight = maxInFlight; return this; } /** * A {@link Option} to use with each fetch operation. * * @param option an option * @param value the option's associated value * @param the type of the option's value * @return a reference to this object. */ public Builder withOption(Option option, U value) { this.options.put(option, value); return this; } /** * Set the Riak-side timeout value. *

* By default, riak has a 60s timeout for operations. Setting * this value will override that default for each fetch. *

* @param timeout the timeout in milliseconds to be sent to riak. * @return a reference to this object. */ public Builder withTimeout(int timeout) { withOption(Option.TIMEOUT, timeout); return this; } /** * Build a {@link MultiFetch} operation from this builder * * @return an initialized {@link MultiFetch} operation */ public MultiFetch build() { return new MultiFetch(this); } } /** * The response from Riak for a MultiFetch command. * */ public static final class Response implements Iterable> { private final List> responses; Response(List> responses) { this.responses = responses; } @Override public Iterator> iterator() { return unmodifiableList(responses).iterator(); } public List> getResponses() { return responses; } } private class Submitter implements Runnable, RiakFutureListener { private final List operations; private final Semaphore inFlight; private final AtomicInteger received = new AtomicInteger(); private final RiakCluster cluster; private final MultiFetchFuture multiFuture; public Submitter(List operations, int maxInFlight, RiakCluster cluster, MultiFetchFuture multiFuture) { this.operations = operations; this.cluster = cluster; this.multiFuture = multiFuture; inFlight = new Semaphore(maxInFlight); } @Override public void run() { for (FetchValue fv : operations) { try { inFlight.acquire(); } catch (InterruptedException ex) { multiFuture.setFailed(ex); break; } RiakFuture future = fv.executeAsync(cluster); future.addListener(this); } } @Override public void handle(RiakFuture f) { multiFuture.addFetchFuture(f); inFlight.release(); int completed = received.incrementAndGet(); if (completed == operations.size()) { multiFuture.setCompleted(); } } } private class MultiFetchFuture extends ListenableFuture> { private final CountDownLatch latch = new CountDownLatch(1); private final List locations; private final List> futures; private volatile Throwable exception; private MultiFetchFuture(List locations) { this.locations = locations; futures = Collections.synchronizedList(new LinkedList>()); } @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } @Override public Response get() throws InterruptedException { latch.await(); return new Response(futures); } @Override public Response get(long timeout, TimeUnit unit) throws InterruptedException { latch.await(timeout, unit); if (isDone()) { return new Response(futures); } else { return null; } } @Override public Response getNow() { if (isDone()) { return new Response(futures); } else { return null; } } @Override public boolean isCancelled() { return false; } @Override public boolean isDone() { return latch.getCount() != 1; } @Override public void await() throws InterruptedException { latch.await(); } @Override public void await(long timeout, TimeUnit unit) throws InterruptedException { latch.await(timeout, unit); } @Override public boolean isSuccess() { return isDone() && exception == null; } @Override public List getQueryInfo() { return locations; } @Override public Throwable cause() { return exception; } private void addFetchFuture(RiakFuture future) { futures.add(future); } private void setCompleted() { latch.countDown(); notifyListeners(); } private void setFailed(Throwable t) { this.exception = t; latch.countDown(); notifyListeners(); } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + locations.hashCode(); result = prime * result + options.hashCode(); result = prime * result + maxInFlight; return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof FetchValue)) { return false; } final MultiFetch other = (MultiFetch) obj; if (this.locations != other.locations && (this.locations == null || !this.locations.equals(other.locations))) { return false; } if (this.options != other.options && (this.options == null || !this.options.equals(other.options))) { return false; } if (this.maxInFlight != other.maxInFlight) { return false; } return true; } @Override public String toString() { return String.format("{locations: %s, options: %s, maxInFlight: %s}", locations, options, maxInFlight); } }