/**
*
*/
package com.ganji.as.thrift.protocol.client.socket.async.pool;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
import org.slf4j.Logger;
import com.ganji.as.thrift.protocol.builder.ClientBuildingConfig;
import com.ganji.as.thrift.protocol.client.request.ThriftClientInvocation;
import com.ganji.as.thrift.protocol.cluster.load.balance.LoadBalance;
import com.ganji.as.thrift.protocol.server.nodes.discovery.ServerNode;
import com.ganji.as.thrift.protocol.server.nodes.discovery.ServerNodesDiscovery;
import com.ganji.as.thrift.protocol.server.nodes.discovery.ServerNodesDiscoveryProvider;
/**
* @author yikangfeng
* @date 2015年7月21日
*/
class SimpleSocketConnectionPoolProviderImpl extends AbstractSocketConnectionPool
implements AutoCloseable {
final private Logger LOGGER_;
final private ClientBuildingConfig clientBuildingConfig_;
final private AtomicBoolean isReady_ = new AtomicBoolean(false);
final int tcpConnectionTimeout_;
final private ConcurrentMap<String, ConcurrentMap<String/* socket connection identity */, SocketConnection>> transportPool_ = new ConcurrentHashMap<>();
final private ServerNodesDiscovery serverNodesDiscovery_;
static public enum ConnectionMode {
HOST, CLUSTER
}
private ConnectionMode connectionMode_;
public SimpleSocketConnectionPoolProviderImpl(
final ClientBuildingConfig clientBuildingConfig) throws Throwable {
this.LOGGER_ = clientBuildingConfig.getLogger();
this.clientBuildingConfig_ = clientBuildingConfig;
this.serverNodesDiscovery_ = ServerNodesDiscoveryProvider.FACTORY
.createServerNodesDiscovery(this.clientBuildingConfig_);
this.serverNodesDiscovery_.nodesDiscovery(this.clientBuildingConfig_
.getDestAddr());
this.tcpConnectionTimeout_ = clientBuildingConfig
.getTcpConnectTimeout();
prepare();
}
private void prepare() throws Throwable {
if (!(this.clientBuildingConfig_.getHosts() == null))
this.connectionMode_ = ConnectionMode.HOST;
if (!(this.clientBuildingConfig_.getDestAddr() == null || this.clientBuildingConfig_
.getDestAddr().isEmpty()))
this.connectionMode_ = ConnectionMode.CLUSTER;
if (this.connectionMode_ == null)
throw new IllegalStateException("Hosts or dest needs at least one.");
int hostConnectionCoreSize = this.clientBuildingConfig_
.getHostConnectionCoreSize();
final InetSocketAddress hostAddress = (InetSocketAddress) this.clientBuildingConfig_
.getHosts();
final List<ServerNode> serverNodes = serverNodesDiscovery_.nodes();
while (hostConnectionCoreSize > 0) {
ConcurrentMap<String, SocketConnection> transports = null;
if (this.connectionMode_ == ConnectionMode.HOST) {
final String key = String.format("%s:%s",
hostAddress.getHostName(), hostAddress.getPort());
transports = transportPool_.get(key);
if (transports == null || transports.isEmpty())
transports = new ConcurrentHashMap<>();
final SocketConnection proxy = createSocketConnection(
hostAddress.getHostName(), hostAddress.getPort());
transports.put(proxy.getIdentity(), proxy);
transportPool_.putIfAbsent(key, transports);
} else if (this.connectionMode_ == ConnectionMode.CLUSTER) {
if (serverNodes != null && !serverNodes.isEmpty()) {
for (final ServerNode serverNode_ : serverNodes) {
if (serverNode_ == null)
continue;
final String key = String.format("%s:%s",
serverNode_.getHost(), serverNode_.getPort());
if (transports == null || transports.isEmpty())
transports = new ConcurrentHashMap<>();
final SocketConnection proxy = createSocketConnection(
serverNode_.getHost(), serverNode_.getPort());
transports.put(proxy.getIdentity(), proxy);
transportPool_.putIfAbsent(key, transports);
}
}
}
hostConnectionCoreSize--;
}
isReady_.getAndSet(true);
if (this.LOGGER_ != null) {
if (this.LOGGER_.isInfoEnabled())
this.LOGGER_
.info("The socket connection pool prepare complete.");
}
}
private SocketConnection createSocketConnection(final String hostName,
final int port) throws IOException {
return new SocketConnectionProxy(hostName, port,
this.clientBuildingConfig_.getTcpConnectTimeout());
}
@Override
public SocketConnection getInternalSocketConnection(
final LoadBalance loadBalance,
final ThriftClientInvocation clientInvocation) throws Throwable {
// TODO Auto-generated method stub
SocketConnection socketConnection = null;
if (this.connectionMode_ == ConnectionMode.HOST) {
final InetSocketAddress hostAddress = (InetSocketAddress) this.clientBuildingConfig_
.getHosts();
socketConnection = getSocketConnectionByHostAndPort(
hostAddress.getHostName(), hostAddress.getPort());
} else if (this.connectionMode_ == ConnectionMode.CLUSTER) {
final List<ServerNode> serverNodes = serverNodesDiscovery_.nodes();
final ServerNode selectedServerNode = loadBalance.select(
serverNodes, clientInvocation);
if (this.LOGGER_ != null) {
if (this.LOGGER_.isInfoEnabled())
this.LOGGER_.info(String.format(
"Load balancing strategy selected node is {%s}",
selectedServerNode));
}
if (selectedServerNode == null)
return socketConnection;
socketConnection = getSocketConnectionByHostAndPort(
selectedServerNode.getHost(), selectedServerNode.getPort());
}
return socketConnection;
}
@Override
public SocketConnection getInternalSocketConnectionByHostAndPort(
final String host, final int port) throws Throwable {
// TODO Auto-generated method stub
if (host == null || host.isEmpty() || port <= 0)
throw new IllegalArgumentException(
"The parameter host or port is not valid.");
final String hostAndPort = String.format("%s:%s", host, port);
SocketConnection availableSocketConnection = internalAcquireSocketConnection(hostAndPort);
if (!(availableSocketConnection == null))
return availableSocketConnection;
final int hostConnectionLimit = this.clientBuildingConfig_
.getHostConnectionLimit();
final ConcurrentMap<String, SocketConnection> transports = this.transportPool_
.get(hostAndPort);
final int currentHostConnectionCount = transports.size();
if (currentHostConnectionCount < hostConnectionLimit) {
// create a new connection.
if (this.LOGGER_ != null) {
if (this.LOGGER_.isInfoEnabled())
this.LOGGER_.info("create a new socket connection.");
}
final SocketConnection newSocketConnection = createSocketConnection(
host, port);
transports.putIfAbsent(newSocketConnection.getIdentity(),
newSocketConnection);
if (newSocketConnection.isIdle())
availableSocketConnection = newSocketConnection;
} else
availableSocketConnection = blockingUnitlAcquireSocketConnection(hostAndPort);
if (availableSocketConnection == null)
throw new NullPointerException(
"No idle socket connection is obtained.");
return availableSocketConnection;
}
private SocketConnection internalAcquireSocketConnection(final String key) {
ConcurrentMap<String/* host:port */, SocketConnection> transports = this.transportPool_
.get(key);
if (transports == null)
this.transportPool_
.putIfAbsent(
key,
transports = new ConcurrentHashMap<String, SocketConnection>());
SocketConnection returnSocketConnection = null;
for (final SocketConnection socketConnection : transports.values()) {
if (socketConnection.isIdle())
returnSocketConnection = socketConnection;
}
return returnSocketConnection;
}
private SocketConnection blockingUnitlAcquireSocketConnection(
final String key) {
// try acquire once
SocketConnection returnSocketConnection = internalAcquireSocketConnection(key);
if (LOGGER_ != null) {
if (LOGGER_.isInfoEnabled())
LOGGER_.info("blocking acquire idle socket connection start...");
}
if (returnSocketConnection == null) {
REPEAT_ACQUIRE: while (true) {
returnSocketConnection = internalAcquireSocketConnection(key);
if (returnSocketConnection == null) {
LockSupport.parkNanos(10000000L);
continue REPEAT_ACQUIRE;
} else {
if (LOGGER_ != null) {
if (LOGGER_.isInfoEnabled())
LOGGER_.info("blocking acquire idle socket connection successful.");
}
break REPEAT_ACQUIRE;
}
}
}
if (LOGGER_ != null) {
if (LOGGER_.isInfoEnabled())
LOGGER_.info("blocking acquire idle socket connection end...");
}
return returnSocketConnection;
}
@Override
public void destory() {
// TODO Auto-generated method stub
final Collection<ConcurrentMap<String, SocketConnection>> values = this.transportPool_
.values();
if (values != null) {
for (final ConcurrentMap<String, SocketConnection> map : values) {
if (map == null || map.isEmpty())
continue;
for (final SocketConnection socketConnection : map.values()) {
if (socketConnection == null)
continue;
socketConnection.get().close();
}
}
}
this.transportPool_.clear();
if (this.serverNodesDiscovery_ != null) {
try {
this.serverNodesDiscovery_.close();
} catch (final Exception ignored) {
}
}
}
@Override
public boolean isReady() {
// TODO Auto-generated method stub
return isReady_.get();
}
@Override
public void close() throws Exception {
// TODO Auto-generated method stub
this.destory();
}
@Override
public void removeSocketConnection(final SocketConnection socketConnection) {
// TODO Auto-generated method stub
if (socketConnection == null)
return;
final String hostName = socketConnection.getHostName();
final int port = socketConnection.getPort();
final String socketConnectionIdentity = socketConnection.getIdentity();
final ConcurrentMap<String, SocketConnection> connections = this.transportPool_
.get(String.format("%s:%s", hostName, port));
if (connections == null || connections.isEmpty())
return;
if (!(connections.get(socketConnectionIdentity) == null))
connections.get(socketConnectionIdentity).get().close();
connections.remove(socketConnectionIdentity);
}
@Override
public void returnSocketConnection(SocketConnection socketConnection) {
// TODO Auto-generated method stub
}
}