<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Http 持久連接與 HttpClient 連接池

          共 47398字,需瀏覽 95分鐘

           ·

          2021-07-07 20:21

          你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

          成功路上并不擁擠,因?yàn)閳?jiān)持的人不多。

          編輯:業(yè)余草

          cnblogs.com/kingszelda/p/8988505.html

          推薦:https://www.xttblog.com/?p=5231

          背景

          HTTP 協(xié)議是無狀態(tài)的協(xié)議,即每一次請(qǐng)求都是互相獨(dú)立的。因此它的最初實(shí)現(xiàn)是,每一個(gè) http 請(qǐng)求都會(huì)打開一個(gè) tcp socket 連接,當(dāng)交互完畢后會(huì)關(guān)閉這個(gè)連接。

          HTTP 協(xié)議是全雙工的協(xié)議,所以建立連接與斷開連接是要經(jīng)過三次握手與四次揮手的。顯然在這種設(shè)計(jì)中,每次發(fā)送 Http 請(qǐng)求都會(huì)消耗很多的額外資源,即連接的建立與銷毀。

          于是,HTTP 協(xié)議的也進(jìn)行了發(fā)展,通過持久連接的方法來進(jìn)行 socket 連接復(fù)用。

          從圖中可以看到:

          1. 在串行連接中,每次交互都要打開關(guān)閉連接

          2. 在持久連接中,第一次交互會(huì)打開連接,交互結(jié)束后連接并不關(guān)閉,下次交互就省去了建立連接的過程。

          持久連接的實(shí)現(xiàn)有兩種:HTTP/1.0+ 的 keep-alive 與 HTTP/1.1 的持久連接。

          HTTP/1.0+ 的 Keep-Alive

          從 1996 年開始,很多 HTTP/1.0瀏覽器與服務(wù)器都對(duì)協(xié)議進(jìn)行了擴(kuò)展,那就是“keep-alive”擴(kuò)展協(xié)議。

          注意,這個(gè)擴(kuò)展協(xié)議是作為 1.0 的補(bǔ)充的“實(shí)驗(yàn)型持久連接”出現(xiàn)的。keep-alive 已經(jīng)不再使用了,最新的 HTTP/1.1 規(guī)范中也沒有對(duì)它進(jìn)行說明,只是很多應(yīng)用延續(xù)了下來。

          使用 HTTP/1.0 的客戶端在首部中加上"Connection:Keep-Alive",請(qǐng)求服務(wù)端將一條連接保持在打開狀態(tài)。服務(wù)端如果愿意將這條連接保持在打開狀態(tài),就會(huì)在響應(yīng)中包含同樣的首部。如果響應(yīng)中沒有包含"Connection:Keep-Alive"首部,則客戶端會(huì)認(rèn)為服務(wù)端不支持 keep-alive,會(huì)在發(fā)送完響應(yīng)報(bào)文之后關(guān)閉掉當(dāng)前連接。

          通過 keep-alive 補(bǔ)充協(xié)議,客戶端與服務(wù)器之間完成了持久連接,然而仍然存在著一些問題:

          • 在 HTTP/1.0 中 keep-alive 不是標(biāo)準(zhǔn)協(xié)議,客戶端必須發(fā)送Connection:Keep-Alive 來激活 keep-alive 連接。

          • 代理服務(wù)器可能無法支持 keep-alive,因?yàn)橐恍┐硎?盲中繼",無法理解首部的含義,只是將首部逐跳轉(zhuǎn)發(fā)。所以可能造成客戶端與服務(wù)端都保持了連接,但是代理不接受該連接上的數(shù)據(jù)。

          HTTP/1.1 的持久連接

          HTTP/1.1 采取持久連接的方式替代了 Keep-Alive。

          HTTP/1.1 的連接默認(rèn)情況下都是持久連接。如果要顯式關(guān)閉,需要在報(bào)文中加上Connection:Close 首部。即在 HTTP/1.1 中,所有的連接都進(jìn)行了復(fù)用。

          然而如同 Keep-Alive 一樣,空閑的持久連接也可以隨時(shí)被客戶端與服務(wù)端關(guān)閉。不發(fā)送 Connection:Close 不意味著服務(wù)器承諾連接永遠(yuǎn)保持打開。

          HttpClient 如何生成持久連接

          HttpClient 中使用了連接池來管理持有連接,同一條 TCP 鏈路上,連接是可以復(fù)用的。HttpClient 通過連接池的方式進(jìn)行連接持久化。

          其實(shí)“池”技術(shù)是一種通用的設(shè)計(jì),其設(shè)計(jì)思想并不復(fù)雜:

          1. 當(dāng)有連接第一次使用的時(shí)候建立連接

          2. 結(jié)束時(shí)對(duì)應(yīng)連接不關(guān)閉,歸還到池中

          3. 下次同個(gè)目的的連接可從池中獲取一個(gè)可用連接

          4. 定期清理過期連接

          所有的連接池都是這個(gè)思路,不過我們看 HttpClient 源碼主要關(guān)注兩點(diǎn):

          • 連接池的具體設(shè)計(jì)方案,以供以后自定義連接池參考

          • 如何與 HTTP 協(xié)議對(duì)應(yīng)上,即理論抽象轉(zhuǎn)為代碼的實(shí)現(xiàn)

          HttpClient 連接池的實(shí)現(xiàn)

          HttpClient 關(guān)于持久連接的處理在下面的代碼中可以集中體現(xiàn),下面從 MainClientExec 摘取了和連接池相關(guān)的部分,去掉了其他部分:

          public class MainClientExec implements ClientExecChain {

              @Override
              public CloseableHttpResponse execute(
                      final HttpRoute route,
                      final HttpRequestWrapper request,
                      final HttpClientContext context,
                      final HttpExecutionAware execAware)
           throws IOException, HttpException 
          {
               //從連接管理器HttpClientConnectionManager中獲取一個(gè)連接請(qǐng)求ConnectionRequest
                  final ConnectionRequest connRequest = connManager.requestConnection(route, userToken);final HttpClientConnection managedConn;
                  final int timeout = config.getConnectionRequestTimeout();
                  //從連接請(qǐng)求ConnectionRequest中獲取一個(gè)被管理的連接HttpClientConnection
                  managedConn = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS);
               //將連接管理器HttpClientConnectionManager與被管理的連接HttpClientConnection交給一個(gè)ConnectionHolder持有
                  final ConnectionHolder connHolder = new ConnectionHolder(this.log, this.connManager, managedConn);
                  try {
                      HttpResponse response;
                      if (!managedConn.isOpen()) {
                    //如果當(dāng)前被管理的連接不是出于打開狀態(tài),需要重新建立連接
                          establishRoute(proxyAuthState, managedConn, route, request, context);
                      }
                 //通過連接HttpClientConnection發(fā)送請(qǐng)求
                      response = requestExecutor.execute(request, managedConn, context);
                 //通過連接重用策略判斷是否連接可重用 
                      if (reuseStrategy.keepAlive(response, context)) {
                          //獲得連接有效期
                          final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
                          //設(shè)置連接有效期
                          connHolder.setValidFor(duration, TimeUnit.MILLISECONDS);
                    //將當(dāng)前連接標(biāo)記為可重用狀態(tài)
                          connHolder.markReusable();
                      } else {
                          connHolder.markNonReusable();
                      }
                  }
                  final HttpEntity entity = response.getEntity();
                  if (entity == null || !entity.isStreaming()) {
                      //將當(dāng)前連接釋放到池中,供下次調(diào)用
                      connHolder.releaseConnection();
                      return new HttpResponseProxy(response, null);
                  } else {
                      return new HttpResponseProxy(response, connHolder);
                  }
          }

          這里看到了在 Http 請(qǐng)求過程中對(duì)連接的處理是和協(xié)議規(guī)范是一致的,這里要展開講一下具體實(shí)現(xiàn)。

          PoolingHttpClientConnectionManager 是 HttpClient 默認(rèn)的連接管理器,首先通過 requestConnection() 獲得一個(gè)連接的請(qǐng)求,注意這里不是連接。

          public ConnectionRequest requestConnection(
                      final HttpRoute route,
                      final Object state)
           
          {final Future<CPoolEntry> future = this.pool.lease(route, state, null);
                  return new ConnectionRequest() {
                      @Override
                      public boolean cancel() {
                          return future.cancel(true);
                      }
                      @Override
                      public HttpClientConnection get(
                              final long timeout,
                              final TimeUnit tunit)
           throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException 
          {
                          final HttpClientConnection conn = leaseConnection(future, timeout, tunit);
                          if (conn.isOpen()) {
                              final HttpHost host;
                              if (route.getProxyHost() != null) {
                                  host = route.getProxyHost();
                              } else {
                                  host = route.getTargetHost();
                              }
                              final SocketConfig socketConfig = resolveSocketConfig(host);
                              conn.setSocketTimeout(socketConfig.getSoTimeout());
                          }
                          return conn;
                      }
                  };
              }

          可以看到返回的 ConnectionRequest 對(duì)象實(shí)際上是一個(gè)持有了 Future,CPoolEntry 是被連接池管理的真正連接實(shí)例。

          從上面的代碼我們應(yīng)該關(guān)注的是:

          • Futurefuture = this.pool.lease(route, state, null)如何從連接池 CPool 中獲得一個(gè)異步的連接Future<CPoolEntry>

          • HttpClientConnection conn = leaseConnection(future, timeout, tunit)如何通過異步連接 Future 獲得一個(gè)真正的連接 HttpClientConnection

          Future

          看一下 CPool 是如何釋放一個(gè) Future 的,AbstractConnPool 核心代碼如下:

          private E getPoolEntryBlocking(
                      final T route, final Object state,
                      final long timeout, final TimeUnit tunit,
                      final Future<E> future)
           throws IOException, InterruptedException, TimeoutException 
          {
               //首先對(duì)當(dāng)前連接池加鎖,當(dāng)前鎖是可重入鎖ReentrantLockthis.lock.lock();
                  try {
                  //獲得一個(gè)當(dāng)前HttpRoute對(duì)應(yīng)的連接池,對(duì)于HttpClient的連接池而言,總池有個(gè)大小,每個(gè)route對(duì)應(yīng)的連接也是個(gè)池,所以是“池中池”
                      final RouteSpecificPool<T, C, E> pool = getPool(route);
                      E entry;
                      for (;;) {
                          Asserts.check(!this.isShutDown, "Connection pool shut down");
                    //死循環(huán)獲得連接
                          for (;;) {
                      //從route對(duì)應(yīng)的池中拿連接,可能是null,也可能是有效連接
                              entry = pool.getFree(state);
                      //如果拿到null,就退出循環(huán)
                              if (entry == null) {
                                  break;
                              }
                      //如果拿到過期連接或者已關(guān)閉連接,就釋放資源,繼續(xù)循環(huán)獲取
                              if (entry.isExpired(System.currentTimeMillis())) {
                                  entry.close();
                              }
                              if (entry.isClosed()) {
                                  this.available.remove(entry);
                                  pool.free(entry, false);
                              } else {
                        //如果拿到有效連接就退出循環(huán)
                                  break;
                              }
                          }
                    //拿到有效連接就退出
                          if (entry != null) {
                              this.available.remove(entry);
                              this.leased.add(entry);
                              onReuse(entry);
                              return entry;
                          }
                    //到這里證明沒有拿到有效連接,需要自己生成一個(gè) 
                          final int maxPerRoute = getMax(route);
                          //每個(gè)route對(duì)應(yīng)的連接最大數(shù)量是可配置的,如果超過了,就需要通過LRU清理掉一些連接
                          final int excess = Math.max(0, pool.getAllocatedCount() + 1 - maxPerRoute);
                          if (excess > 0) {
                              for (int i = 0; i < excess; i++) {
                                  final E lastUsed = pool.getLastUsed();
                                  if (lastUsed == null) {
                                      break;
                                  }
                                  lastUsed.close();
                                  this.available.remove(lastUsed);
                                  pool.remove(lastUsed);
                              }
                          }
                    //當(dāng)前route池中的連接數(shù),沒有達(dá)到上線
                          if (pool.getAllocatedCount() < maxPerRoute) {
                              final int totalUsed = this.leased.size();
                              final int freeCapacity = Math.max(this.maxTotal - totalUsed, 0);
                      //判斷連接池是否超過上線,如果超過了,需要通過LRU清理掉一些連接
                              if (freeCapacity > 0) {
                                  final int totalAvailable = this.available.size();
                         //如果空閑連接數(shù)已經(jīng)大于剩余可用空間,則需要清理下空閑連接
                                  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);
                                      }
                                  }
                        //根據(jù)route建立一個(gè)連接
                                  final C conn = this.connFactory.create(route);
                        //將這個(gè)連接放入route對(duì)應(yīng)的“小池”中
                                  entry = pool.add(conn);
                        //將這個(gè)連接放入“大池”中
                                  this.leased.add(entry);
                                  return entry;
                              }
                          }
                   //到這里證明沒有從獲得route池中獲得有效連接,并且想要自己建立連接時(shí)當(dāng)前route連接池已經(jīng)到達(dá)最大值,即已經(jīng)有連接在使用,但是對(duì)當(dāng)前線程不可用
                          boolean success = false;
                          try {
                              if (future.isCancelled()) {
                                  throw new InterruptedException("Operation interrupted");
                              }
                      //將future放入route池中等待
                              pool.queue(future);
                      //將future放入大連接池中等待
                              this.pending.add(future);
                      //如果等待到了信號(hào)量的通知,success為true
                              if (deadline != null) {
                                  success = this.condition.awaitUntil(deadline);
                              } else {
                                  this.condition.await();
                                  success = true;
                              }
                              if (future.isCancelled()) {
                                  throw new InterruptedException("Operation interrupted");
                              }
                          } finally {
                              //從等待隊(duì)列中移除
                              pool.unqueue(future);
                              this.pending.remove(future);
                          }
                          //如果沒有等到信號(hào)量通知并且當(dāng)前時(shí)間已經(jīng)超時(shí),則退出循環(huán)
                          if (!success && (deadline != null && deadline.getTime() <= System.currentTimeMillis())) {
                              break;
                          }
                      }
                 //最終也沒有等到信號(hào)量通知,沒有拿到可用連接,則拋異常
                      throw new TimeoutException("Timeout waiting for connection");
                  } finally {
                 //釋放對(duì)大連接池的鎖
                      this.lock.unlock();
                  }
              }

          上面的代碼邏輯有幾個(gè)重要點(diǎn):

          • 連接池有個(gè)最大連接數(shù),每個(gè) route 對(duì)應(yīng)一個(gè)小連接池,也有個(gè)最大連接數(shù)
          • 不論是大連接池還是小連接池,當(dāng)超過數(shù)量的時(shí)候,都要通過 LRU 釋放一些連接
          • 如果拿到了可用連接,則返回給上層使用
          • 如果沒有拿到可用連接,HttpClient 會(huì)判斷當(dāng)前 route 連接池是否已經(jīng)超過了最大數(shù)量,沒有到上限就會(huì)新建一個(gè)連接,并放入池中
          • 如果到達(dá)了上限,就排隊(duì)等待,等到了信號(hào)量,就重新獲得一次,等待不到就拋超時(shí)異常
          • 通過線程池獲取連接要通過 ReetrantLock 加鎖,保證線程安全

          到這里為止,程序已經(jīng)拿到了一個(gè)可用的 CPoolEntry 實(shí)例,或者拋異常終止了程序。

          HttpClientConnection

          protected HttpClientConnection leaseConnection(
                      final Future<CPoolEntry> future,
                      final long timeout,
                      final TimeUnit tunit)
           throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException 
          {
                  final CPoolEntry entry;
                  try {
                 //從異步操作Future<CPoolEntry>中獲得CPoolEntry
                      entry = future.get(timeout, tunit);
                      if (entry == null || future.isCancelled()) {
                          throw new InterruptedException();
                      }
                      Asserts.check(entry.getConnection() != null"Pool entry with no connection");
                      if (this.log.isDebugEnabled()) {
                          this.log.debug("Connection leased: " + format(entry) + formatStats(entry.getRoute()));
                      }
                 //獲得一個(gè)CPoolEntry的代理對(duì)象,對(duì)其操作都是使用同一個(gè)底層的HttpClientConnection
                      return CPoolProxy.newProxy(entry);
                  } catch (final TimeoutException ex) {
                      throw new ConnectionPoolTimeoutException("Timeout waiting for connection from pool");
                  }
              }

          HttpClient 如何復(fù)用持久連接?

          在上一章中,我們看到了 HttpClient 通過連接池來獲得連接,當(dāng)需要使用連接的時(shí)候從池中獲得。

          對(duì)應(yīng)著第三章的問題:

          1. 當(dāng)有連接第一次使用的時(shí)候建立連接
          2. 結(jié)束時(shí)對(duì)應(yīng)連接不關(guān)閉,歸還到池中
          3. 下次同個(gè)目的的連接可從池中獲取一個(gè)可用連接
          4. 定期清理過期連接

          我們?cè)诘谒恼轮锌吹搅?HttpClient 是如何處理 1、3 的問題的,那么第 2 個(gè)問題是怎么處理的呢?

          即 HttpClient 如何判斷一個(gè)連接在使用完畢后是要關(guān)閉,還是要放入池中供他人復(fù)用?再看一下 MainClientExec 的代碼

          //發(fā)送Http連接
          response = requestExecutor.execute(request, managedConn, context);
          //根據(jù)重用策略判斷當(dāng)前連接是否要復(fù)用
          if (reuseStrategy.keepAlive(response, context)) {
              //需要復(fù)用的連接,獲取連接超時(shí)時(shí)間,以response中的timeout為準(zhǔn)
              final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
              if (this.log.isDebugEnabled()) {
                  final String s;
                         //timeout的是毫秒數(shù),如果沒有設(shè)置則為-1,即沒有超時(shí)時(shí)間
                  if (duration > 0) {
                      s = "for " + duration + " " + TimeUnit.MILLISECONDS;
                  } else {
                      s = "indefinitely";
                  }
                  this.log.debug("Connection can be kept alive " + s);
              }
                      //設(shè)置超時(shí)時(shí)間,當(dāng)請(qǐng)求結(jié)束時(shí)連接管理器會(huì)根據(jù)超時(shí)時(shí)間決定是關(guān)閉還是放回到池中
              connHolder.setValidFor(duration, TimeUnit.MILLISECONDS);
              //將連接標(biāo)記為可重用
                      connHolder.markReusable();
          else {
                      //將連接標(biāo)記為不可重用
              connHolder.markNonReusable();
          }

          可以看到,當(dāng)使用連接發(fā)生過請(qǐng)求之后,有連接重試策略來決定該連接是否要重用,如果要重用就會(huì)在結(jié)束后交給 HttpClientConnectionManager 放入池中。

          那么連接復(fù)用策略的邏輯是怎么樣的呢?

          public class DefaultClientConnectionReuseStrategy extends DefaultConnectionReuseStrategy {

              public static final DefaultClientConnectionReuseStrategy INSTANCE = new DefaultClientConnectionReuseStrategy();

              @Override
              public boolean keepAlive(final HttpResponse response, final HttpContext context) {
               //從上下文中拿到request
                  final HttpRequest request = (HttpRequest) context.getAttribute(HttpCoreContext.HTTP_REQUEST);
                  if (request != null) {
                 //獲得Connection的Header
                      final Header[] connHeaders = request.getHeaders(HttpHeaders.CONNECTION);
                      if (connHeaders.length != 0) {
                          final TokenIterator ti = new BasicTokenIterator(new BasicHeaderIterator(connHeaders, null));
                          while (ti.hasNext()) {
                              final String token = ti.nextToken();
                      //如果包含Connection:Close首部,則代表請(qǐng)求不打算保持連接,會(huì)忽略response的意愿,該頭部這是HTTP/1.1的規(guī)范
                              if (HTTP.CONN_CLOSE.equalsIgnoreCase(token)) {
                                  return false;
                              }
                          }
                      }
                  }
               //使用父類的的復(fù)用策略
                  return super.keepAlive(response, context);
              }

          }

          看一下父類的復(fù)用策略

          if (canResponseHaveBody(request, response)) {
                          final Header[] clhs = response.getHeaders(HTTP.CONTENT_LEN);
                          //如果reponse的Content-Length沒有正確設(shè)置,則不復(fù)用連接
                    //因?yàn)閷?duì)于持久化連接,兩次傳輸之間不需要重新建立連接,則需要根據(jù)Content-Length確認(rèn)內(nèi)容屬于哪次請(qǐng)求,以正確處理“粘包”現(xiàn)象
                          //所以,沒有正確設(shè)置Content-Length的response連接不能復(fù)用
                          if (clhs.length == 1) {
                              final Header clh = clhs[0];
                              try {
                                  final int contentLen = Integer.parseInt(clh.getValue());
                                  if (contentLen < 0) {
                                      return false;
                                  }
                              } catch (final NumberFormatException ex) {
                                  return false;
                              }
                          } else {
                              return false;
                          }
                      }
                  if (headerIterator.hasNext()) {
                      try {
                          final TokenIterator ti = new BasicTokenIterator(headerIterator);
                          boolean keepalive = false;
                          while (ti.hasNext()) {
                              final String token = ti.nextToken();
                      //如果response有Connection:Close首部,則明確表示要關(guān)閉,則不復(fù)用
                              if (HTTP.CONN_CLOSE.equalsIgnoreCase(token)) {
                                  return false;
                      //如果response有Connection:Keep-Alive首部,則明確表示要持久化,則復(fù)用
                              } else if (HTTP.CONN_KEEP_ALIVE.equalsIgnoreCase(token)) {
                                  keepalive = true;
                              }
                          }
                          if (keepalive) {
                              return true;
                          }
                      } catch (final ParseException px) {
                          return false;
                      }
                  }
               //如果response中沒有相關(guān)的Connection首部說明,則高于HTTP/1.0版本的都復(fù)用連接  
                  return !ver.lessEquals(HttpVersion.HTTP_1_0);

          總結(jié)一下:

          • 如果 request 首部中包含 Connection:Close,不復(fù)用
          • 如果 response 中 Content-Length 長(zhǎng)度設(shè)置不正確,不復(fù)用
          • 如果 response 首部包含 Connection:Close,不復(fù)用
          • 如果 reponse 首部包含 Connection:Keep-Alive,復(fù)用
          • 都沒命中的情況下,如果 HTTP 版本高于 1.0 則復(fù)用

          從代碼中可以看到,其實(shí)現(xiàn)策略與我們第二、三章協(xié)議層的約束是一致的。

          HttpClient 如何清理過期連接

          在 HttpClient4.4 版本之前,在從連接池中獲取重用連接的時(shí)候會(huì)檢查下是否過期,過期則清理。

          之后的版本則不同,會(huì)有一個(gè)單獨(dú)的線程來掃描連接池中的連接,發(fā)現(xiàn)有離最近一次使用超過設(shè)置的時(shí)間后,就會(huì)清理。默認(rèn)的超時(shí)時(shí)間是 2 秒鐘。

          public CloseableHttpClient build() {
                      //如果指定了要清理過期連接與空閑連接,才會(huì)啟動(dòng)清理線程,默認(rèn)是不啟動(dòng)的
                      if (evictExpiredConnections || evictIdleConnections) {
                    //創(chuàng)造一個(gè)連接池的清理線程
                          final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(cm,
                                  maxIdleTime > 0 ? maxIdleTime : 10, maxIdleTimeUnit != null ? maxIdleTimeUnit : TimeUnit.SECONDS,
                                  maxIdleTime, maxIdleTimeUnit);
                          closeablesCopy.add(new Closeable() {
                              @Override
                              public void close() throws IOException {
                                  connectionEvictor.shutdown();
                                  try {
                                      connectionEvictor.awaitTermination(1L, TimeUnit.SECONDS);
                                  } catch (final InterruptedException interrupted) {
                                      Thread.currentThread().interrupt();
                                  }
                              }

                          });
                    //執(zhí)行該清理線程
                          connectionEvictor.start();
          }

          可以看到在 HttpClientBuilder 進(jìn)行 build 的時(shí)候,如果指定了開啟清理功能,會(huì)創(chuàng)建一個(gè)連接池清理線程并運(yùn)行它。

          public IdleConnectionEvictor(
                      final HttpClientConnectionManager connectionManager,
                      final ThreadFactory threadFactory,
                      final long sleepTime, final TimeUnit sleepTimeUnit,
                      final long maxIdleTime, final TimeUnit maxIdleTimeUnit)
           
          {
                  this.connectionManager = Args.notNull(connectionManager, "Connection manager");
                  this.threadFactory = threadFactory != null ? threadFactory : new DefaultThreadFactory();
                  this.sleepTimeMs = sleepTimeUnit != null ? sleepTimeUnit.toMillis(sleepTime) : sleepTime;
                  this.maxIdleTimeMs = maxIdleTimeUnit != null ? maxIdleTimeUnit.toMillis(maxIdleTime) : maxIdleTime;
                  this.thread = this.threadFactory.newThread(new Runnable() {
                      @Override
                      public void run() {
                          try {
                      //死循環(huán),線程一直執(zhí)行
                              while (!Thread.currentThread().isInterrupted()) {
                        //休息若干秒后執(zhí)行,默認(rèn)10秒
                                  Thread.sleep(sleepTimeMs);
                         //清理過期連接
                                  connectionManager.closeExpiredConnections();
                         //如果指定了最大空閑時(shí)間,則清理空閑連接
                                  if (maxIdleTimeMs > 0) {
                                      connectionManager.closeIdleConnections(maxIdleTimeMs, TimeUnit.MILLISECONDS);
                                  }
                              }
                          } catch (final Exception ex) {
                              exception = ex;
                          }

                      }
                  });
              }

          總結(jié)一下:

          • 只有在 HttpClientBuilder 手動(dòng)設(shè)置后,才會(huì)開啟清理過期與空閑連接
          • 手動(dòng)設(shè)置后,會(huì)啟動(dòng)一個(gè)線程死循環(huán)執(zhí)行,每次執(zhí)行 sleep 一定時(shí)間,調(diào)用 HttpClientConnectionManager 的清理方法清理過期與空閑連接。

          本文總結(jié)

          • HTTP 協(xié)議通過持久連接的方式,減輕了早期設(shè)計(jì)中的過多連接問題
          • 持久連接有兩種方式:HTTP/1.0+ 的 Keep-Avlive 與 HTTP/1.1 的默認(rèn)持久連接
          • HttpClient 通過連接池來管理持久連接,連接池分為兩個(gè),一個(gè)是總連接池,一個(gè)是每個(gè) route 對(duì)應(yīng)的連接池
          • HttpClient 通過異步的 Future<CPoolEntry> 來獲取一個(gè)池化的連接
          • 默認(rèn)連接重用策略與 HTTP 協(xié)議約束一致,根據(jù) response 先判斷Connection:Close 則關(guān)閉,在判斷 Connection:Keep-Alive 則開啟,最后版本大于 1.0 則開啟
          • 只有在 HttpClientBuilder 中手動(dòng)開啟了清理過期與空閑連接的開關(guān)后,才會(huì)清理連接池中的連接
          • HttpClient4.4 之后的版本通過一個(gè)死循環(huán)線程清理過期與空閑連接,該線程每次執(zhí)行都 sleep 一會(huì),以達(dá)到定期執(zhí)行的效果

          上面的研究是基于 HttpClient 源碼的個(gè)人理解,如果有誤,希望大家積極留言討論。

          瀏覽 76
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产swag在线观看 | 内射网站在线观看在线观看 | sm捆绑网址 | 91在线精品无码秘 入口APP | 国产骚屌扛腿操 |