对象池 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的压力

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值