package net.rubyeye.xmemcached.aws;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.code.yanf4j.config.Configuration;
import com.google.code.yanf4j.core.Session;
import com.google.code.yanf4j.core.SocketOption;
import net.rubyeye.xmemcached.CommandFactory;
import net.rubyeye.xmemcached.MemcachedClientStateListener;
import net.rubyeye.xmemcached.MemcachedSessionLocator;
import net.rubyeye.xmemcached.XMemcachedClient;
import net.rubyeye.xmemcached.XMemcachedClientBuilder;
import net.rubyeye.xmemcached.auth.AuthInfo;
import net.rubyeye.xmemcached.buffer.BufferAllocator;
import net.rubyeye.xmemcached.buffer.SimpleBufferAllocator;
import net.rubyeye.xmemcached.command.Command;
import net.rubyeye.xmemcached.command.TextCommandFactory;
import net.rubyeye.xmemcached.exception.MemcachedException;
import net.rubyeye.xmemcached.impl.ArrayMemcachedSessionLocator;
import net.rubyeye.xmemcached.transcoders.SerializingTranscoder;
import net.rubyeye.xmemcached.transcoders.Transcoder;
import net.rubyeye.xmemcached.utils.InetSocketAddressWrapper;
/**
* AWS ElasticCache Client.
*
* @since 2.3.0
* @author dennis
*
*/
public class AWSElasticCacheClient extends XMemcachedClient implements
ConfigUpdateListener {
private static final Logger log = LoggerFactory
.getLogger(AWSElasticCacheClient.class);
private boolean firstTimeUpdate = true;
private List<InetSocketAddress> configAddrs = new ArrayList<InetSocketAddress>();
public synchronized void onUpdate(ClusterConfigration config) {
if (firstTimeUpdate) {
firstTimeUpdate = false;
removeConfigAddrs();
}
List<CacheNode> oldList = this.currentClusterConfiguration != null ? this.currentClusterConfiguration
.getNodeList() : Collections.EMPTY_LIST;
List<CacheNode> newList = config.getNodeList();
List<CacheNode> addNodes = new ArrayList<CacheNode>();
List<CacheNode> removeNodes = new ArrayList<CacheNode>();
for (CacheNode node : newList) {
if (!oldList.contains(node)) {
addNodes.add(node);
}
}
for (CacheNode node : oldList) {
if (!newList.contains(node)) {
removeNodes.add(node);
}
}
// Begin to update server list
for (CacheNode node : addNodes) {
try {
this.connect(new InetSocketAddressWrapper(node
.getInetSocketAddress(), this.configPoller
.getCacheNodeOrder(node), 1, null));
} catch (IOException e) {
log.error("Connect to " + node + "failed.", e);
}
}
for (CacheNode node : removeNodes) {
try {
this.removeAddr(node.getInetSocketAddress());
} catch (Exception e) {
log.error("Remove " + node + " failed.");
}
}
this.currentClusterConfiguration = config;
}
private void removeConfigAddrs() {
for (InetSocketAddress configAddr : this.configAddrs) {
this.removeAddr(configAddr);
while (this.getConnector().getSessionByAddress(configAddr) != null
&& this.getConnector().getSessionByAddress(configAddr)
.size() > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
private final ConfigurationPoller configPoller;
/**
* Default elasticcache configuration poll interval, it's one minute.
*/
public static final long DEFAULT_POLL_CONFIG_INTERVAL_MS = 60000;
/**
* Construct an AWSElasticCacheClient instance with one config address and
* default poll interval.
*
* @since 2.3.0
* @param addr
* config server address.
* @throws IOException
*/
public AWSElasticCacheClient(InetSocketAddress addr) throws IOException {
this(addr, DEFAULT_POLL_CONFIG_INTERVAL_MS);
}
/**
* Construct an AWSElasticCacheClient instance with one config address and
* poll interval.
*
* @since 2.3.0
* @param addr
* config server address.
* @param pollConfigIntervalMills
* config poll interval in milliseconds.
* @throws IOException
*/
public AWSElasticCacheClient(InetSocketAddress addr,
long pollConfigIntervalMills) throws IOException {
this(addr, pollConfigIntervalMills, new TextCommandFactory());
}
public AWSElasticCacheClient(InetSocketAddress addr,
long pollConfigIntervalMills, CommandFactory cmdFactory)
throws IOException {
this(asList(addr), pollConfigIntervalMills, cmdFactory);
}
private static List<InetSocketAddress> asList(InetSocketAddress addr) {
List<InetSocketAddress> addrs = new ArrayList<InetSocketAddress>();
addrs.add(addr);
return addrs;
}
/**
* Construct an AWSElasticCacheClient instance with config server addresses
* and default config poll interval.
*
* @since 2.3.0
* @param addrs
* config server list.
* @throws IOException
*/
public AWSElasticCacheClient(List<InetSocketAddress> addrs)
throws IOException {
this(addrs, DEFAULT_POLL_CONFIG_INTERVAL_MS);
}
/**
* Construct an AWSElasticCacheClient instance with config server addresses.
*
* @since 2.3.0
* @param addrs
* @param pollConfigIntervalMills
* @throws IOException
*/
public AWSElasticCacheClient(List<InetSocketAddress> addrs,
long pollConfigIntervalMills) throws IOException {
this(addrs, pollConfigIntervalMills, new TextCommandFactory());
}
/**
* Construct an AWSElasticCacheClient instance with config server addresses.
*
* @since 2.3.0
* @param addrs
* config server list.
* @param pollConfigIntervalMills
* config poll interval in milliseconds.
* @param commandFactory
* protocol command factory.
* @throws IOException
*/
@SuppressWarnings("unchecked")
public AWSElasticCacheClient(List<InetSocketAddress> addrs,
long pollConfigIntervalMills, CommandFactory commandFactory)
throws IOException {
this(new ArrayMemcachedSessionLocator(), new SimpleBufferAllocator(),
XMemcachedClientBuilder.getDefaultConfiguration(),
XMemcachedClientBuilder.getDefaultSocketOptions(),
new TextCommandFactory(), new SerializingTranscoder(),
(List<MemcachedClientStateListener>) Collections.EMPTY_LIST,
(Map<InetSocketAddress, AuthInfo>) Collections.EMPTY_MAP, 1,
XMemcachedClient.DEFAULT_CONNECT_TIMEOUT, null, true, addrs,
pollConfigIntervalMills);
}
private static Map<InetSocketAddress, InetSocketAddress> getAddressMapFromConfigAddrs(
List<InetSocketAddress> configAddrs) {
Map<InetSocketAddress, InetSocketAddress> m = new HashMap<InetSocketAddress, InetSocketAddress>();
for (InetSocketAddress addr : configAddrs) {
m.put(addr, null);
}
return m;
}
AWSElasticCacheClient(MemcachedSessionLocator locator,
BufferAllocator allocator, Configuration conf,
Map<SocketOption, Object> socketOptions,
CommandFactory commandFactory, Transcoder transcoder,
List<MemcachedClientStateListener> stateListeners,
Map<InetSocketAddress, AuthInfo> map, int poolSize,
long connectTimeout, String name, boolean failureMode,
List<InetSocketAddress> configAddrs, long pollConfigIntervalMills)
throws IOException {
super(locator, allocator, conf, socketOptions, commandFactory,
transcoder, getAddressMapFromConfigAddrs(configAddrs),
stateListeners, map, poolSize, connectTimeout, name,
failureMode);
if (pollConfigIntervalMills <= 0) {
throw new IllegalArgumentException(
"Invalid pollConfigIntervalMills value.");
}
// Use failure mode by default.
this.commandFactory = commandFactory;
this.setFailureMode(true);
this.configAddrs = configAddrs;
this.configPoller = new ConfigurationPoller(this,
pollConfigIntervalMills);
// Run at once to get config at startup.
// It will call onUpdate in the same thread.
this.configPoller.run();
if (this.currentClusterConfiguration == null) {
throw new IllegalStateException(
"Retrieve ElasticCache config from `"
+ configAddrs.toString() + "` failed.");
}
this.configPoller.start();
}
private volatile ClusterConfigration currentClusterConfiguration;
/**
* Get cluster config from cache node by network command.
*
* @return
*/
public ClusterConfigration getConfig() throws MemcachedException,
InterruptedException, TimeoutException {
return this.getConfig("cluster");
}
/**
* Get config by key from cache node by network command.
*
* @since 2.3.0
* @return clusetr config.
*/
public ClusterConfigration getConfig(String key) throws MemcachedException,
InterruptedException, TimeoutException {
Command cmd = this.commandFactory.createAWSElasticCacheConfigCommand(
"get", key);
final Session session = this.sendCommand(cmd);
this.latchWait(cmd, opTimeout, session);
cmd.getIoBuffer().free();
this.checkException(cmd);
String result = (String) cmd.getResult();
if (result == null) {
throw new MemcachedException(
"Operation fail,may be caused by networking or timeout");
}
return AWSUtils.parseConfiguration(result);
}
/**
* Get the current using configuration in memory.
*
* @since 2.3.0
* @return current cluster config.
*/
public ClusterConfigration getCurrentConfig() {
return this.currentClusterConfiguration;
}
}