透明代理的使用 在對後端的連接中使用對象池(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的壓力