Linkerd 通過 ServiceProfile 實現超時和重試

Linkerd 服務網格解決的最重要問題之一是可觀察性:提供服務行爲的詳細視圖,Linkerd 對可觀察性的價值主張是,它可以爲你的 HTTP 和 gRPC 服務提供黃金指標,這些都是自動執行,無需更改代碼或開發人員參與的。

開箱即用,Linkerd 在每個服務的基礎上提供這些指標:跨越服務的所有請求,無論這些請求是什麼。然而,有時需要獲得更細粒度的指標。例如前面的 Emojivoto 應用程序中的 Emoji 微服務,前面章節中看到的 Linkerd 報告的指標是在該服務的所有端點上聚合的。在實際場景下面,我們可能還希望看到特定端點的成功率或延遲,例如,一個端點可能對服務特別關鍵,或者特別慢。

爲了解決這個問題,Linkerd 使用了服務配置文件(service profile)的概念,服務配置文件是一個可選的配置,它通知 Linkerd 對服務的不同類型的請求,按其路由進行分類。路由只是一個端點(對於 gRPC)或一個 HTTP verb 和端點(對於 HTTP)。

服務配置文件允許 Linkerd 爲服務提供每個路由(pre-route)而不是每個服務指標,在後面的章節中,我們還將看到服務配置文件允許 Linkerd 對服務執行重試和超時等配置,但是現在我們先只關注指標。

另外需要注意的是服務配置文件並不是簡單的服務與 Linkerd 一起運行所必需的,它們是可選的配置位,可以實現 Linkerd 的更高級行爲,它們也是 Linkerd 使用 Kubernetes CRD 的少數示例之一。接下來我們仍然通過 Emojivoto 應用說明下服務配置文件的使用。

生成服務配置文件

Linkerd 的服務配置文件是通過實例化一個名爲 ServiceProfile 的 Kubernetes CRD 來實現的。ServiceProfile 會枚舉 Linkerd 爲該服務期望的路由。

我們可以手動創建 ServiceProfile,但也可以自動生成它們。Linkerd CLI 有一個 profile 命令,可以通過幾種不同的方式來生成服務配置文件。最常見的方法之一是從服務的現有資源(如 OpenAPI/Swagger 規範或 protobuf 文件)生成它們。

$ linkerd profile -h
Output service profile config for Kubernetes.

Usage:
  linkerd profile [flags] (--template | --open-api file | --proto file) (SERVICE)

Examples:
  # Output a basic template to apply after modification.
  linkerd profile -n emojivoto --template web-svc

  # Generate a profile from an OpenAPI specification.
  linkerd profile -n emojivoto --open-api web-svc.swagger web-svc

  # Generate a profile from a protobuf definition.
  linkerd profile -n emojivoto --proto Voting.proto vote-svc


Flags:
  -h, --help               help for profile
      --ignore-cluster     Output a service profile through offline generation
  -n, --namespace string   Namespace of the service
      --open-api string    Output a service profile based on the given OpenAPI spec file
      --proto string       Output a service profile based on the given Protobuf spec file
      --template           Output a service profile template

Global Flags:
      --api-addr string            Override kubeconfig and communicate directly with the control plane at host:port (mostly for testing)
      --as string                  Username to impersonate for Kubernetes operations
      --as-group stringArray       Group to impersonate for Kubernetes operations
      --cni-namespace string       Namespace in which the Linkerd CNI plugin is installed (default "linkerd-cni")
      --context string             Name of the kubeconfig context to use
      --kubeconfig string          Path to the kubeconfig file to use for CLI requests
  -L, --linkerd-namespace string   Namespace in which Linkerd is installed ($LINKERD_NAMESPACE) (default "linkerd")
      --verbose                    Turn on debug logging

上面的幫助命令輸出列出了可用於爲 ServiceProfile 資源生成 YAML 的標誌,可以看到其中就有一個 --open-api 標誌,用於指示 ServiceProfile 資源將從指定的 OpenAPISwagger 文檔來生成服務配置文件,通過 --proto 標誌可以指示從指定的 Protobuf 文件生成服務配置文件。

Emojivoto 的 web 服務有一個簡單的 Swagger 規範文件,內容如下所示:

# web.swagger
openapi: 3.0.1
version: v10
paths:
  /api/list:
    get: {}
  /api/vote:
    get: {}

現在我們就可以利用上面的規範文件來生成一個 ServiceProfile 對象,命令如下所示:

$ linkerd profile -n emojivoto --open-api web.swagger web-svc > web-sp.yaml

上述命令將輸出服務 web-svcServiceProfile 資源清單文件。請注意,就像 linkerd install 命令一樣,linkerd profile 命令也只生成 YAML,它不會將 YAML 應用到集羣,所以我們將輸出重定向到 web-sp.yaml 文件,對應生成的文件內容如下所示:

apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
  creationTimestamp: null
  name: web-svc.emojivoto.svc.cluster.local
  namespace: emojivoto
spec:
  routes:
    - condition:
        method: GET
        pathRegex: /api/list
      name: GET /api/list
    - condition:
        method: GET
        pathRegex: /api/vote
      name: GET /api/vote

上面的資源清單文件就是一個典型的 ServiceProfile 對象的聲明方式,spec.routes 用來聲明所有的路由規則,每條路由中包含一個 namecondition 屬性:

除了通過 OpenAPI 可以生成服務配置文件之外,也可以通過 Protobuf 來生成,gRPC 協議使用 protobuf 對請求和響應進行編碼和解碼,這意味着每個 gRPC 服務也有一個 protobuf 定義。

Emojivoto 應用的 Voting 微服務就包含有 protobuf 定義,文件內容如下所示:

syntax = "proto3";
option go_package = "github.com/buoyantio/emojivoto/proto";

package emojivoto.v1;

message VotingResult {
    string Shortcode = 1;
    int32 Votes = 2;
}

message VoteRequest {
}

message VoteResponse {
}

message ResultsRequest {
}

message ResultsResponse {
    repeated VotingResult results = 1;
}

service VotingService {
    rpc VotePoop (VoteRequest) returns (VoteResponse);
    rpc VoteJoy (VoteRequest) returns (VoteResponse);
    rpc VoteSunglasses (VoteRequest) returns (VoteResponse);
    ......省略部分內容
}

同樣現在我們可以使用 linkerd profile 命令來生成對應的 ServiceProfile 對象:

$ linkerd profile -n emojivoto --proto Voting.proto voting-svc > voting-sp.yaml

此命令的輸出結果將比上一個命令的輸出多很多,因爲 Voting.proto 文件定義了更多路由,我們可以查看下 voting-sp.yaml 文件,並將其與上面創建的 ServiceProfile 資源進行比較。

此外我們還可以用另外一種方法來動態生成 ServiceProfile,Linkerd 可以監控在指定時間段內進入的實時請求,並從中收集路由數據。對於不瞭解服務提供的內部路由的集羣管理員來說,這是一個非常強大的功能,因爲 Linkerd 會負責處理請求並將它們寫入文件,該特性的底層原理是上一節中提到的實時觀察請求的 Tap 功能。

現在,讓我們使用 linkerd profile 命令監控 emoji 服務 10 秒並將輸出重定向到文件,與前面學習的所有命令一樣,輸出會打印到終端,並且此命令會將輸出重定向到文件,因此我們只需運行該命令(並等待 10 秒)一次。

Linkerd Viz 擴展也有自己的配置文件子命令,可以與 Tap 功能一起使用,從實時流量中生成服務配置文件!如下命令所示:

$ linkerd viz profile emoji-svc --tap deploy/emoji --tap-duration 10s -n emojivoto > emoji-sp.yaml

當請求發送到 emoji 服務時,這些路由通過 Tap 功能收集。我們也可以使用 Emoji.proto 文件生成服務配置文件,然後去比較使用 --proto--tap 標誌創建的 ServiceProfile 資源的定義。

生成的 ServiceProfile 資源清單文件內容如下所示:

# emoji-sp.yaml
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
  creationTimestamp: null
  name: emoji-svc.emojivoto.svc.cluster.local
  namespace: emojivoto
spec:
  routes:
    - condition:
        method: POST
        pathRegex: /emojivoto\.v1\.EmojiService/FindByShortcode
      name: POST /emojivoto.v1.EmojiService/FindByShortcode
    - condition:
        method: POST
        pathRegex: /emojivoto\.v1\.EmojiService/ListAll
      name: POST /emojivoto.v1.EmojiService/ListAll

如果你需要手動編寫服務配置文件,linkerd profile 命令有一個 --template 標誌,可以生成 ServiceProfile 資源的腳手架,然後可以使用你的服務的詳細信息對其進行更新。

$ linkerd profile --template -n emojivoto emoji-svc
### ServiceProfile for emoji-svc.emojivoto ###
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
  name: emoji-svc.emojivoto.svc.cluster.local
  namespace: emojivoto
spec:
  # A service profile defines a list of routes.  Linkerd can aggregate metrics
  # like request volume, latency, and success rate by route.
  routes:
  - name: '/authors/{id}'

    # Each route must define a condition.  All requests that match the
    # condition will be counted as belonging to that route.  If a request
    # matches more than one route, the first match wins.
    condition:
      # The simplest condition is a path regular expression.
      pathRegex: '/authors/\d+'

      # This is a condition that checks the request method.
      method: POST

      # If more than one condition field is set, all of them must be satisfied.
      # This is equivalent to using the 'all' condition:
      # all:
      # - pathRegex: '/authors/\d+'
      # - method: POST

      # Conditions can be combined using 'all', 'any', and 'not'.
      # any:
      # - all:
      #   - method: POST
      #   - pathRegex: '/authors/\d+'
      # - all:
      #   - not:
      #       method: DELETE
      #   - pathRegex: /info.txt

    # A route may be marked as retryable.  This indicates that requests to this
    # route are always safe to retry and will cause the proxy to retry failed
    # requests on this route whenever possible.
    # isRetryable: true

    # A route may optionally define a list of response classes which describe
    # how responses from this route will be classified.
    responseClasses:

    # Each response class must define a condition.  All responses from this
    # route that match the condition will be classified as this response class.
    - condition:
        # The simplest condition is a HTTP status code range.
        status:
          min: 500
          max: 599

        # Specifying only one of min or max matches just that one status code.
        # status:
        #   min: 404 # This matches 404s only.

        # Conditions can be combined using 'all', 'any', and 'not'.
        # all:
        # - status:
        #     min: 500
        #     max: 599
        # - not:
        #     status:
        #       min: 503

      # The response class defines whether responses should be counted as
      # successes or failures.
      isFailure: true

    # A route can define a request timeout.  Any requests to this route that
    # exceed the timeout will be canceled.  If unspecified, the default timeout
    # is '10s' (ten seconds).
    # timeout: 250ms

  # A service profile can also define a retry budget.  This specifies the
  # maximum total number of retries that should be sent to this service as a
  # ratio of the original request volume.
  # retryBudget:
  #   The retryRatio is the maximum ratio of retries requests to original
  #   requests.  A retryRatio of 0.2 means that retries may add at most an
  #   additional 20% to the request load.
  #   retryRatio: 0.2

  #   This is an allowance of retries per second in addition to those allowed
  #   by the retryRatio.  This allows retries to be performed, when the request
  #   rate is very low.
  #   minRetriesPerSecond: 10

  #   This duration indicates for how long requests should be considered for the
  #   purposes of calculating the retryRatio.  A higher value considers a larger
  #   window and therefore allows burstier retries.
  #   ttl: 10s

上面的命令會輸出包含 ServiceProfile 資源的字段和每個字段的詳細說明,如果你需要 ServiceProfile 定義的快速參考,就可以使用 --template 標誌!

到這裏我們就瞭解瞭如何生成 ServiceProfile 資源清單文件,接下來我們來查看服務配置文件中定義的每個路由的指標數據。

Linkerd Dashboard 中查看 Per-Route Metrics

上面我們瞭解瞭如何使用 linkerd profile 命令來生成 ServiceProfile 資源清單文件,現在讓我們重新運行生成命令,並直接將生成的 ServiceProfile 對象直接應用到集羣中:

$ linkerd profile -n emojivoto --open-api web.swagger web-svc | kubectl apply -f -
serviceprofile.linkerd.io/web-svc.emojivoto.svc.cluster.local created
$ linkerd profile -n emojivoto --proto Voting.proto voting-svc | kubectl apply -f -
serviceprofile.linkerd.io/voting-svc.emojivoto.svc.cluster.local created
$ linkerd viz profile emoji-svc --tap deploy/emoji --tap-duration 10s -n emojivoto | kubectl apply -f -
serviceprofile.linkerd.io/emoji-svc.emojivoto.svc.cluster.local created

當上面的命令運行成功後,讓我們打開 Linkerd 儀表盤來查看下相關指標,我們先導航到 Web Deployment 下查看每個路由的指標。

Linkerd Dashboard 中的路由指標

從上圖可以看出來在 ROUTE METRICS 選項卡下面相比默認的 [DEFAULT] 路由多了兩個路由,這兩個路由就是我們通過 linkerd profile --open-api 命令從 web.swagger 文件中生成的兩條路由,每行列中,我們可以看到這兩條路由的每條指標數據。在部署 ServiceProfile 對象之前,我們只能看到 web 服務的聚合指標,部署後我們現在可以看到 /api/list 這條路由是 100% 成功的,/api/vote 路由有一些錯誤。

同樣在服務配置文件之前,我們只知道 web 服務正在返回錯誤,現在我們錯誤是來自與 /api/vote 路由,另外的 [DEFAULT] 默認路由表示當服務配置文件中沒有路由匹配請求時 Linkerd 使用的路由,其會捕獲在 ServiceProfile 之前觀察到的任何流量。

現在我們再去看看另外的服務配置文件,其中的 Voting 服務比較具有代表性,因爲它包含很多路由。在 Linkerd Dashboard 頁面上在 emojivoto 命名空間上進入 Voting Deployment,切換到 ROUTE METRICS 選項卡。我們會該服務下有非常多的路由,上面的 web 服務我們知道 /api/vote 路由的請求成功率低於 100%,所有 voting 服務中的每條路由信息都有可能會提供相關的錯誤信息,由於路由非常多,我們可以直接按照 Success Rate 這一列進行升序排序,正常就可以看到 VoteDoughnut 路由成功率爲 0。

Voting 服務路由

通過 Linkerd CLI 查看 Per-Route Metrics

上面我們已經瞭解瞭如何通過 Dashboard 來查看 Emojivoto 應用中服務的每個路由指標了,接下來我們再嘗試使用 CLI 工具查看每個路由的指標。

linkerd viz routes 命令使用與儀表盤使用的相同指標,讓我們看看使用 Linkerd CLI 來查看 emoji 服務的路由,如下所示:

$ linkerd viz routes deploy/emoji -n emojivoto
ROUTE                                               SERVICE   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
POST /emojivoto.v1.EmojiService/FindByShortcode   emoji-svc   100.00%   1.0rps           1ms           1ms           1ms
POST /emojivoto.v1.EmojiService/ListAll           emoji-svc   100.00%   1.0rps           1ms           1ms           1ms
[DEFAULT]                                         emoji-svc         -        -             -             -             -

我們可以看到,爲 emoji 服務定義的兩條路由都是成功的,並且在 1ms 的時間內處理了請求,可以看出這些路由是健康的。還要注意我們的默認路由,標記爲 [DEFAULT],同樣這是 Linkerd 在服務配置文件中沒有與請求匹配的路由時使用的路由。

現在我們用同樣的方式通過 linkerd viz routes 命令來查看 voting 和 web 服務的路由,如下所示:

$ linkerd viz routes deploy/web -n emojivoto
ROUTE           SERVICE   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
GET /api/list   web-svc   100.00%   1.0rps           1ms           1ms           1ms
GET /api/vote   web-svc    88.33%   1.0rps           2ms           7ms           9ms
[DEFAULT]       web-svc         -        -             -             -             -
$ linkerd viz routes deploy/voting -n emojivoto
ROUTE                             SERVICE   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
Results                        voting-svc         -        -             -             -             -
Vote100                        voting-svc   100.00%   0.0rps           1ms           1ms           1ms
VoteBacon                      voting-svc         -        -             -             -             -
VoteBalloon                    voting-svc         -        -             -             -             -
# ......

voting 服務的輸出很長,因爲它有很多路由,因此可以通過 grep 命令來查找 VoteDoughnut 路由:linkerd viz routes deploy/voting -n emojivoto | grep VoteDoughnut(或者可以使用 -o json 標誌以及 jq 之類的工具來解析輸出)。

到這裏我們就瞭解了 Linkerd 的服務配置文件功能,目前我們暫時專注於服務配置文件的可觀測性功能,我們可以查看之前瞭解的每個路由的黃金指標。接下來我們將進一步深入瞭解 ServiceProfile 並探索 Linkerd 的重試和超時功能。

重試與超時

接下來我們將來了解如何使用 ServiceProfile 配置超時、重試。Linkerd 可以通過流量拆分、負載均衡、重試和超時等功能來確保可靠性,這些中的每一個都在提高系統的整體可靠性方面發揮着重要作用。

但是這些特性究竟能增加什麼樣的可靠性呢?歸根結底是 Linkerd 可以幫助防止瞬時故障。如果服務完全關閉,或始終返回失敗,或始終掛起,那麼再多的重試或負載均衡都無濟於事。但是,如果服務的一個實例出現問題,或者潛在問題只是暫時的,那麼這個時候 Linkerd 就可以派上用場了,而且這些部分的、暫時的故障是分佈式系統的最常出現的問題!

當涉及到 Linkerd 的負載均衡、重試和超時的核心可靠性特性時,這裏有兩件重要的事情需要理解(流量分割,也是一個可靠性特性,但有點不太一樣,我們將在後面的章節中來解決這個問題):

  1. 所有這些技術都發生在客戶端:進行調用的代理是執行這些功能的代理,而不是服務器端的代理。如果你的服務器是網格的,但你的客戶端不是的,那麼將不會在兩者之間的調用中啓用這些功能!

  2. 這三個特性一起使用效果最好。沒有重試,超時沒有什麼價值;如果沒有負載均衡,重試幾乎也沒有什麼價值。

我們可以先了解下負載均衡,Linkerd 會自動在可能的目的地之間對請求進行負載均衡,請注意請求這個詞 - 與四層或 TCP 負載均衡不同,它會均衡連接,Linkerd 將建立到可能的端點集的連接,並在所有這些連接之間均衡請求。這允許對流量進行更細粒度的控制,並且在後臺,Linkerd 的方法非常複雜,使用諸如服務器延遲的指數加權移動平均(EWMA) 之類的技術來優化請求的去向;儘可能跨端點彙集連接;並自動將 HTTP/1.1 流量升級到代理之間的 HTTP/2 連接。然而,從我們的角度來看,並沒有進行任何配置,只需要知道:Linkerd 會自動在其端點之間平衡請求

接着看看超時,超時是在路由上設置最長時間的一種方式。比如你的服務上有一個名爲 getValue() 的路由,而 getValue() 的性能是不可預測的:大多數時候 getValue() 會在 10 毫秒內返回,但有時需要 10 分鐘才能返回,也許是在某些有爭議的資源上存在鎖競爭。如果沒有超時,調用 getValue() 將需要 10 毫秒到 10 分鐘之間的任何時間,但是如果設置了 500 毫秒的超時時間,那麼 getValue() 則最多需要 500 毫秒。

添加超時可以作爲一種機制來限制系統的最壞情況延遲,它允許 getValue() 的調用者具有更可預測的性能,並且不會佔用等待 10 分鐘長的調用返回的資源。其次,更重要的是,超時可以與重試和負載均衡相結合,以自動將請求重新發送到服務的不同實例。如果 getValue() 在實例 A 上很慢,在實例 B 或 C 上可能會很快,甚至多次重試也將遠遠少於等待 10 分鐘。

所以我們再來看看重試,重試是指 Linkerd 自動重試請求。這會在什麼場景下有用呢?同樣由於某些臨時錯誤:如果特定實例上的特定路由返回錯誤,並且簡單地重試該請求可能會導致響應成功,當然重要的是要意識到簡單地重試請求並不能保證成功響應。如果底層錯誤不是暫時的,或者如果 Linkerd 無法重試,那麼應用程序仍然需要處理這些錯誤。

在實踐中,實現重試可能會很麻煩。如果想當然的做也是會有一定風險的,可能會給系統增加額外的負載,這個負載可能會讓事情變得更糟糕。一種常見的故障場景就是重試風暴:服務 A 中的瞬時故障觸發 B 對它請求的重試;這些重試導致 A 過載,這意味着 B 開始失敗的請求;這會觸發其調用者 C 對 B 的重試,然後導致 B 過載,依此類推。對於允許你配置每個請求的最大重試次數的系統尤其如此:每個請求最多重試 3 次聽起來可能沒什麼問題,但在最壞的情況下會增加 300% 的負載。

Linkerd 通過使用重試預算而不是每個請求的限制來設置重試的參數,將重試風暴的可能性降到最低。重試預算是可以重試的請求總數的百分比,Linkerd 的默認行爲是允許對失敗的請求進行 20% (200) 次重試,再加上每秒額外的 10 個請求。例如,如果原始請求負載是每秒 1000 個請求,那麼 Linkerd 將允許每秒重試 210 個請求。當預算用盡時,Linkerd 不會重試請求,而是會向客戶端返回 504 錯誤。

綜上所述:負載均衡、重試和超時都是爲了在出現部分、暫時性故障的情況下保障應用程序的可靠性,並防止這些故障升級爲全局中斷。但它們並不是靈丹妙藥,我們的應用程序中必須仍然能夠處理錯誤。

使用 Per-Route Metrics 來確定何時重試和超時

上面我們瞭解了在 Linkerd 中使用重試和超時的原因,接下來讓我們在前面瞭解的可觀測性功能的基礎上,使用指標來做有關應用重試和超時的決策。

首先,我們將查看 emojivoto 命名空間中所有 Deployments 的統計信息,然後我們再深入研究不健康的服務。直接使用 linkerd viz stat 命令即可,如下所示:

$ linkerd viz stat deploy -n emojivoto
NAME       MESHED   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99   TCP_CONN
emoji         1/1   100.00%   2.3rps           1ms           1ms           4ms          3
vote-bot      1/1   100.00%   0.3rps           1ms           2ms           2ms          1
voting        1/1    87.01%   1.3rps           1ms           7ms           9ms          3
web           1/1    91.91%   2.3rps           1ms          10ms          28ms          3

stat 命令向我們展示了黃金指標,包括成功率和延遲,我們可以注意到 voting 和 web 服務的成功率低於 100%,接下來我們可以通過 linkerd viz edges 命令來了解服務之間的關係。

$ linkerd viz edges deploy -n emojivoto
SRC          DST        SRC_NS        DST_NS      SECURED
vote-bot     web        emojivoto     emojivoto   √
web          emoji      emojivoto     emojivoto   √
web          voting     emojivoto     emojivoto   √
prometheus   emoji      linkerd-viz   emojivoto   √
prometheus   vote-bot   linkerd-viz   emojivoto   √
prometheus   voting     linkerd-viz   emojivoto   √
prometheus   web        linkerd-viz   emojivoto   √

當然也可以通過 Linkerd Dashboard 來查看章魚圖去了解服務之間的關係。

章魚圖

從上面的結果可以看出 web 服務中的 Pods 對 voting 服務的 Pods 進行了調用,所以我們可以猜測是 voting 服務導致了 web 服務的錯誤,當然這還沒結束,還記得前面我們介紹的 ServiceProfile 文件嗎?我們有每條路由的指標,應該能夠準確地看到哪些路由的成功率低於 100%,可以通過 linkerd viz routes 命令去了解 voting 服務的路由指標情況,如下命令所示:

$ linkerd viz routes deploy/voting -n emojivoto
# ......
VoteDog                        voting-svc         -        -             -             -             -
VoteDoughnut                   voting-svc     0.00%   0.1rps           1ms           1ms           1ms
VoteFax                        voting-svc         -        -             -             -             -
VoteFire                       voting-svc   100.00%   0.0rps           1ms           1ms           1ms
VoteFlightDeparture            voting-svc         -        -             -             -             -
# ......

同樣我們也可以通過 Linkerd Dashboard 去查看 voting 服務的 ROUTE METRICS 信息,如下圖所示:

Voting 服務路由

最終我們可以定位到是 VoteDoughnut 這條是請求失敗的路由。現在我們不僅知道 web 服務和 voting 服務之間發生了錯誤,而且也知道了 VoteDoughnut 路由發生了錯誤。接下來我們可以使用重試來嘗試解決錯誤,同時也可以要求開發人員進行代碼調試。

配置重試

在我們開始爲 VotingDoughnut 路由配置重試之前,我們必須首先仔細查看 web 和 voting 服務的指標,因爲這將幫助我們真正瞭解應用重試是否可以解決問題。這裏我們將只使用 Linkerd CLI,因爲它可以用通過使用 -o wide 標誌向我們顯示實際和有效的請求量和成功率,Linkerd 儀表盤會顯示整體成功率和 RPS,但不顯示實際和有效的指標。實際指標和有效指標之間的區別是:

在沒有重試和超時的情況下,顯然這兩個數據是相同的。但是,一旦配置了重試或超時,它們就可能會不一樣了。例如,重試會使實際的 RPS 高於有效的 RPS,因爲從服務器的角度來看,重試是另一個請求,但從客戶端的角度來看,它是同一個請求。重試可以使實際成功率低於有效成功率,因爲失敗的重試調用也發生在服務器上,但不會暴露給客戶端。而超時可能會產生相反的效果:這取決於具體的返回時間,一個最終成功返回的超時請求可能會使實際成功率高於有效成功率,因爲服務器將其視爲成功,而客戶端只看到失敗。

總的來說就是 Linkerd 的實際和有效指標在重試或超時的情況下可能會有所不同,但實際數字代表實際命中服務器的情況,而有效數字代表了在 Linkerd 的可靠性邏輯完成其職責後,客戶端有效地得到了對其請求的響應。

比如我們通過下面的命令來查看 vote-bot 服務的路由指標情況:

$ linkerd viz routes deploy/vote-bot -n emojivoto --to deploy/web -owide
ROUTE           SERVICE   EFFECTIVE_SUCCESS   EFFECTIVE_RPS   ACTUAL_SUCCESS   ACTUAL_RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
GET /api/list   web-svc             100.00%          1.0rps          100.00%       1.0rps           1ms           2ms           2ms
GET /api/vote   web-svc              86.21%          1.0rps           86.21%       1.0rps           2ms           7ms          10ms
[DEFAULT]       web-svc                   -               -                -            -             -             -             -

上面的命令中我們添加了一個 -o wide 的標誌,這樣輸出結果會包含實際和有效的成功和 RPS 指標。從 vote-bot 服務來看,web 服務的 /api/vote 路由的有效成功率和實際成功率都低於 100%,這是因爲現在我們還沒有配置重試。而且我們不能假設所有請求都是可重試的,重試請求對於 Linkerd 來說,是有非常具體的條件的:

現在我們通過在 web 服務的 /api/vote 路由中添加重試功能來驗證下前面的知識。我們再次查看下 web 服務的 ServiceProfile 對象,內容如下所示:

# web-sp.yaml
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
  name: web-svc.emojivoto.svc.cluster.local
  namespace: emojivoto
spec:
  routes:
    - condition:
        method: GET
        pathRegex: /api/list
      name: GET /api/list
    - condition:
        method: GET
        pathRegex: /api/vote
      name: GET /api/vote

接着我們爲路由 /api/vote 增加一個 isRetryable: true 的屬性,如下所示:

# web-sp-with-retry.yaml
# ......
- condition:
    method: GET
    pathRegex: /api/vote
  name: GET /api/vote
  isRetryable: true

更新後重新應用該 ServiceProfile 對象:

$ kubectl apply -f web-sp-with-retry.yaml

應用後我們可以觀察 vote-bot 服務的路由指標變化情況:

$ watch linkerd viz routes deploy/vote-bot -n emojivoto --to deploy/web -o wide
Every 2.0s: linkerd viz routes deploy/vote-bot -n emojivoto --to deploy/web -o wide                   MBP2022.local: Sun Aug 28 17:41:37 2022

ROUTE           SERVICE   EFFECTIVE_SUCCESS   EFFECTIVE_RPS   ACTUAL_SUCCESS   ACTUAL_RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
GET /api/list   web-svc             100.00%          1.0rps          100.00%       1.0rps           2ms           8ms          10ms
GET /api/vote   web-svc              85.00%          1.0rps           16.50%       5.0rps           4ms         255ms         290ms
[DEFAULT]       web-svc                   -               -                -            -             -             -             -

可以看到實際成功率變得非常底了,因爲重試的結果可能還是錯誤。上面我們提到了 Linkerd 的重試行爲是由重試預算配置的,當配置 isRetryable: true 的時候,會應用默認的重試預算規則,以下 ServiceProfile 資源的 YAML 片段,顯示了具有默認值的 retryBudget 對象配置:

spec:
  retryBudget:
    retryRatio: 0.2
    minRetriesPerSecond: 10
    ttl: 10s

其中 retryBudget 參數是具有三個主要的字段:

配置超時

除了重試和重試預算外,Linkerd 還提供超時功能,允許你確保對指定路由的請求永遠不會超過指定的時間。

爲了說明這一點,讓我們重新來看一看 web 和 voting 服務的每個路由指標。

$ linkerd viz routes deploy/vote-bot -n emojivoto --to deploy/web -o wide
$ linkerd viz routes deploy/web -n emojivoto --to deploy/voting -o wide

在前面我們已經瞭解到 web 和 voting 之間的延遲接近 1ms,爲了演示超時,我們將 /api/vote 路由超時時間設置爲 0.5ms,這樣基本上都無法滿足要求就超時了,Linkerd 會將錯誤發送會客戶端,成功率變爲 0 了。

修改 web 服務的 ServiceProfile 對象,添加 timeout 屬性,如下所示:

# web-sp-with-timeout.yaml
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
  name: web-svc.emojivoto.svc.cluster.local
  namespace: emojivoto
spec:
  routes:
    - condition:
        method: GET
        pathRegex: /api/list
      name: GET /api/list
    - condition:
        method: GET
        pathRegex: /api/vote
      name: GET /api/vote
      timeout: 0.5ms
      isRetryable: true

應用上面的對象後,服務的有效和實際成功率就會都下降到 0,因爲 /api/vote 在 0.5ms 內都會超時,所以成功率變爲 0。

$ watch linkerd viz routes deploy/vote-bot -n emojivoto --to deploy/web -o wide
Every 2.0s: linkerd viz routes deploy/vote-bot -n emojivoto --to deploy/web -o wide                   MBP2022.local: Sun Aug 28 19:12:15 2022

ROUTE           SERVICE   EFFECTIVE_SUCCESS   EFFECTIVE_RPS   ACTUAL_SUCCESS   ACTUAL_RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99
GET /api/list   web-svc             100.00%          1.0rps          100.00%       1.0rps           2ms           2ms           2ms
GET /api/vote   web-svc               0.00%          1.0rps            0.00%       0.0rps           0ms           0ms           0ms
[DEFAULT]       web-svc                   -               -                -            -             -             -             -

到這裏我們就瞭解了 Linkerd 中重試和超時使用,重試和超時是 Linkerd 整體策略的一部分,用於在出現瞬時故障時增加可靠性。我們通過使用服務配置文件中的每條路由指標來決定何時以及如何配置重試和超時。Linkerd 用重試預算爲其重試提供參數,可以避免出現 "重試風暴" 的情況,當重試預算用完時,代理將停止向服務發送請求,以避免服務過載,並可能影響到應用程序的其他部分。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/nusZWnVbGKtZbYknlYvFWQ