透明代理的使用 在对后端的连接中使用对象池(backend是一个池子 里面是一个域名的连接)
使用需要用到的组件
- GenericObjectPool.Config
- DefaultChannelGroup
- PoolableObjectFactory BasePoolableObjectFactory
- GenericObjectPool
调用的流程
- 需要重写的函数 makeObject、 validateObject、 destroyObject
- 1、makeObject 创建对象的具体实现
- 2、borrowObject 获取对象池中的对象简单而言就是去LinkedList中获取一个对象,如果不存在的话,要调用构造方法中第一个参数Factory工厂类的makeObject()方法去创建一个对象再获取,获取到对象后要调用validateObject方法判断该对象是否是可用的,如果是可用的才拿去使用。LinkedList容器减一
- 3、returnObject 先调用validateObject方法判断该对象是否是可用的,如果可用则归还到池中,LinkedList容器加一,如果是不可以的则则调用destroyObject方法进行销毁。
避免泄漏
- 产生原因
在一些极端的情况下出现 borrowObject/invalidateObject 没有被调用导致的泄漏问题。
对象泄漏会导致对象池中的对象数量一直上升,达到设置的上限
再调用 borrowObject 就会永远等待或者抛出java.util.NoSuchElementException: Timeout waiting for idle object 异常。
- 解决策略
1. 设置抛弃时间
GenericObjectPool判断一个对象是否泄漏是根据对象最后一次使用或者最后一次borrow的时间进行判断的,
如果超出了预设的值就会被认为是一个泄漏的对象被清理掉(PooledObjectFactory.destroyObject在这一过程中会被调用)。
抛弃时间可以通过 AbandonedConfig.setRemoveAbandonedTimeout 进行设置,时间单位是秒
2. 设置了抛弃时间以后还需要打开泄漏清理才会生效。泄漏判断的开启可以通过两种方式
> 从对象池中获取对象的时候进行清理如果当前对象池中少于2个idle状态的对象或者 active数量>最大对象数-3 的时候,
在borrow对象的时候启动泄漏清理。通过 AbandonedConfig.setRemoveAbandonedOnBorrow 为 true 进行开启。
> 启动定时任务进行清理AbandonedConfig.setRemoveAbandonedOnMaintenance 设置为 true 以后,
在维护任务运行的时候会进行泄漏对象的清理,可以通过 GenericObjectPool.setTimeBetweenEvictionRunsMillis 设置维护任务执行的时间间隔。
- 使用例子
GenericObjectPool<PoolObj> pool = new GenericObjectPool<PoolObj>(new MyPooledObjectFactory(),config);
AbandonedConfig abandonedConfig = new AbandonedConfig();
abandonedConfig.setRemoveAbandonedOnMaintenance(true); //在Maintenance的时候检查是否有泄漏
abandonedConfig.setRemoveAbandonedOnBorrow(true); //borrow 的时候检查泄漏
abandonedConfig.setRemoveAbandonedTimeout(10); //如果一个对象borrow之后10秒还没有返还给pool,认为是泄漏的对象
pool.setAbandonedConfig(abandonedConfig);
pool.setTimeBetweenEvictionRunsMillis(5000); //5秒运行一次维护任务
透明代理使用demo
- 初始化定义
GenericObjectPool.Config poolConfig = new GenericObjectPool.Config();
poolConfig.maxActive = config.getIntAttribute("maxActive", DEFAULT_POOLCONFIG_MAXACTIVE);
poolConfig.maxIdle = config.getIntAttribute("maxIdle", DEFAULT_POOLCONFIG_MAXIDLE);
poolConfig.minIdle = config.getIntAttribute("minIdle", DEFAULT_POOLCONFIG_MINIDLE);
poolConfig.maxWait = config.getIntAttribute("maxWait", DEFAULT_POOLCONFIG_MAXWAIT);
poolConfig.testOnBorrow = true;
poolConfig.testOnReturn = true;
poolConfig.whenExhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_GROW;
port = config.getIntAttribute("port", 80);
int idleTimeout = config.getIntAttribute("idleTimeout", DEFAULT_IDLE_TIMEOUT);
int connectionTimeout = config.getIntAttribute("connectionTimeout", DEFAULT_CONNECTION_TIMEOUT);
int maxResponseLength = config.getIntAttribute("maxResponseLength", DEFAULT_MAX_RESPONSE_LENGTH);
channelGroup = new DefaultChannelGroup();
channelPool =new BackendChannelPool(host, port, idleTimeout, connectionTimeout, maxResponseLength,
clientSocketChannelFactory, timer, poolConfig,
new HostBackendHandlerListener() {
@Override
public void onExceptionCaught(BackendRequest request,
ChannelHandlerContext ctx, ExceptionEvent e) {
try {
if (request == null) {
return;
}
// 如果是 ClosedChannelException 则无需使pool中的连接失效,因为
// 创建连接的时候已经添加了连接关闭监听器来处理 参看 BackendConnection
if (!(e.getCause() instanceof ClosedChannelException)
&& !(e.getCause() instanceof ConnectException)) {
BackendConnection connection = request.getConnection();
channelPool.invalidateObject(connection);
}
} catch (Exception ex) {
LogUtils.error(" invalidate object error ", ex);
}
}
@Override
public void onMessageReceived(BackendRequest request,
ChannelHandlerContext ctx, MessageEvent e) {
}
@Override
public void onChannelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) {
channelGroup.add(ctx.getChannel());
}
@Override
public void onMessageProcessed(BackendRequest request,
ProxyHttpResponse response, boolean closeConnection,
ChannelHandlerContext ctx, MessageEvent e) {
BackendConnection connection = request.getConnection();
try {
if (closeConnection) {
if (connection.isOpen()) {
channelPool.invalidateObject(connection);
}
} else {
channelPool.returnObject(connection);
}
} catch (Exception ex) {
LogUtils.error(" return object error ", ex);
}
}
},backendExecutor);
String detectPath = config.getAttribute("detectPath");
int detectPeriod = config.getIntAttribute("detectPeriod", DEFAULT_DETECT_PERIOD_TIME);
this.detectTimes = config.getIntAttribute("detectTimes", DEFAULT_DETECT_TIMES);
if (!StringUtils.isBlank(detectPath)) {
client = new NettyHttpClient(timer);
LogUtils.info(this.getName() + " start schedule backend detect " + host + ":" + port + detectPath);
detectExecutor = Executors.newSingleThreadScheduledExecutor();
detectExecutor.scheduleAtFixedRate(new BackendDetectThread(host, port, detectPath, client),
detectPeriod, detectPeriod, TimeUnit.SECONDS);
}
}
- 创建对象
public Object makeObject() throws Exception {
if(LogUtils.isTraceEnabled()){
LogUtils.trace("BackendChannelPool makeObject");
}
// await*() in I/O thread causes a dead lock or sudden performance drop.
// pool.borrowObject() 必须在新的线程中执行
// Configure the client.
final ClientBootstrap cb = new ClientBootstrap(
clientSocketChannelFactory);
final BlockingQueue<BackendRequest> requestQueue = new LinkedBlockingQueue<BackendRequest>();
final ChannelPipelineFactory cpf = new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() throws Exception {
// Create a default pipeline implementation.
final ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("timeout", new ProxyIdleStateHandler(timer, 0, 0, idleTimeout, TimeUnit.MILLISECONDS));
pipeline.addLast("decoder", new ProxyHttpResponseDecoder());
pipeline.addLast("aggregator", new ProxyHttpChunkAggregator(maxResponseLength));
final BackendRelayingHandler handler = new BackendRelayingHandler(
handlerListener,requestQueue,backendExecutor);
final BackendRequestEncoder encoder = new BackendRequestEncoder(requestQueue);
pipeline.addLast("encoder", encoder);
pipeline.addLast("handler", handler);
return pipeline;
}
};
// Set up the event pipeline factory.
cb.setPipelineFactory(cpf);
//TODO more option config.
cb.setOption("connectTimeoutMillis", connectionTimeout * 1000);
ChannelFuture future = cb.connect(new InetSocketAddress(host,
port));
if(LogUtils.isDebugEnabled()){
LogUtils.debug("ClientChannelObjectFactory.makeObject ChannelFuture: "+host+":"+port);
}
future = future.await();
if(future.isCancelled()){
throw new ConnectTimeoutException("request cancelled.");
}else if(!future.isSuccess()){
throw new ConnectTimeoutException(future.getCause());
}else{
return new BackendConnection(future.getChannel());
}
}
重要优化点
-
connectionPool.setMaxActive(maxActive); connectionPool.setMaxIdle(maxActive); 成对配置
-
设置maxWait connectionPool.setMaxWait(maxWait);
不设置会导致当被依赖的服务由于某些故障(如机器资源被某进程大量占用)而响应极慢时,会有大量线程blocked在borrowObject的逻辑,最终导致resin线程耗尽,服务卡死,用户请求被拒绝
因此需要对maxWait进行设置,且设置的时间不宜过大(高吞吐时仍有可能导致web卡死),也不宜过小(可能导致较多的请求失败)
基本原则: qt<N, 其中q为服务最大吞吐(请求/s), t为设置的maxWait时间(s), N为resin的最大线程数 resin的线程数当前为512, 预估最大吞吐为100, 因此有t<N/q=512/100=5.12 我们将其配置为3s(即3000ms), 从而当该对象池中的对象出现异常时,仍可扛住512/3约为170qps的压力