HttpAsyncClient#excuete执行分析
三个线程
我们知道HttpAsyncClient是异步非阻塞的,这意味着其中并然存在执行任务的异步线程,这这其中就包括连接线程与工作线程
- 用户线程:顾名思义,就是发起execute()的线程。
- 连接线程:主要负责http连接的建立与被建立。
- 用户线程:主要负责实际的请求的发送与响应的接收。
连接线程的创建
它实际在HttpAsyncClient对象创建时被创建,具体在HttpAsyncClientBuilder#build
调用 new InternalHttpAsyncClient
在其父类 CloseableHttpAsyncClientBase
构造函数中创建了 reactorThread
实例,它的实际工作者是DefaultConnectingIOReactor
对象,后者且在调用PoolingNHttpClientConnectionManager#start
方法后就开了主循环。
工作线程创建
工作线程实际就由连接线程创建,在AbstractMultiworkerIOReactor#execute
方法将创建若干个工作线程,后者的实际工作对象是Worker中的AbstractIOReactor
对象。
基本流程图
发起请求
从用户调用excute()方法开始,到请求数据被发送出去,主要包含两大步骤
- 获取连接
- 数据发送
获取连接
从一路从excute
跟进到AbstractNIOConnPool#processPendingRequest
方法,其中主要
的做的事有两件,一是根据route和state尝试寻找可复用的连接,这里的可复用得益于HTTP的keepAlive机制可以一定时间内不用每次请求都重新连接。如果没有可复用的连接将在条件允许的情况下创建新的连接,这里条件主要有针对单个路由的连接数setMaxConnPerRoute()
和总的连接数setMaxConnTotal()
,同样这也是我们构建一个HttpAsyncClient对象需要重点关注的配置内容(不配置很有可能在线上环境造成连接不够用的情况)。
// 尝试寻找可复用的连接
final RouteSpecificPool<T, C, E> pool = getPool(route);
E entry;
for (;;) {
entry = pool.getFree(state);
if (entry == null) {
break;
}
if (entry.isClosed() || entry.isExpired(System.currentTimeMillis())) {
entry.close();
this.available.remove(entry);
pool.free(entry, false);
} else {
break;
}
}
.....
// 条件允许建立新的连接
final SessionRequest sessionRequest = this.ioReactor.connect(
remoteAddress, localAddress, route, this.sessionRequestCallback);
创建新的连接
如果连接可复用那实际上就没有连接线程什么事,否则就将调用IOReactor#connect
来创建新的连接,这里需要注意的是连接并不意味着在这里马上建立,而是通知连接线程来完成,具体体现在这两行代码
// 添加到请求队列
this.requestQueue.add(sessionRequest);
// 唤醒连接线程
this.selector.wakeup();
前面我们提到连接线程的时说到在PoolingNHttpClientConnectionManager#start
方法后连接线程就开了主循环execute
,在主循环中,它阻塞在this.selector.select(this.selectTimeout)
,而在用户线程中唤醒了它,而后开始在processEvents
方法中迭代requestQueue,在其中将发起实际连接的建立与连接事件的注册。(这里的使用是JavaNIO模型)
// 实际连接的建立
connected = AccessController.doPrivileged(
new PrivilegedExceptionAction<Boolean>() {
@Override
public Boolean run() throws IOException {
return socketChannel.connect(targetAddress);
};
});
.....
// 连接事件的注册
final SelectionKey key = socketChannel.register(this.selector, SelectionKey.OP_CONNECT,
requestHandle);
而后在processEvent
中将把后续的工作分发给工作线程并唤醒工作线程
protected void addChannel(final ChannelEntry entry) {
// Distribute new channels among the workers
final int i = Math.abs(this.currentWorker++ % this.workerCount);
this.dispatchers[i].addChannel(entry);
....
// addChannel的实现
Args.notNull(channelEntry, "Channel entry");
this.newChannels.add(channelEntry);
this.selector.wakeup();
}
数据发送
worker线程被唤醒后将迭代newChannels队列注册可读事件,触发回调
// 注册可读事件
key = channel.register(this.selector, SelectionKey.OP_READ);
.....
// 触发回调
if (!sessionRequest.isTerminated()) {
sessionRequest.completed(session);
}
回调的第一步回调到AbstractNIOConnPool#requestCompleted
在这里加入到连接池的已租赁连接中并且触发下一步回调到
PoolingNHttpClientConnectionManager#requestConnection
中的completed
,进而最终回调到AbstractClientExchangeHandler#requestConnection
的completed
触发AbstractClientExchangeHandler#connectionAllocated
,它将进一步的调用NHttpConnectionBase#requestOutput
方法发布可写事件,事件发布后将回到worker线程的主循环当中, 触发写事件 writable(key)
将传数据写由buffer写入到channel中,至此完成数据的发送
protected void processEvent(final SelectionKey key) {
final IOSessionImpl session = (IOSessionImpl) key.attachment();
try {
.....
// 接收到响应,可以读数据
if (key.isReadable()) {
session.resetLastRead();
readable(key);
}
// 可以请发请求,写入数据
if (key.isWritable()) {
session.resetLastWrite();
writable(key);
}
} catch (final CancelledKeyException ex) {
queueClosedSession(session);
key.attach(null);
}
}
数据的写入
从writable(key)
一步步跟踪到DefaultNHttpClientConnection#produceOutput
,数据实际就是由这里从buffer写入channel的
if (this.outbuf.hasData()) {
// 由buffer写入channel
final int bytesWritten = this.outbuf.flush(this.session.channel());
if (bytesWritten > 0) {
this.outTransportMetrics.incrementBytesTransferred(bytesWritten);
}
}
debug模式下我们可以拿到outbuf
对象中的buff
属性其中的hb
字节数组就是真实的数据
比如以下
[71, 69, 84, 32, 47, 116, 101, 115, 116, 47, 116, 101, 115, 116, 50, 32, 72, 84, 84, 80, 47, 49, 46, 49, 13, 10, 72,
111, 115, 116, 58, 32, 108, 111, 99, 97, 108, 104, 111, 115, 116, 58, 56, 48, 56, 48, 13, 10, 67, 111, 110, 110,
101, 99, 116, 105, 111, 110, 58, 32, 75, 101, 101, 112, 45, 65, 108, 105, 118, 101, 13, 10, 85, 115, 101, 114, 45,
65, 103, 101, 110, 116, 58, 32, 65, 112, 97, 99, 104, 101, 45, 72, 116, 116, 112, 65, 115, 121, 110, 99, 67, 108,
105, 101, 110, 116, 47, 52, 46, 49, 46, 52, 32, 40, 74, 97, 118, 97, 47, 49, 46, 56, 46, 48, 95, 50, 48, 50, 41, 13,
10, 13, 10....]
转化为String格式就是一个标准的HTTP请求报文
GET /test/test2 HTTP/1.1
Host: localhost:8080
Connection: Keep-Alive
User-Agent: Apache-HttpAsyncClient/4.1.4 (Java/1.8.0_202)