博客 / 詳情

返回

Istio 流量治理實戰:鏡像、超時重試、熔斷與限流,一次講透

前言

本小節繼續來描述istio對於流量的各種操作

流量鏡像

對標nginx的mirror功能,複製一份流量到對應的地址去,通常用來做從線上環境引流至其他環境做測試或者分析

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: backend-vs
  namespace: default
spec:
  hosts:
  - backend-service
  - api.wilsontest.com
  http:
  - mirror:
      host: backend-service
      subset: v1
    mirrorPercentage:
      value: 100
    route:
    - destination:
        host: backend-service
        subset: v0

流量先到v0版本,istio-proxy複製一份流量到v1版本。如果不想1比1復刻,可以調整mirrorPercentage百分比功能

如果mirror host的目標不存在,怎麼發現該錯誤及時調整host配置呢?

超時/重試

配置超時/重試的原因主要是為了解決:

  • 調用外部網絡的接口,很容易產生諸如502、504、499,甚至連接中斷等問題,有了重試,可以儘可能的嘗試再次發起,而不是直接報錯
  • 公用雲網絡抖動,導致客户端收到一堆5xx,從而引起客户產生不適
  • 後端服務沒有優雅更新,一旦發版,導致大量502,重試可以緩解502,避免告警風暴
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: backend-retry
spec:
  hosts:
  - backend-service
  http:
  - route:
    - destination:
        host: backend-service
    timeout: 1s
    retries:
      attempts: 3  # 最大重試次數
      perTryTimeout: 1s  # 每次嘗試的超時
      retryOn:  # 觸發重試的條件
        - 5xx
        - gateway-error
        - connect-failure
        - refused-stream

有位老哥説了,如果一套qps很高的集羣,一旦發生重試,那就意味着短時間之內上游服務的qps至少翻一倍(第一波請求不成功,很快第二波請求就要來了),那這時候上游服務就有被沖垮的風險

説的沒錯,重試是為了提高請求的成功率,但是不可避免增加系統負載,並且增加請求的響應時間,如果大量重試,那就會導致重試風暴,帶來更大的問題

重試次數

為了避免重試風暴,在配置策略的時候應該考慮合理的重試次數

    retries:
      attempts: 3  # 最大重試次數
      perTryTimeout: 1s  # 每次嘗試的超時

重試3次,每次間隔1s,然後就應該報錯,介入查看了

級聯超時

超時時間逐層遞減,前端超時 > 網關超時 > 服務超時
frontend: timeout: 5s
nginx-test: timeout: 3s
backend-service: timeout: 2s

退避策略

簡而言之,就是重試失敗之後不是馬上重試,而是等一段時間再重試

  • 固定退避(Fixed Backoff):每次重試等待固定時間
    • attempt 1: 等待 100ms
    • attempt 2: 等待 100ms
    • attempt 3: 等待 100ms
  • 線性退避(Linear Backoff):等待時間線性增加
    • attempt 1: 等待 100ms
    • attempt 2: 等待 200ms
    • attempt 3: 等待 300ms
  • 指數退避(Exponential Backoff):等待時間按指數增加(乘以係數),最使用也最常用
    • attempt 1: 等待 100ms
    • attempt 2: 等待 200ms
    • attempt 3: 等待 400ms
    • attempt 4: 等待 800ms
    • attempt 5: 等待 1600ms
  • 隨機退避(Jitter/隨機抖動):在退避時間中加入隨機性,打破同一時間重試,避免"驚羣效應"
    • attempt 1: 等待 100ms ± 隨機時間
    • attempt 2: 等待 200ms ± 隨機時間
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: backend-vs
  namespace: default
spec:
  hosts:
  - backend-service
  - api.wilsontest.com
  http:
  - retries:
      attempts: 10
      perTryTimeout: 1s
      retryOn: 5xx,connect-failure
    route:
    - destination:
        host: backend-service
        subset: v0

測試istio-proxy的策略

istio-proxy自帶了指數退避隨機退避,初始25ms

為了探索istio-proxy是否帶有指數退避隨機退避的特點,特意設置attempts: 10(日常用可以設置小一點,比如筆者通常設置為3)

設置後端報錯代碼,只要報錯5xx即可,所以我直接將代碼的關鍵字改錯,應該會報語法錯誤或者方法找不到之類的

Traceback (most recent call last):
  File "/usr/local/lib/python3.11/site-packages/tornado/web.py", line 1846, in _execute
    result = method(*self.path_args, **self.path_kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/test.py", line 9, in get
    self.writ(ret)
    ^^^^^^^^^
AttributeError: 'TestFlow' object has no attribute 'writ'

都準備好了,開始測試:

  • curl -s -H 'host: api.wilsontest.com' 10.22.12.178:30785/test
  • 查看日誌,有11條日誌,符合預期:第1次訪問+attempts: 10
    [2026-02-05T06:51:41.322Z] "GET /test HTTP/1.1" 500 - upstream=10.244.0.73:10000 duration=1ms route=default
    [2026-02-05T06:51:41.332Z] "GET /test HTTP/1.1" 500 - upstream=10.244.0.73:10000 duration=1ms route=default
    [2026-02-05T06:51:41.369Z] "GET /test HTTP/1.1" 500 - upstream=10.244.0.73:10000 duration=2ms route=default
    [2026-02-05T06:51:41.441Z] "GET /test HTTP/1.1" 500 - upstream=10.244.0.73:10000 duration=1ms route=default
    [2026-02-05T06:51:41.463Z] "GET /test HTTP/1.1" 500 - upstream=10.244.0.73:10000 duration=2ms route=default
    [2026-02-05T06:51:41.480Z] "GET /test HTTP/1.1" 500 - upstream=10.244.0.73:10000 duration=1ms route=default
    [2026-02-05T06:51:41.660Z] "GET /test HTTP/1.1" 500 - upstream=10.244.0.73:10000 duration=1ms route=default
    [2026-02-05T06:51:41.787Z] "GET /test HTTP/1.1" 500 - upstream=10.244.0.73:10000 duration=1ms route=default
    [2026-02-05T06:51:41.804Z] "GET /test HTTP/1.1" 500 - upstream=10.244.0.73:10000 duration=1ms route=default
    [2026-02-05T06:51:41.978Z] "GET /test HTTP/1.1" 500 - upstream=10.244.0.73:10000 duration=1ms route=default
    [2026-02-05T06:51:42.116Z] "GET /test HTTP/1.1" 500 - upstream=10.244.0.73:10000 duration=2ms route=default
    
  • 分析下時間
    序號 時間戳 與上一次間隔
    1 41.322
    2 41.332 +10ms
    3 41.369 +37ms
    4 41.441 +72ms
    5 41.463 +22ms
    6 41.480 +17ms
    7 41.660 +180ms
    8 41.787 +127ms
    9 41.804 +17ms
    10 41.978 +174ms
    11 42.116 +138ms
  • 從日誌看來確實滿足了指數+隨機,初始 backoff:~25ms、指數增長、加入 jitter(隨機抖動)
    • 10ms → 37ms → 72ms → 180ms → 127ms → 174ms → 138ms

經過這次簡單的測試:

  • 配置重試應該要針對冪等的request,非冪等是絕對不能使用重試的
  • retries應該要配置小一些,否則就會出現重試風暴,就像測試中10次,相當於原請求放大了10倍

熔斷

熔斷是為了保護後端服務不被流量風暴淹沒,保護系統整體穩定

  • 目標:如果後端檢測5xx,超過3次,就將該pod踢下線,30s之後又加回來

    apiVersion: networking.istio.io/v1
    kind: DestinationRule
    metadata:
      name: backend-dr
      namespace: default
    spec:
      host: backend-service
      subsets:
      - labels:
          version: v0
        name: v0
      trafficPolicy:
        outlierDetection:
          baseEjectionTime: 30s
          consecutive5xxErrors: 3
          interval: 5s
          maxEjectionPercent: 100
    
    • baseEjectionTime: 30s:服務被下線的時間,30s
    • consecutive5xxErrors: 3:觸發熔斷的條件,有3次5xx
    • interval: 5s:檢測間隔,5s
    • maxEjectionPercent: 100,被下線的服務比例,100%
  • 後端backend服務依然會報錯500,先訪問3次,curl -s -H 'host: api.wilsontest.com' 10.22.12.178:30785/test

    Traceback (most recent call last):
      File "/usr/local/lib/python3.11/site-packages/tornado/web.py", line 1846, in _execute
        result = method(*self.path_args, **self.path_kwargs)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/opt/test.py", line 9, in get
        self.writ(ret)
    
  • 第四次再訪問

    no healthy upstream
    
  • 符合預期,第四次服務直接被熔斷了,並且由於backend的pod只有1個,istio下線了,導致nginx沒有upstream

限流

首先基於http1.1,每次發起http並不是短鏈了,而是長連接。為了不讓每次都產生3次握手與4次揮手的連接消耗,istio-proxy與後端服務backend之間會維護一個長連接

  • 配置在DestinationRule上

    apiVersion: networking.istio.io/v1
    kind: DestinationRule
    metadata:
      name: backend-dr
      namespace: default
    spec:
      host: backend-service
      subsets:
      - labels:
          version: v0
        name: v0
      trafficPolicy:
        connectionPool:
          http:
            http1MaxPendingRequests: 1
            maxRequestsPerConnection: 5
    
    
    • http1MaxPendingRequests: 1maxRequestsPerConnection: 5是為了方便測試,改得非常的小
    • http1MaxPendingRequests: 1:等待“可用連接”的 HTTP 請求數量,如果沒有可用連接,最多允許1個,超出就報503
    • maxRequestsPerConnection: 5:一條 TCP 連接上最多處理多少個 HTTP 請求
  • 使用wrk壓測工具,用20個併發,同時發送20個連接,向目標url發送請求,持續1s

    ▶ wrk -t20 -c20 -d1s -H 'Host: api.wilson.com' http://10.22.12.178:30785/test
    Running 1s test @ http://10.22.12.178:30785/test
      20 threads and 20 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    10.66ms    3.18ms  21.85ms   76.73%
        Req/Sec    93.55     16.33   171.00     80.75%
      1990 requests in 1.10s, 650.09KB read
      Non-2xx or 3xx responses: 92
    Requests/sec:   1808.21
    Transfer/sec:    590.70KB
    
    
    • 可以看到,1秒之內有1990個請求發送至目標url
    • 其中有92個請求有問題
  • 檢查日誌

    ...
    [2026-02-06T07:37:08.168Z] "GET /test HTTP/1.1" 200 - upstream=10.244.0.73:10000 duration=5ms route=default
    [2026-02-06T07:37:08.169Z] "GET /test HTTP/1.1" 503 UO upstream=- duration=0ms route=default
    [2026-02-06T07:37:08.169Z] "GET /test HTTP/1.1" 0 DC upstream=10.244.0.73:10000 duration=4ms route=default
    ...
    
    • http_code是200是正常請求,503就是熔斷保護的結果,觸發了istio熔斷保護而返回客户端503
    • http_code是0,通常意味着 連接在 HTTP 響應頭完整返回之前就已經斷開了,這非常類似於nginx的499。他們本質都描述了一個問題,客户端沒有等到結果就終止連接了,這應該和我們壓測只持續了1s有關係

聯繫我

  • 聯繫我,做深入的交流


至此,本文結束
在下才疏學淺,有撒湯漏水的,請各位不吝賜教...

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.