com.wealoha.thrift.ThriftClientPool Maven / Gradle / Ivy
package com.wealoha.thrift;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.thrift.TServiceClient;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.wealoha.thrift.exception.ConnectionFailException;
import com.wealoha.thrift.exception.NoBackendServiceException;
import com.wealoha.thrift.exception.ThriftException;
/**
* Pool for ThriftClient
*
*
* ThriftClientPool pool = new ThriftClientPool(services, clientFactory)
*
*
* @author javamonk
* @createTime 2014年7月4日 下午3:55:16
*/
public class ThriftClientPool {
private Logger logger = LoggerFactory.getLogger(getClass());
private final Function clientFactory;
private final GenericObjectPool> pool;
private List services;
private boolean serviceReset = false;
private final PoolConfig poolConfig;
/**
* Construct a new pool using default config
*
* @param services
* @param factory
*/
public ThriftClientPool(List services, Function factory) {
this(services, factory, new PoolConfig(), null);
}
/**
* Construct a new pool using
*
* @param services
* @param factory All IFace(subclass of TServiceClient) were generated
* by thrift. We don't know their types. Since they all extends
* super class TServiceClient,
* construct a new Client need only just one line:
* transport->new Client(new TBinaryProtocol(transport))
* @param config
*/
public ThriftClientPool(List services, Function factory,
PoolConfig config) {
this(services, factory, config, null);
}
public ThriftClientPool(List services, Function factory,
PoolConfig config, ThriftProtocolFactory pFactory) {
if (services == null || services.size() == 0) {
throw new IllegalArgumentException("services is empty!");
}
if (factory == null) {
throw new IllegalArgumentException("factory is empty!");
}
if (config == null) {
throw new IllegalArgumentException("config is empty!");
}
this.services = services;
this.clientFactory = factory;
this.poolConfig = config;
// test if config change
this.poolConfig.setTestOnReturn(true);
this.poolConfig.setTestOnBorrow(true);
this.pool = new GenericObjectPool<>(new BasePooledObjectFactory>() {
@Override
public ThriftClient create() throws Exception {
// get from global list first
List serviceList = ThriftClientPool.this.services;
ServiceInfo serviceInfo = getRandomService(serviceList);
TTransport transport = getTransport(serviceInfo);
try {
transport.open();
} catch (TTransportException e) {
logger.info("transport open fail service: host={}, port={}",
serviceInfo.getHost(), serviceInfo.getPort());
if (poolConfig.isFailover()) {
while (true) {
try {
// mark current fail and try next, until none service available
serviceList = removeFailService(serviceList, serviceInfo);
serviceInfo = getRandomService(serviceList);
transport = getTransport(serviceInfo); // while break here
logger.info("failover to next service host={}, port={}",
serviceInfo.getHost(), serviceInfo.getPort());
transport.open();
break;
} catch (TTransportException e2) {
logger.warn("failover fail, services left: {}", serviceList.size());
}
}
} else {
throw new ConnectionFailException("host=" + serviceInfo.getHost() + ", ip="
+ serviceInfo.getPort(), e);
}
}
ThriftClient client = new ThriftClient<>(clientFactory.apply(transport), pool,
serviceInfo);
logger.debug("create new object for pool {}", client);
return client;
}
@Override
public PooledObject> wrap(ThriftClient obj) {
return new DefaultPooledObject<>(obj);
}
@Override
public boolean validateObject(PooledObject> p) {
ThriftClient client = p.getObject();
// check if return client in current service list if
if (serviceReset) {
if (!ThriftClientPool.this.services.contains(client.getServiceInfo())) {
logger.warn("not return object because it's from previous config {}",
client);
client.closeClient();
return false;
}
}
return super.validateObject(p);
}
@Override
public void destroyObject(PooledObject> p) throws Exception {
p.getObject().closeClient();
super.destroyObject(p);
}
}, poolConfig);
}
public List getServices() {
return services;
}
/**
* set new services for this pool
*
* @param services
*/
public void setServices(List services) {
if (services == null || services.size() == 0) {
throw new IllegalArgumentException("services is empty!");
}
this.services = services;
serviceReset = true;
}
private TTransport getTransport(ServiceInfo serviceInfo) {
if (serviceInfo == null) {
throw new NoBackendServiceException();
}
TTransport transport;
if (poolConfig.getTimeout() > 0) {
transport = new TSocket(serviceInfo.getHost(), serviceInfo.getPort(),
poolConfig.getTimeout());
} else {
transport = new TSocket(serviceInfo.getHost(), serviceInfo.getPort());
}
return transport;
}
/**
* get a random service
*
* @param serviceList
* @return
*/
private ServiceInfo getRandomService(List serviceList) {
if (serviceList == null || serviceList.size() == 0) {
return null;
}
return serviceList.get(RandomUtils.nextInt(0, serviceList.size()));
}
private List removeFailService(List list, ServiceInfo serviceInfo) {
logger.info("remove service from current service list: host={}, port={}",
serviceInfo.getHost(), serviceInfo.getPort());
return list.stream() //
.filter(si -> !serviceInfo.equals(si)) //
.collect(Collectors.toList());
}
/**
* get a client from pool
*
* @return
* @throws ThriftException
* @throws NoBackendServiceException if
* {@link PoolConfig#setFailover(boolean)} is set and no
* service can connect to
* @throws ConnectionFailException if
* {@link PoolConfig#setFailover(boolean)} not set and
* connection fail
*/
public ThriftClient getClient() throws ThriftException {
try {
return pool.borrowObject();
} catch (Exception e) {
if (e instanceof ThriftException) {
throw (ThriftException) e;
}
throw new ThriftException("Get client from pool failed.", e);
}
}
/**
* get a client's IFace from pool
*
*
* -
* Important: Iface is totally generated by
* thrift, a ClassCastException will be thrown if assign not
* match!
* -
* Limitation: The return object can only used
* once.
*
*
* @return
* @throws ThriftException
* @throws NoBackendServiceException if
* {@link PoolConfig#setFailover(boolean)} is set and no
* service can connect to
* @throws ConnectionFailException if
* {@link PoolConfig#setFailover(boolean)} not set and
* connection fail
* @throws IllegalStateException if call method on return object twice
*/
@SuppressWarnings("unchecked")
public X iface() {
ThriftClient client;
try {
client = pool.borrowObject();
} catch (Exception e) {
if (e instanceof ThriftException) {
throw (ThriftException) e;
}
throw new ThriftException("Get client from pool failed.", e);
}
AtomicBoolean returnToPool = new AtomicBoolean(false);
return (X) Proxy.newProxyInstance(this.getClass().getClassLoader(), client.iFace()
.getClass().getInterfaces(), (proxy, method, args) -> {
if (returnToPool.get()) {
throw new IllegalStateException("Object returned via iface can only used once!");
}
boolean success = false;
try {
Object result = method.invoke(client.iFace(), args);
success = true;
return result;
} finally {
if (success) {
pool.returnObject(client);
} else {
client.closeClient();
pool.invalidateObject(client);
}
returnToPool.set(true);
}
});
}
@Override
protected void finalize() throws Throwable {
if (pool != null) {
pool.close();
}
super.finalize();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy