<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      zuul 網關超時優化

      1. 概述

      前段時間,線上的服務不知道為啥,突然全部的服務都超時,所有的請求經過網關都超時,后來進行鏈路追蹤排查,發現有一個服務鏈接 RDS 數據庫,一個查詢花費了 20S 的查詢時間,導致后續調用該服務的應用都超時。然后超時的連接占滿了 zuul 的轉發池,最終導致了所有經過 gateway 的服務都在等待,導致全體服務全部超時。

      原因找出來之后,我就在納悶,為何一個服務超時會導致所有服務都延時,作為一個高可用的網關, zuul 的設計應該不會這么差吧,并且,當時系統的 QPS 也不是很高,所以,必須找出這次超時的問題所在,于是,我就開始了此次網關超時的排查和優化!

      下圖是出現問題是的相關監控

      1715158142695.png

      因為公司的服務是 java 和 node 應用都存在,所以使用的 zuul 是簡單路由轉發,未使用 服務注冊中心之類的,所以,熔斷器和

      2. 問題分析

      首先,因為網關的相關配置都是之前填的,有些參數不是特別的清楚,下面的配置文件是我當時系統的相關配置。

      zuul:
        host:
          connect-timeout-millis: 30000
          socket-timeout-millis: 60000
      
      ribbon:
        ConnectTimeout: 300000
        ReadTimeout: 60000
        eureka:
          enabled: false
      
      hystrix:
        command:
          default:
            execution:
              isolation:
                thread:
                  timeoutInMilliseconds: 70000
      

      2.1 zuul 參數解釋

      connect-timeout-millis

      此參數為 zuul 網關連接服務的時間,單位為毫秒,我這里配置的是 30S,如果 30S 之內未連接到待轉發的下一服務,則轉發將報錯,此請求也就將結束。這段時間為下圖的第 1 步的時間

      如下圖所示:

      1715158174139.png

      socket-timeout-millis

      zuul 網關連接到服務并且服務返回結果這一部分時間,單位為毫秒,我這里配置的是 60S,如果 60S 之內下一服務還沒有返回, gateway 將報轉發超時。

      此時間為上圖的 1+2+3 三部分時間

      ribbon.ConnectTimeout

      此為 ribbon 轉發的連接時間,如果 zuul 使用的服務調用,則將采用此時間

      ribbon.ReadTimeout

      此為 ribbon 轉發到返回的時間,如果 zuul 使用的是服務調用,則將采用此時間

      超時時間采用哪個?

      以上四個都是 zuul 的超時時間,但是問題來了,四個時間,到底采用哪兩個呢?

      我通過閱讀 zuul 的相關文檔,了解到,如果 zuul 使用的服務發現,則將會使用 rebbon進行負載均衡,即 ribbon.ConnectTimeoutribbon.ReadTimeout。 如果 zuul 使用的是簡單路由(通過配置 url 進行路由轉發),則將采用 socket-timeout-millisconnect-timeout-millis

      If you want to configure the socket timeouts and read timeouts for requests proxied through Zuul, you have two options, based on your configuration:

      • If Zuul uses service discovery, you need to configure these timeouts with the ribbon.ReadTimeout and ribbon.SocketTimeout Ribbon properties.
      • If you have configured Zuul routes by specifying URLs, you need to use zuul.host.connect-timeout-millis and zuul.host.socket-timeout-millis.

      2.2 問題分析

      因為我的服務未使用服務注冊中心,所以,很明顯, 配置的 ribbon 并未生效,zuul 超時時間使用的 socket-timeout-millisconnect-timeout-millis

      但是問題又來了,我 zuul 的超時時間為 60S,為何我的服務相應時間平均達到了 3 分鐘,遠遠超過了我設置的 60S,所以肯定是 zuul 還出現了相應的問題。

      后續通過了解到 zuul 源碼和架構,明白 zuul 是 NIO 架構,即一個請求進來,經過 zuul 攔截器 Filter,最終交給 HttpClient 進行請求,zuul 為了高性能,使用 HttpClient 連接池。

      獲取連接源碼

      下面是 zuul 獲取 HttpClient 連接池的代碼

      AbstractConnPool.getPoolEntryBlocking,看這個名字就知道。這是一個阻塞獲取池資源的方法

          private E getPoolEntryBlocking(
                  final T route, final Object state,
                  final long timeout, final TimeUnit timeUnit,
                  final Future<E> future) throws IOException, InterruptedException, ExecutionException, TimeoutException {
      
              Date deadline = null;
              if (timeout > 0) {
                  deadline = new Date (System.currentTimeMillis() + timeUnit.toMillis(timeout));
              }
              this.lock.lock();
              try {
                  final RouteSpecificPool<T, C, E> pool = getPool(route);
                  E entry;
                  for (;;) {
                      Asserts.check(!this.isShutDown, "Connection pool shut down");
                      if (future.isCancelled()) {
                          throw new ExecutionException(operationAborted());
                      }
                      for (;;) {
                          entry = pool.getFree(state);
                          if (entry == null) {
                              break;
                          }
                          if (entry.isExpired(System.currentTimeMillis())) {
                              entry.close();
                          }
                          if (entry.isClosed()) {
                              this.available.remove(entry);
                              pool.free(entry, false);
                          } else {
                              break;
                          }
                      }
                      if (entry != null) {
                          this.available.remove(entry);
                          this.leased.add(entry);
                          onReuse(entry);
                          return entry;
                      }
      
                      // New connection is needed
                      final int maxPerRoute = getMax(route);
                      // Shrink the pool prior to allocating a new connection
                      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);
                          }
                      }
      
                      if (pool.getAllocatedCount() < maxPerRoute) {
                          final int totalUsed = this.leased.size();
                          final int freeCapacity = Math.max(this.maxTotal - totalUsed, 0);
                          if (freeCapacity > 0) {
                              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);
                                  }
                              }
                              final C conn = this.connFactory.create(route);
                              entry = pool.add(conn);
                              this.leased.add(entry);
                              return entry;
                          }
                      }
      
                      boolean success = false;
                      try {
                          pool.queue(future);
                          this.pending.add(future);
                          if (deadline != null) {
                              success = this.condition.awaitUntil(deadline);
                          } else {
                              this.condition.await();
                              success = true;
                          }
                          if (future.isCancelled()) {
                              throw new ExecutionException(operationAborted());
                          }
                      } finally {
                          // In case of 'success', we were woken up by the
                          // connection pool and should now have a connection
                          // waiting for us, or else we're shutting down.
                          // Just continue in the loop, both cases are checked.
                          pool.unqueue(future);
                          this.pending.remove(future);
                      }
                      // check for spurious wakeup vs. timeout
                      if (!success && (deadline != null && deadline.getTime() <= System.currentTimeMillis())) {
                          break;
                      }
                  }
                  throw new TimeoutException("Timeout waiting for connection");
              } finally {
                  this.lock.unlock();
              }
          }
      
      1. 代碼已建立有一個deadline ,然后判斷timeout ,這個timeout要注意。如果大于零才會賦值deadline, 如果為0 則不會賦值deadline 也就是說deadline始終為null
              Date deadline = null;
              if (timeout > 0) {
                  //如果超時時間有效,則設定deadline
                  deadline = new Date (System.currentTimeMillis() + tunit.toMillis(timeout));
              }
      
      1. 進入鎖代碼。pool.getFree 獲取池資源。如果獲取到了,并且Connect的檢驗并沒有被關閉,則直接return entry
                      Asserts.check(!this.isShutDown, "Connection pool shut down");
                      for (;;) {
                          //獲取池資源
                          entry = pool.getFree(state);
                          if (entry == null) {
                              break;
                          }
                          //校驗超時
                          if (entry.isExpired(System.currentTimeMillis())) {
                              entry.close();
                          }
                          if (entry.isClosed()) {
                              this.available.remove(entry);
                              pool.free(entry, false);
                          } else {
                              break;
                          }
                      }
                      if (entry != null) {
                          this.available.remove(entry);
                          this.leased.add(entry);
                          onReuse(entry);
                          return entry;
                      }
      
      1. 如果沒有獲取到 進行接下來的代碼。
      2. 判斷是否達到了host配置的最大池數量,是否需要增加, 如果需要增加,則會在增加新連接之前縮小池,然后再分配返回entry
                      // New connection is needed  獲取是否需要創建新的連接
                      final int maxPerRoute = getMax(route);
                      // Shrink the pool prior to allocating a new connection
                      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);
                          }
                      }
      
                      if (pool.getAllocatedCount() < maxPerRoute) {
                          final int totalUsed = this.leased.size();
                          final int freeCapacity = Math.max(this.maxTotal - totalUsed, 0);
                          if (freeCapacity > 0) {
                              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);
                                  }
                              }
                              final C conn = this.connFactory.create(route);
                              entry = pool.add(conn);
                              this.leased.add(entry);
                              return entry;
                          }
                      }
      
      1. 如果并不是上面的情況,實際情況就是池子被用光了,而且還達到了最大。就不能從池子中獲取資源了。只能等了……
      2. 等待的時候會判斷deadline , 如果deadline不為null 就會await一個時間。如果為null,那么等待就會無限等待,直到有資源。
                      boolean success = false;
                      try {
                          if (future.isCancelled()) {
                              throw new InterruptedException("Operation interrupted");
                          }
                          pool.queue(future);
                          this.pending.add(future);
                          //判斷deadline是否有效
                          if (deadline != null) {
                              //如果有效就等待至deadline
                              success = this.condition.awaitUntil(deadline);
                          } else {
                             //如果無效就一直等待,沒有超時時間
                              this.condition.await();
                              success = true;
                          }
                          if (future.isCancelled()) {
                              throw new InterruptedException("Operation interrupted");
                          }
                      } finally {
                          // In case of 'success', we were woken up by the
                          // connection pool and should now have a connection
                          // waiting for us, or else we're shutting down.
                          // Just continue in the loop, both cases are checked.
                          pool.unqueue(future);
                          this.pending.remove(future);
                      }
      

      創建連接池源碼

      問題通過以上的源碼就發現了,關鍵問題是線程池的等待時間,設置一個連接的等待時間即可解決,使得線程不會一直等待 HttpCllient 連接,我找到相應的創建 CloseableHttpClient 衛士,位于 SimpleHostRoutingFilter#newClient#newClient(),源碼如下

      	protected CloseableHttpClient newClient() {
      		final RequestConfig requestConfig = RequestConfig.custom()
      				.setConnectionRequestTimeout(
      				// 設置超時鏈接時間
      						this.hostProperties.getConnectionRequestTimeoutMillis())
      				.setSocketTimeout(this.hostProperties.getSocketTimeoutMillis())
      				.setConnectTimeout(this.hostProperties.getConnectTimeoutMillis())
      				.setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();
      		return httpClientFactory.createBuilder().setDefaultRequestConfig(requestConfig)
      				.setConnectionManager(this.connectionManager).disableRedirectHandling()
      				.build();
      	}
      

      此處可看到,主要是從 this.hostProperties.getConnectionRequestTimeoutMillis(),拿到超時時間,最終我找到了 Springboot 配置 connectionRequestTimeoutMillis 位置,即 ZuulProperties.Host#connectionRequestTimeoutMillis,代碼如下

      	/**
      	 * Represents a host.
      	 */
      	public static class Host {
      
      		/**
      		 * The maximum number of total connections the proxy can hold open to backends.
      		 */
      		private int maxTotalConnections = 200;
      
      		/**
      		 * The maximum number of connections that can be used by a single route.
      		 */
      		private int maxPerRouteConnections = 20;
      
      		/**
      		 * The socket timeout in millis. Defaults to 10000.
      		 */
      		private int socketTimeoutMillis = 10000;
      
      		/**
      		 * The connection timeout in millis. Defaults to 2000.
      		 */
      		private int connectTimeoutMillis = 2000;
      
      		/**
      		 * The timeout in milliseconds used when requesting a connection from the
      		 * connection manager. Defaults to -1, undefined use the system default.
      		 * 此處時間為 -1,即永久等待
      		 */
      		private int connectionRequestTimeoutMillis = -1;
      
      		/**
      		 * The lifetime for the connection pool.
      		 */
      		private long timeToLive = -1;
      
      		/**
      		 * The time unit for timeToLive.
      		 */
      		private TimeUnit timeUnit = TimeUnit.MILLISECONDS;
      
      		public Host() {
      		}
      
              // get set and toString
      
      	}
      

      通過源碼可看到,zuul 有如下的相關配置:

      • maxTotalConnections:HttpClient 總連接數,默認值為200
      • maxPerRouteConnections:HttpClient 單個服務(即服務發現中的每個服務)連接數,默認為 20
      • socketTimeoutMillis:連接服務時間,單位為毫秒,默認為10秒
      • connectTimeoutMillis:服務返回時間,單位為毫秒,默認時間為20秒
      • connectionRequestTimeoutMillis:連接 HttpClient 等待時間,默認為-1,即永久!

      下面來對為何超時進行一個復現:

      假設 100 個請求進來,HttpClient 連接池大小為 20,其中 20 個請求一直在等待遠程服務返回,其余 80 個請求一直在等待連接池的空閑連接,所以連接一直在等待,最終導致其它服務無法進入,最終導致所有服務都癱瘓了。

      因為我的 gateway 未使用路由發現,所以,微服務中的熔斷器和負載均衡,均使用不上。所以只能采用如下方法:

      • 設置 HttpClient 連接時間,即 connectionRequestTimeoutMillis 設置為 10S
      • 增大 HttpClient 連接池大小,使得有足夠多的連接數,來增大并發量,即設置 maxTotalConnections 和 maxPerRouteConnections,這里我設置成了 500 和 250

      下面是我調優后的參數:

      zuul:
        host:
          connect-timeout-millis: 30000
          socket-timeout-millis: 60000
          max-total-connections: 500
          max-per-route-connections: 250
          connection-request-timeout-millis: 10000
      

      3. 后續

      雖然增大了 HttpClient 連接池大小,修改了連接 HttpClient 的時間,但是進行壓測時,依舊會出現某些請求,時間超過了 60S,我們以上的設置為 60S 再加上搶占連接池,總時間也不過 70S,如果超過70S,則會報搶占 HttpClient 連接池異常。但是我壓測后的結果,并不如此,以下是我壓測的結果

      1715158216609.png

      以上壓測顯示,95% 請求時間為 164 S,遠遠超過了我們預測的 70S,并且壓測的 QPS 很低,才 5.1,所以 zuul 的效率還不是很高,還待優化!

      預知后事如何,請期待接下來的博客

      posted @ 2024-07-12 10:36  booleandev  閱讀(180)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 产综合无码一区| 宝贝腿开大点我添添公视频免 | 国产极品美女网站在线观看| 国产一卡2卡三卡4卡免费网站| 亚洲经典av一区二区| 久久国语对白| 东京热无码国产精品| 亚洲综合国产精品第一页| 国产成人无码精品亚洲| 西西人体大胆444WWW| 国产一区二区三区黄色片| 日本一区不卡高清更新二区| 99久久精品国产一区色| 幻女free性俄罗斯毛片| 北碚区| 毛片亚洲AV无码精品国产午夜| 又粗又硬又黄a级毛片| 一区二区三区AV波多野结衣| 综合无码一区二区三区| 久久国产一区二区日韩av| 日韩中文字幕国产精品| 国产精品一区在线蜜臀 | 日韩精品福利一二三专区| 亚洲欧美日韩国产精品专区| 久女女热精品视频在线观看| 国产美女深夜福利在线一| 无码人妻精品一区二区三区蜜桃 | 国产精品麻豆中文字幕| 色av专区无码影音先锋| 狠狠色狠狠综合久久| 成人国产精品日本在线观看| 日韩精品一区二区三区在线观看 | 亚洲一区久久蜜臀av| 亚洲国产日韩a在线亚洲| 永久免费无码av在线网站| 深夜福利啪啪片| 巨大黑人极品videos精品| 在线中文字幕国产精品| 黑人av无码一区| 国产精成人品日日拍夜夜免费| 亚洲春色在线视频|