HttpAsyncClient#excuete执行分析

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#requestConnectioncompleted触发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)
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值