源码分析并解决 HttpAsyncClient 关于 Connection lease request time out 异常

问题描述:

接到相应反馈后,我马上去追踪问题点。首先是定位到通知商户的类是 HttpAsyncClient 。接着去看系统的配置,如下:

这是我就会去想。是连接数的问题还是连接池的问题,还是应用本身机器的的问题呢?明明是异步客户端,为啥还会频繁出现超时的问题呢?带着疑问,我就一路去追踪源码。最后找到了报错的信息点。如图。

源码分析并解决 HttpAsyncClient 关于 Connection lease request time out 异常_第1张图片

好了,找到问题的爆发点,就成功了一半。究竟具体是什么原因造成的呢?我就从源码一步一步地分析。

源码分析并解决 HttpAsyncClient 关于 Connection lease request time out 异常_第2张图片

源码分析并解决 HttpAsyncClient 关于 Connection lease request time out 异常_第3张图片

首先是AbstractNIOConnPool 类的 lease 方法

public Future lease(
            final T route, final Object state,
            final long connectTimeout, final long leaseTimeout, final TimeUnit tunit,
            final FutureCallback callback) {
        Args.notNull(route, "Route");
        Args.notNull(tunit, "Time unit");
        Asserts.check(!this.isShutDown.get(), "Connection pool shut down");
        final BasicFuture future = new BasicFuture(callback);
        this.lock.lock(); // 同步
        try {
            final long timeout = connectTimeout > 0 ? tunit.toMillis(connectTimeout) : 0;
            final LeaseRequest request = new LeaseRequest(route, state, timeout, leaseTimeout, future);
            final boolean completed = processPendingRequest(request); //  从连接池获取连接的方法
            if (!request.isDone() && !completed) {  // 因为连接池满而不能马上获得连接的的, 加入到一个leasing的LinkedList中, 它会在后续的某些操作中被取出来重新尝试连接发送请求(我们系统报错是因为获取不到连接重新放回到这里面)
                this.leasingRequests.add(request);
            }
            if (request.isDone()) {  
                this.completedRequests.add(request);
            }
        } finally {
            this.lock.unlock();
        }
        fireCallbacks();
        return future;
    }

//接下来是 processPendingRequest 方法。
private boolean processPendingRequest(final LeaseRequest request) {
        final T route = request.getRoute();
        final Object state = request.getState();
        final long deadline = request.getDeadline();

        final long now = System.currentTimeMillis();
        if (now > deadline) {   //现在系统时间大于超时时间(这里是我们的爆发点)
 	    request.failed(new TimeoutException("Connection lease request time out"));
            return false;
        }

        final RouteSpecificPool pool = getPool(route);
        E entry;
        for (;;) { // 租借连接池连接
            entry = pool.getFree(state); // getFree即是从available中获取一个state匹配的连接
            if (entry == null) { // 没有可用连接退出循环
                break;
            }
            // 清除不可用连接
            if (entry.isClosed() || entry.isExpired(System.currentTimeMillis())) {
                entry.close();
                this.available.remove(entry);
                pool.free(entry, false);
            } else {
                break;
            }
        }
        if (entry != null) { // 找到连接退出
            this.available.remove(entry);
            this.leased.add(entry);
            request.completed(entry);
            onLease(entry);
            return true;
        }

        // 需要新连接的情况
        final int maxPerRoute = getMax(route);
        // 已经分配的连接超出可分配限制
        // Shrink the pool prior to allocating a new connection
        final int excess = Math.max(0, pool.getAllocatedCount() + 1 - maxPerRoute);//(因为我们配置连接池最大是一个,所以这里是1+1 -1  = 1)

        // 对连接池进行缩减, 将上次使用的连接关闭并删除, 直到超出的连接全被清除
        if (excess > 0) {
            for (int i = 0; i < excess; i++) {
                final E lastUsed = pool.getLastUsed(); // 这个方法是取到 available 里的最后一个连接, 也就是说会出现所有连接都被租借出去了的情况, 这样的话就相当于连接池满, 到下一步的 if (pool.getAllocatedCount() < maxPerRoute) 即会 false, 最后导致request进入 leasingRequest 列表
                if (lastUsed == null) {
                    break;
                }
                lastUsed.close();
                this.available.remove(lastUsed);
                pool.remove(lastUsed);
            }
        }

        // 已分配连接数 < 最大连接数限制, 开始新建(我们报错会执行这里,因为我们连接池只配置一个,所以放回fasle,导致request进入 leasingRequest 列表)
        if (pool.getAllocatedCount() < maxPerRoute) {
            // 总共被使用的数量等于 正在等待连接数 + 已经租借出去的连接数
            final int totalUsed = this.pending.size() + this.leased.size();
            final int freeCapacity = Math.max(this.maxTotal - totalUsed, 0);
            if (freeCapacity == 0) {
                return false;
            }
            // 需要注意的是pool里available不为空, 也有可能拿不到可用连接, 因为state不匹配
            final int totalAvailable = this.available.size();
            if (totalAvailable > freeCapacity - 1) {
                if (!this.available.isEmpty()) {
                    final E lastUsed = this.available.removeLast();
                    lastUsed.close();
                    final RouteSpecificPool otherpool = getPool(lastUsed.getRoute());
                    otherpool.remove(lastUsed);
                }
            }

            // 创建连接监视器阶段, 创建了一个监时此次请求的监视对象 SessionRequest, 并调用selector的wakeup(), 出发实际的连接操作
            final SocketAddress localAddress;
            final SocketAddress remoteAddress;
            try {
                remoteAddress = this.addressResolver.resolveRemoteAddress(route);
                localAddress = this.addressResolver.resolveLocalAddress(route);
            } catch (final IOException ex) {
                request.failed(ex);
                return false;
            }
            final SessionRequest sessionRequest = this.ioreactor.connect(
                    remoteAddress, localAddress, route, this.sessionRequestCallback);
            final int timout = request.getConnectTimeout() < Integer.MAX_VALUE ?
                    (int) request.getConnectTimeout() : Integer.MAX_VALUE;
            sessionRequest.setConnectTimeout(timout);
            // 加入到总pending集合
            this.pending.add(sessionRequest);
            // 加入到route连接池pending集合
            pool.addPending(sessionRequest, request.getFuture());
            return true;
        } else {
            return false;
        }
    }

    // 检查最后一个完成的request的结果, 并设置future的状态
    private void fireCallbacks() {
        LeaseRequest request;
        while ((request = this.completedRequests.poll()) != null) {
            final BasicFuture future = request.getFuture();
            final Exception ex = request.getException();
            final E result = request.getResult();
            if (ex != null) {
                future.failed(ex);
            } else if (result != null) {
                future.completed(result);
            } else {
                future.cancel();
            }
        }
    }

 

 

最后加入到leasingRequest,加入到这里地方后,后续就会被执行,执行的时候当前时间已经大于了我们设置获取连接池的时间 3 秒,所以就报这个错了。

 

解决办法:

将连接池的连接数调大,同时适当调大连接池连接时间,即可解决问题。生产环境最后也是这么操作后,问题得以解决。

 

你可能感兴趣的:(java,网络)