问题描述:
接到相应反馈后,我马上去追踪问题点。首先是定位到通知商户的类是 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 秒,所以就报这个错了。
解决办法:
将连接池的连接数调大,同时适当调大连接池连接时间,即可解决问题。生产环境最后也是这么操作后,问题得以解决。