對象池 GenericObjectPool 通用對象池

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章