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

问题描述:

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

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

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

首先是AbstractNIOConnPool 类的 lease 方法

public Future<E> lease(
            final T route, final Object state,
            final long connectTimeout, final long leaseTimeout, final TimeUnit tunit,
            final FutureCallback<E> callback) {
        Args.notNull(route, "Route");
        Args.notNull(tunit, "Time unit");
        Asserts.check(!this.isShutDown.get(), "Connection pool shut down");
        final BasicFuture<E> future = new BasicFuture<E>(callback);
        this.lock.lock(); // 同步
        try {
            final long timeout = connectTimeout > 0 ? tunit.toMillis(connectTimeout) : 0;
            final LeaseRequest<T, C, E> request = new LeaseRequest<T, C, E>(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<T, C, E> 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<T, C, E> 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<T, C, E> 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<T, C, E> request;
        while ((request = this.completedRequests.poll()) != null) {
            final BasicFuture<E> 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 秒,所以就报这个错了。

 

解决办法:

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

 

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Redisson是一个基于Redis的Java驻留库,提供了分布式和线程安全的Java数据结构。Redisson的分布式锁实现是基于Redis的setnx命令和Lua脚本实现的。下面是Redisson分布式锁的源码分析: 1.获取锁 Redisson的分布式锁获取方法是tryAcquire方法,该方法首先会尝试使用setnx命令在Redis中创建一个key,如果创建成功则表示获取锁成功,否则会进入自旋等待。在自旋等待期间,Redisson会使用watchDog机制来监控锁的状态,如果锁被其他线程释放,则会重新尝试获取锁。 2.释放锁 Redisson的分布式锁释放方法是release方法,该方法会使用Lua脚本来判断当前线程是否持有锁,如果持有锁则会使用del命令删除锁的key。 3.watchDog机制 Redisson的watchDog机制是用来监控锁的状态的,该机制会在获取锁时启动一个定时任务,定时任务会检查锁的状态,如果锁被其他线程释放,则会重新尝试获取锁。 ```java // 获取锁 public boolean tryAcquire(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException { long time = unit.toMillis(waitTime); long current = System.currentTimeMillis(); final long threadId = Thread.currentThread().getId(); final long leaseTimeInMillis = unit.toMillis(leaseTime); while (true) { if (tryAcquire()) { scheduleExpirationRenewal(threadId, leaseTimeInMillis); return true; } time -= (System.currentTimeMillis() - current); if (time <= 0) { return false; } current = System.currentTimeMillis(); if (Thread.interrupted()) { throw new InterruptedException(); } // watchDog机制 RFuture<RedissonLockEntry> future = subscribe(threadId); if (!future.await(time, TimeUnit.MILLISECONDS)) { return false; } } } // 释放锁 public void unlock() { if (isHeldByCurrentThread()) { unlockInner(); } } private void unlockInner() { Long ttl = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG, "if (redis.call('hexists', KEYS[1], ARGV[2]) == 0) then return nil end; " + "local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1); " + "if (counter > 0) then return 0 end; " + "redis.call('del', KEYS[1]); " + "redis.call('publish', KEYS[2], ARGV[1]); " + "return 1;", Arrays.<Object>asList(getName(), getChannelName()), encode(new UnlockMessage(getName(), getLockName())), id); cancelExpirationRenewal(); if (ttl == null) { throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + id + " thread-id: " + Thread.currentThread().getId()); } if (ttl == -1) { get(lockName).deleteAsync(); } } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值