package edu.berkeley.thebes.twopl.client;
import java.io.FileNotFoundException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.naming.ConfigurationException;
import org.apache.thrift.TException;
import org.apache.thrift.transport.TTransportException;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.Meter;
import com.yammer.metrics.core.Timer;
import com.yammer.metrics.core.TimerContext;
import edu.berkeley.thebes.common.config.Config;
import edu.berkeley.thebes.common.interfaces.IThebesClient;
import edu.berkeley.thebes.common.thrift.ServerAddress;
import edu.berkeley.thebes.common.thrift.TTransactionAbortedException;
import edu.berkeley.thebes.twopl.common.TwoPLMasterRouter;
import edu.berkeley.thebes.twopl.common.thrift.TwoPLThriftUtil;
import edu.berkeley.thebes.twopl.common.thrift.TwoPLTransactionResult;
import edu.berkeley.thebes.twopl.common.thrift.TwoPLTransactionService;
/**
* This client forwards transactions to an appropriate {@link ThebesTwoPLTransactionManager}.
*
* We buffer the transaction and sends it off at the END.
* Accordingly, GET and PUT cannot return valid values.
*/
public class ThebesTwoPLClient implements IThebesClient {
private final Meter requestMetric = Metrics.newMeter(ThebesTwoPLClient.class, "2pl-requests", "requests", TimeUnit.SECONDS);
private final Meter operationMetric = Metrics.newMeter(ThebesTwoPLClient.class, "2pl-operations", "operations", TimeUnit.SECONDS);
private final Meter errorMetric = Metrics.newMeter(ThebesTwoPLClient.class, "2pl-errors", "errors", TimeUnit.SECONDS);
private final Timer latencyMetric = Metrics.newTimer(ThebesTwoPLClient.class, "2pl-latencies", TimeUnit.MILLISECONDS, TimeUnit.SECONDS);
private boolean inTransaction;
private List<String> xactCommands;
private TwoPLMasterRouter masterRouter;
// Note: only ConcurrentMap for method putIfAbsent.
private ConcurrentMap<Integer, AtomicInteger> clusterToAccessesMap;
@Override
public void open() throws TTransportException, ConfigurationException, FileNotFoundException {
masterRouter = new TwoPLMasterRouter();
}
@Override
public void beginTransaction() throws TException {
if (inTransaction) {
throw new TException("Currently in a transaction.");
}
clusterToAccessesMap = Maps.newConcurrentMap();
xactCommands = Lists.newArrayList();
inTransaction = true;
}
@Override
public void abortTransaction() throws TException {
throw new TException("abort not supported by ThebesTwoPLClient");
}
@Override
public boolean commitTransaction() throws TException {
if (!inTransaction) {
return false;
}
requestMetric.mark();
operationMetric.mark(xactCommands.size());
// Open the transaction client with the TM that's closest to the most-used masters.
int max = -1;
Integer maxClusterID = null;
for (Entry<Integer, AtomicInteger> clusterIDCount : clusterToAccessesMap.entrySet()) {
if (maxClusterID == null || clusterIDCount.getValue().get() > max) {
maxClusterID = clusterIDCount.getKey();
max = clusterIDCount.getValue().get();
}
}
if (maxClusterID == null) {
maxClusterID = Config.getClusterID();
}
ServerAddress bestTM = Config.getTwoPLTransactionManagerByCluster(maxClusterID);
TwoPLTransactionService.Client xactClient =
TwoPLThriftUtil.getTransactionServiceClient(bestTM.getIP(), bestTM.getPort());
inTransaction = false;
TwoPLTransactionResult result;
final TimerContext timer = latencyMetric.time();
try {
result = xactClient.execute(xactCommands);
timer.stop();
System.out.println("Transaction committed successfully.");
for (Entry<String, ByteBuffer> value : result.requestedValues.entrySet()) {
System.out.println("Returned: " + value.getKey() + " -> "
+ value.getValue().getInt());
}
return true;
} catch (TTransactionAbortedException e) {
System.out.println("ERROR: " + e.getErrorMessage());
System.out.println("Transaction aborted.");
errorMetric.mark();
return false;
} catch (RuntimeException e) {
errorMetric.mark();
throw e;
} finally {
timer.stop();
}
}
@Override
public boolean put(String key, ByteBuffer value) throws TException {
if (!inTransaction) {
throw new TException("Must be in a transaction!");
}
xactCommands.add("put " + key + " " + new String(value.array()));
incrementCluster(key);
return true;
}
@Override
public ByteBuffer get(String key) throws TException {
if (!inTransaction) {
throw new TException("Must be in a transaction!");
}
xactCommands.add("get " + key);
incrementCluster(key);
return null;
}
private void incrementCluster(String key) {
ServerAddress address = masterRouter.getMasterAddressByKey(key);
int clusterID = address.getClusterID();
clusterToAccessesMap.putIfAbsent(clusterID, new AtomicInteger(0));
clusterToAccessesMap.get(clusterID).incrementAndGet();
}
/** Adds a raw command accepted by the Thebes Transactional Language (TTL). */
@Override
public void sendCommand(String cmd) throws TException {
if (!inTransaction) {
throw new TException("Must be in a transaction!");
}
xactCommands.add(cmd);
}
public void close() { return; }
}