改善應用程式在 Go 裝置上的效能 (第 2 部分:分析器)

1. 簡介

e0509e8a07ad5537.png

上次更新時間:2022 年 7 月 14 日

應用程式的可觀測性

可觀測性和持續分析器

「可觀測性」一詞用於描述系統的屬性。如果系統具有可觀測性,團隊就能主動偵錯。在這種情況下,可觀測性的三大支柱 (記錄、指標和追蹤記錄) 是系統取得可觀測性的基本工具。

此外,除了可觀測性的三大支柱外,持續分析也是可觀測性的另一個重要元件,且使用者群正在擴大。Cloud Profiler 是其中一個發起者,提供簡單的介面,可深入瞭解應用程式呼叫堆疊中的效能指標。

本程式碼研究室是本系列的第 2 部分,涵蓋持續分析代理程式的檢測。第 1 部分涵蓋使用 OpenTelemetry 和 Cloud Trace 進行分散式追蹤,您將在第 1 部分中進一步瞭解如何找出微服務的瓶頸。

建構項目

在本程式碼研究室中,您將在「莎士比亞應用程式」(又稱 Shakesapp) 的伺服器服務中,設定持續分析器代理程式,該應用程式會在 Google Kubernetes Engine 叢集上執行。Shakesapp 的架構如下所述:

44e243182ced442f.png

  • Loadgen 會以 HTTP 格式將查詢字串傳送至用戶端
  • 用戶端會透過 gRPC 將查詢從 loadgen 傳遞至伺服器
  • 伺服器接受用戶端傳送的查詢,從 Google Cloud Storage 擷取所有莎士比亞作品的文字格式,搜尋含有查詢內容的行,並將符合條件的行數傳回給用戶端

在第 1 部分中,您發現伺服器服務的某處存在瓶頸,但無法找出確切原因。

課程內容

  • 如何嵌入 Profiler 代理程式
  • 如何使用 Cloud Profiler 找出瓶頸

本程式碼研究室說明如何在應用程式中設定持續分析器代理程式。

軟硬體需求

  • Go 的基本知識
  • Kubernetes 的基本知識

2. 設定和需求

自修實驗室環境設定

如果您沒有 Google 帳戶 (Gmail 或 Google 應用程式),請先建立帳戶。登入 Google Cloud Platform 主控台 ( console.cloud.google.com),然後建立新專案。

如果您已有專案,請按一下主控台左上方的專案選取下拉式選單:

7a32e5469db69e9.png

然後在隨即顯示的對話方塊中,按一下「NEW PROJECT」(新專案) 按鈕,建立新專案:

7136b3ee36ebaf89.png

如果您還沒有專案,應該會看到如下對話方塊,請建立第一個專案:

870a3cbd6541ee86.png

在隨後的專案建立對話方塊中,您可以輸入新專案的詳細資料:

affdc444517ba805.png

請記住專案 ID,所有 Google Cloud 專案的專案 ID 都是不重複的名稱 (上述名稱已遭占用,因此不適用於您,抱歉!)。本程式碼研究室稍後會將其稱為 PROJECT_ID。

接下來,如果尚未啟用,您需要在開發人員控制台中啟用帳單,才能使用 Google Cloud 資源並啟用 Cloud Trace API

15d0ef27a8fbab27.png

完成本程式碼研究室的費用不應超過數美元,但如果您決定使用更多資源,或是將資源繼續執行 (請參閱本文件結尾的「清除」一節),則可能會增加費用。Google Cloud Trace、Google Kubernetes Engine 和 Google Artifact Registry 的價格請參閱官方說明文件。

Google Cloud Platform 新使用者享有價值 $300 美元的免費試用期,因此本程式碼研究室應完全免費。

Google Cloud Shell 設定

雖然可以透過筆電遠端操作 Google Cloud 和 Google Cloud Trace,但在本程式碼研究室中,我們將使用 Google Cloud Shell,這是 Cloud 中執行的指令列環境。

這部以 Debian 為基礎的虛擬機器,搭載各種您需要的開發工具,並提供永久的 5GB 主目錄,而且可在 Google Cloud 運作,大幅提升網路效能並強化驗證功能。也就是說,您只需要瀏覽器 (Chromebook 也可以) 就能完成本程式碼研究室。

如要從 Cloud Shell 啟動 Cloud Shell,只要按一下「啟用 Cloud Shell」圖示 gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A,系統就會佈建並連線至環境,這項作業只需要幾分鐘。

JjEuRXGg0AYYIY6QZ8d-66gx_Mtc-_jDE9ijmbXLJSAXFvJt-qUpNtsBsYjNpv2W6BQSrDc1D-ARINNQ-1EkwUhz-iUK-FUCZhJ-NtjvIEx9pIkE-246DomWuCfiGHK78DgoeWkHRw

Screen Shot 2017-06-14 at 10.13.43 PM.png

連至 Cloud Shell 後,您應該會看到驗證已完成,專案也已設為獲派的專案 ID PROJECT_ID

gcloud auth list

指令輸出

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

指令輸出

[core]
project = <PROJECT_ID>

如果專案未設定,請發出下列指令:

gcloud config set project <PROJECT_ID>

在尋找「PROJECT_ID」嗎?請檢查您在設定步驟中使用的 ID,或在 Cloud 控制台資訊主頁中尋找:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Cloud Shell 也會預設設定部分環境變數,這些變數在您執行後續指令時可能很有用。

echo $GOOGLE_CLOUD_PROJECT

指令輸出

<PROJECT_ID>

最後,設定預設可用區和專案。

gcloud config set compute/zone us-central1-f

你可以選擇各種不同區域。詳情請參閱「地區和區域」。

前往語言設定

在本程式碼研究室中,所有原始碼都使用 Go。在 Cloud Shell 執行下列指令,確認 Go 版本是否為 1.17 以上

go version

指令輸出

go version go1.18.3 linux/amd64

設定 Google Kubernetes 叢集

在本程式碼研究室中,您會在 Google Kubernetes Engine (GKE) 上執行微服務叢集。本程式碼研究室的流程如下:

  1. 將基準專案下載至 Cloud Shell
  2. 將微服務建構到容器中
  3. 將容器上傳至 Google Artifact Registry (GAR)
  4. 將容器部署至 GKE
  5. 修改服務的原始碼,以便進行追蹤儀表化
  6. 前往步驟 2

啟用 Kubernetes Engine

首先,我們設定 Kubernetes 叢集,讓 Shakesapp 在 GKE 上執行,因此需要啟用 GKE。前往「Kubernetes Engine」選單,然後按下「啟用」按鈕。

548cfd95bc6d344d.png

現在可以開始建立 Kubernetes 叢集。

建立 Kubernetes 叢集

在 Cloud Shell 執行下列指令,建立 Kubernetes 叢集。請確認區域值位於您要用於建立 Artifact Registry 存放區的區域下方。如果存放區區域未涵蓋該區域,請變更區域值 us-central1-f

gcloud container clusters create otel-trace-codelab2 \
--zone us-central1-f \
--release-channel rapid \
--preemptible \
--enable-autoscaling \
--max-nodes 8 \
--no-enable-ip-alias \
--scopes cloud-platform

指令輸出

Note: Your Pod address range (`--cluster-ipv4-cidr`) can accommodate at most 1008 node(s).
Creating cluster otel-trace-codelab2 in us-central1-f... Cluster is being health-checked (master is healthy)...done.     
Created [https://container.googleapis.com/v1/projects/development-215403/zones/us-central1-f/clusters/otel-trace-codelab2].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-f/otel-trace-codelab2?project=development-215403
kubeconfig entry generated for otel-trace-codelab2.
NAME: otel-trace-codelab2
LOCATION: us-central1-f
MASTER_VERSION: 1.23.6-gke.1501
MASTER_IP: 104.154.76.89
MACHINE_TYPE: e2-medium
NODE_VERSION: 1.23.6-gke.1501
NUM_NODES: 3
STATUS: RUNNING

設定 Artifact Registry 和 Skaffold

現在我們有可供部署的 Kubernetes 叢集。接著,我們要準備容器登錄檔,以便推送及部署容器。在這些步驟中,我們需要設定 Artifact Registry (GAR) 和 Skaffold,以便使用。

設定 Artifact Registry

前往「Artifact Registry」選單,然後按下「啟用」按鈕。

45e384b87f7cf0db.png

稍待片刻,您會看到 GAR 的存放區瀏覽器。按一下「CREATE REPOSITORY」按鈕,然後輸入存放區名稱。

d6a70f4cb4ebcbe3.png

在本程式碼研究室中,我將新存放區命名為 trace-codelab。構件格式為「Docker」,位置類型為「區域」。選擇的區域應與 Google Compute Engine 預設區域相近。舉例來說,上方的範例選擇了「us-central1-f」,因此這裡我們選擇「us-central1 (Iowa)」。然後按一下「建立」按鈕。

9c2d1ce65258ef70.png

現在,您會在存放區瀏覽器中看到「trace-codelab」。

7a3c1f47346bea15.png

我們稍後會返回這裡檢查登錄路徑。

Skaffold 設定

如果您要建構在 Kubernetes 上執行的微服務,Skaffold 是非常實用的工具。只要使用一小組指令,即可處理建構、推送及部署應用程式容器的工作流程。Skaffold 預設會使用 Docker Registry 做為容器登錄檔,因此您需要設定 Skaffold,讓它在將容器推送至 GAR 時能夠辨識。

再次開啟 Cloud Shell,確認是否已安裝 Skaffold。(Cloud Shell 預設會將 skaffold 安裝至環境中)。執行下列指令,查看 Skaffold 版本。

skaffold version

指令輸出

v1.38.0

現在,您可以註冊供 Skaffold 使用的預設存放區。如要取得登錄路徑,請前往 Artifact Registry 資訊主頁,然後點選您在上一個步驟中設定的存放區名稱。

7a3c1f47346bea15.png

頁面頂端就會顯示導覽標記路徑。按一下 e157b1359c3edc06.png 圖示,將登錄檔路徑複製到剪貼簿。

e0f2ae2144880b8b.png

按一下複製按鈕後,瀏覽器底部會顯示對話方塊,並顯示類似下列內容的訊息:

已複製「us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab」

返回 Cloud Shell。執行 skaffold config set default-repo 指令,並使用您剛才從資訊主頁複製的值。

skaffold config set default-repo us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab

指令輸出

set value default-repo to us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab for context gke_stackdriver-sandbox-3438851889_us-central1-b_stackdriver-sandbox

此外,您還需要將登錄檔設定為 Docker 設定。執行下列指令:

gcloud auth configure-docker us-central1-docker.pkg.dev --quiet

指令輸出

{
  "credHelpers": {
    "gcr.io": "gcloud",
    "us.gcr.io": "gcloud",
    "eu.gcr.io": "gcloud",
    "asia.gcr.io": "gcloud",
    "staging-k8s.gcr.io": "gcloud",
    "marketplace.gcr.io": "gcloud",
    "us-central1-docker.pkg.dev": "gcloud"
  }
}
Adding credentials for: us-central1-docker.pkg.dev

您現在可以進行下一個步驟,在 GKE 上設定 Kubernetes 容器。

摘要

在這個步驟中,您會設定程式碼研究室環境:

  • 設定 Cloud Shell
  • 為容器登錄建立 Artifact Registry 存放區
  • 設定 Skaffold 以使用 Container Registry
  • 建立 Kubernetes 叢集,讓 Codelab 微服務在其中執行

下一步

在下一個步驟中,您會在伺服器服務中設定持續分析器代理程式。

3. 建構、推送及部署微服務

下載程式碼研究室教材

在上一個步驟中,我們已設定本程式碼研究室的所有必要條件。現在您可以在這些服務上執行整個微服務。程式碼研究室資料位於 GitHub,因此請使用下列 Git 指令將資料下載至 Cloud Shell 環境。

cd ~
git clone https://github.com/ymotongpoo/opentelemetry-trace-codelab-go.git
cd opentelemetry-trace-codelab-go

專案的目錄結構如下:

.
├── README.md
├── step0
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step1
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step2
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step3
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step4
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
├── step5
│   ├── manifests
│   ├── proto
│   ├── skaffold.yaml
│   └── src
└── step6
    ├── manifests
    ├── proto
    ├── skaffold.yaml
    └── src
  • 資訊清單:Kubernetes 資訊清單檔案
  • proto:用戶端與伺服器之間通訊的 proto 定義
  • src:各項服務原始碼的目錄
  • skaffold.yaml:Skaffold 的設定檔

在本程式碼研究室中,您將更新 step4 資料夾下的原始碼。您也可以參考 step[1-6] 資料夾中的原始碼,瞭解從一開始的變更。(第 1 部分涵蓋步驟 0 到步驟 4,第 2 部分涵蓋步驟 5 和步驟 6)

執行 skaffold 指令

最後,您已準備好建構、推送及部署整個內容至剛建立的 Kubernetes 叢集。這聽起來像是包含多個步驟,但實際上 Skaffold 會為您完成所有作業。請使用下列指令試試看:

cd step4
skaffold dev

執行指令後,您會看到 docker build 的記錄輸出內容,並確認這些內容已成功推送至登錄檔。

指令輸出

...
---> Running in c39b3ea8692b
 ---> 90932a583ab6
Successfully built 90932a583ab6
Successfully tagged us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step1
The push refers to repository [us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice]
cc8f5a05df4a: Preparing
5bf719419ee2: Preparing
2901929ad341: Preparing
88d9943798ba: Preparing
b0fdf826a39a: Preparing
3c9c1e0b1647: Preparing
f3427ce9393d: Preparing
14a1ca976738: Preparing
f3427ce9393d: Waiting
14a1ca976738: Waiting
3c9c1e0b1647: Waiting
b0fdf826a39a: Layer already exists
88d9943798ba: Layer already exists
f3427ce9393d: Layer already exists
3c9c1e0b1647: Layer already exists
14a1ca976738: Layer already exists
2901929ad341: Pushed
5bf719419ee2: Pushed
cc8f5a05df4a: Pushed
step1: digest: sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe size: 2001

推送所有服務容器後,Kubernetes 部署作業會自動啟動。

指令輸出

sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 size: 1997
Tags used in deployment:
 - serverservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step4@sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe
 - clientservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/clientservice:step4@sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8
 - loadgen -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/loadgen:step4@sha256:eea2e5bc8463ecf886f958a86906cab896e9e2e380a0eb143deaeaca40f7888a
Starting deploy...
 - deployment.apps/clientservice created
 - service/clientservice created
 - deployment.apps/loadgen created
 - deployment.apps/serverservice created
 - service/serverservice created

部署完成後,您會在每個容器中看到實際應用程式記錄,這些記錄會發出至 stdout,如下所示:

指令輸出

[client] 2022/07/14 06:33:15 {"match_count":3040}
[loadgen] 2022/07/14 06:33:15 query 'love': matched 3040
[client] 2022/07/14 06:33:15 {"match_count":3040}
[loadgen] 2022/07/14 06:33:15 query 'love': matched 3040
[client] 2022/07/14 06:33:16 {"match_count":3040}
[loadgen] 2022/07/14 06:33:16 query 'love': matched 3040
[client] 2022/07/14 06:33:19 {"match_count":463}
[loadgen] 2022/07/14 06:33:19 query 'tear': matched 463
[loadgen] 2022/07/14 06:33:20 query 'world': matched 728
[client] 2022/07/14 06:33:20 {"match_count":728}
[client] 2022/07/14 06:33:22 {"match_count":463}
[loadgen] 2022/07/14 06:33:22 query 'tear': matched 463

請注意,此時您會看到伺服器傳送的所有訊息。好了,您現在可以開始使用 OpenTelemetry 檢測應用程式,對服務進行分散式追蹤。

開始檢測服務前,請先按下 Ctrl-C 關閉叢集。

指令輸出

...
[client] 2022/07/14 06:34:57 {"match_count":1}
[loadgen] 2022/07/14 06:34:57 query 'what's past is prologue': matched 1
^CCleaning up...
 - W0714 06:34:58.464305   28078 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead.
 - To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke
 - deployment.apps "clientservice" deleted
 - service "clientservice" deleted
 - deployment.apps "loadgen" deleted
 - deployment.apps "serverservice" deleted
 - service "serverservice" deleted

摘要

在這個步驟中,您已在環境中準備好程式碼研究室教材,並確認 Skaffold 正常運作。

下一步

在下一個步驟中,您將修改 loadgen 服務的原始碼,以檢測追蹤記錄資訊。

4. Cloud Profiler 代理程式的檢測

持續剖析的概念

在說明持續剖析的概念之前,我們需要先瞭解剖析的概念。剖析是分析應用程式動態 (動態程式分析) 的方法之一,通常會在應用程式開發期間執行,例如負載測試等。這項活動會測量特定期間的系統指標,例如 CPU 和記憶體用量。收集設定檔資料後,開發人員會分析程式碼外的資料。

持續分析是正常分析的延伸做法:針對長時間執行的應用程式,定期執行短時間視窗設定檔,並收集大量設定檔資料。然後根據應用程式的特定屬性 (例如版本號碼、部署區域、測量時間等),自動產生統計分析結果。如要進一步瞭解這個概念,請參閱我們的說明文件

由於目標是正在執行的應用程式,因此可以定期收集剖析資料,並傳送至後端,後端會後續處理統計資料。這是 Cloud Profiler 代理程式,您很快就會將其嵌入伺服器服務。

嵌入 Cloud Profiler 代理程式

按一下 Cloud Shell 右上方的 776a11bfb2122549.png 按鈕,開啟 Cloud Shell 編輯器。從左側窗格的檔案總管開啟 step4/src/server/main.go,然後找出主要函式。

step4/src/server/main.go

func main() {
        ...
        // step2. setup OpenTelemetry
        tp, err := initTracer()
        if err != nil {
                log.Fatalf("failed to initialize TracerProvider: %v", err)
        }
        defer func() {
                if err := tp.Shutdown(context.Background()); err != nil {
                        log.Fatalf("error shutting down TracerProvider: %v", err)
                }
        }()
        // step2. end setup

        svc := NewServerService()
        // step2: add interceptor
        interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider())
        srv := grpc.NewServer(
                grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor(interceptorOpt)),
                grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor(interceptorOpt)),
        )
        // step2: end adding interceptor
        shakesapp.RegisterShakespeareServiceServer(srv, svc)
        healthpb.RegisterHealthServer(srv, svc)
        if err := srv.Serve(lis); err != nil {
                log.Fatalf("error serving server: %v", err)
        }
}

main 函式中,您會看到 OpenTelemetry 和 gRPC 的一些設定程式碼,這些程式碼已在程式碼研究室第 1 部分中完成。現在您要在此處新增 Cloud Profiler 代理程式的檢測。就像我們對 initTracer() 所做的一樣,您可以編寫名為 initProfiler() 的函式,方便閱讀。

step4/src/server/main.go

import (
        ...
        "cloud.google.com/go/profiler" // step5. add profiler package
        "cloud.google.com/go/storage"
        ...
)

// step5: add Profiler initializer
func initProfiler() {
        cfg := profiler.Config{
                Service:              "server",
                ServiceVersion:       "1.0.0",
                NoHeapProfiling:      true,
                NoAllocProfiling:     true,
                NoGoroutineProfiling: true,
                NoCPUProfiling:       false,
        }
        if err := profiler.Start(cfg); err != nil {
                log.Fatalf("failed to launch profiler agent: %v", err)
        }
}

讓我們仔細看看 profiler.Config{} 物件中指定的選項。

  • 服務:您可以在剖析器資訊主頁選取及切換的服務名稱
  • ServiceVersion:服務版本名稱。您可以根據這個值比較設定檔資料集。
  • NoHeapProfiling:停用記憶體用量分析功能
  • NoAllocProfiling:停用記憶體配置剖析
  • NoGoroutineProfiling:停用 Goroutine 剖析功能
  • NoCPUProfiling:停用 CPU 剖析

在本程式碼研究室中,我們只會啟用 CPU 剖析功能。

現在,您只需要在 main 函式中呼叫這個函式。請務必在匯入區塊中匯入 Cloud Profiler 套件。

step4/src/server/main.go

func main() {
        ...
        defer func() {
                if err := tp.Shutdown(context.Background()); err != nil {
                        log.Fatalf("error shutting down TracerProvider: %v", err)
                }
        }()
        // step2. end setup

        // step5. start profiler
        go initProfiler()
        // step5. end

        svc := NewServerService()
        // step2: add interceptor
        ...
}

請注意,您是使用 go 關鍵字呼叫 initProfiler() 函式。因為 profiler.Start() 會封鎖,所以您需要在另一個 goroutine 中執行。現在可以開始建構了。請務必在部署前執行 go mod tidy

go mod tidy

現在,請使用新的伺服器服務部署叢集。

skaffold dev

通常只要幾分鐘,Cloud Profiler 就會顯示火焰圖。在頂端的搜尋框中輸入「profiler」,然後按一下 Profiler 的圖示。

3d8ca8a64b267a40.png

接著您會看到下列火焰圖。

7f80797dddc0128d.png

摘要

在這個步驟中,您已將 Cloud Profiler 代理程式嵌入伺服器服務,並確認代理程式會產生火焰圖。

下一步

在下一個步驟中,您將使用火焰圖調查應用程式的瓶頸原因。

5. 分析 Cloud Profiler 火焰圖

什麼是火焰圖?

火焰圖是剖析資料的視覺化方式之一。如需詳細說明,請參閱我們的文件,但簡要來說:

  • 每個長條都代表應用程式中的方法/函式呼叫
  • 垂直方向是呼叫堆疊,呼叫堆疊會從上到下成長
  • 水平方向代表資源用量,越長表示用量越高。

因此,我們來看看取得的火焰圖。

7f80797dddc0128d.png

分析火焰圖

在上一節中,您已瞭解火焰圖中的每個長條都代表函式/方法呼叫,長度則表示函式/方法中的資源用量。Cloud Profiler 的火焰圖會依遞減順序或長度 (從左到右) 排序長條,因此您可以先查看圖表左上方。

6d90760c6c1183cd.png

在本例中,grpc.(*Server).serveStreams.func1.2 明顯耗用最多 CPU 時間,從上到下查看呼叫堆疊,會發現大部分時間都花在 main.(*serverService).GetMatchCount,也就是伺服器服務中的 gRPC 伺服器處理常式。

在 GetMatchCount 下方,您會看到一系列 regexp 函式:regexp.MatchStringregexp.Compile。這些函式庫來自標準套件,因此應從多個角度 (包括效能) 經過充分測試。但結果顯示 regexp.MatchStringregexp.Compile 的 CPU 時間資源用量偏高。根據上述事實,這裡的假設是 regexp.MatchString 的使用與效能問題有關。因此,我們來讀取使用該函式的原始碼。

step4/src/server/main.go

func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) {
        resp := &shakesapp.ShakespeareResponse{}
        texts, err := readFiles(ctx, bucketName, bucketPrefix)
        if err != nil {
                return resp, fmt.Errorf("fails to read files: %s", err)
        }
        for _, text := range texts {
                for _, line := range strings.Split(text, "\n") {
                        line, query := strings.ToLower(line), strings.ToLower(req.Query)
                        isMatch, err := regexp.MatchString(query, line)
                        if err != nil {
                                return resp, err
                        }
                        if isMatch {
                                resp.MatchCount++
                        }
                }
        }
        return resp, nil
}

這是呼叫 regexp.MatchString 的位置。閱讀原始碼後,您可能會發現函式是在巢狀 for 迴圈中呼叫。因此使用這項功能時可能會出錯。我們來查閱 regexp 的 GoDoc。

80b8a4ba1931ff7b.png

根據該文件,regexp.MatchString 會在每次呼叫時編譯規則運算式模式。因此,資源用量過高的原因可能是這樣。

摘要

在這個步驟中,您分析火焰圖,並假設資源耗用原因。

下一步

在下一個步驟中,您將更新伺服器服務的原始碼,並確認版本 1.0.0 的變更。

6. 更新原始碼並比較火焰圖

更新原始碼

在上一個步驟中,您假設 regexp.MatchString 的用量與大量資源耗用有關。因此,請按照下列步驟解決問題。開啟程式碼,稍微變更該部分。

step4/src/server/main.go

func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) {
        resp := &shakesapp.ShakespeareResponse{}
        texts, err := readFiles(ctx, bucketName, bucketPrefix)
        if err != nil {
                return resp, fmt.Errorf("fails to read files: %s", err)
        }

        // step6. considered the process carefully and naively tuned up by extracting
        // regexp pattern compile process out of for loop.
        query := strings.ToLower(req.Query)
        re := regexp.MustCompile(query)
        for _, text := range texts {
                for _, line := range strings.Split(text, "\n") {
                        line = strings.ToLower(line)
                        isMatch := re.MatchString(line)
                        // step6. done replacing regexp with strings
                        if isMatch {
                                resp.MatchCount++
                        }
                }
        }
        return resp, nil
}

如您所見,現在系統會從 regexp.MatchString 擷取 regexp 模式編譯程序,並移出巢狀 for 迴圈。

部署這段程式碼前,請務必更新 initProfiler() 函式中的版本字串。

step4/src/server/main.go

func initProfiler() {
        cfg := profiler.Config{
                Service:              "server",
                ServiceVersion:       "1.1.0", // step6. update version
                NoHeapProfiling:      true,
                NoAllocProfiling:     true,
                NoGoroutineProfiling: true,
                NoCPUProfiling:       false,
        }
        if err := profiler.Start(cfg); err != nil {
                log.Fatalf("failed to launch profiler agent: %v", err)
        }
}

現在來看看實際操作方式吧!使用 skaffold 指令部署叢集。

skaffold dev

過一段時間後,重新載入 Cloud Profiler 資訊主頁,看看情況如何。

283cfcd4c13716ad.png

請務必將版本變更為 "1.1.0",這樣就只會看到 1.1.0 版的設定檔。如您所見,GetMatchCount 的長條圖長度縮短,CPU 時間的使用率 (即長條圖長度) 也隨之降低。

e3a1456b4aada9a5.png

您不僅可以查看單一版本的火焰圖,還能比較兩個版本之間的差異。

841dec77d8ba5595.png

將「比較對象」下拉式清單的值變更為「版本」,並將「比較版本」的值變更為原始版本「1.0.0」。

5553844292d6a537.png

您會看到這類火焰圖。圖表的形狀與 1.1.0 相同,但顏色不同。在比較模式中,顏色代表的意義如下:

  • 藍色:減少的值 (資源用量)
  • 橘色:獲得的價值 (資源用量)
  • 灰色:中立

瞭解圖例後,我們來進一步瞭解函式。按一下要放大的長條,即可查看堆疊中的詳細資料。請點選 main.(*serverService).GetMatchCount 長條。將滑鼠游標懸停在長條上,即可查看比較詳細資料。

ca08d942dc1e2502.png

這表示 CPU 總作業時間從 5.26 秒縮短至 2.88 秒 (總時間為 10 秒 = 取樣視窗)。這是一大進步!

現在,您可以根據設定檔資料分析結果,提升應用程式效能。

摘要

在這個步驟中,您在伺服器服務中進行編輯,並在 Cloud Profiler 的比較模式中確認改善成效。

下一步

在下一個步驟中,您將更新伺服器服務的原始碼,並確認版本 1.0.0 的變更。

7. 額外步驟:確認追蹤記錄瀑布圖的改善情況

分散式追蹤與持續分析的差異

在 Codelab 第 1 部分中,您確認可以找出要求路徑中微服務的瓶頸服務,但無法找出特定服務瓶頸的確切原因。在第 2 部分的程式碼研究室中,您已瞭解持續剖析功能可讓您從呼叫堆疊中找出單一服務內的瓶頸。

在這個步驟中,我們來檢視分散式追蹤 (Cloud Trace) 的瀑布圖,並查看與持續分析的差異。

這張瀑布圖是查詢「love」的其中一個追蹤記錄。總共耗時約 6.7 秒 (6700 毫秒)。

e2b7dec25926ee51.png

這是針對相同查詢進行改善後的結果。如您所見,總延遲時間現在為 1.5 秒 (1500 毫秒),相較於先前的實作方式,這是一大進步。

feeb7207f36c7e5e.png

這裡的重點是,在分散式追蹤瀑布圖中,除非您在各處都進行跨度檢測,否則無法取得呼叫堆疊資訊。此外,分散式追蹤只著重於服務之間的延遲,而持續剖析則著重於單一服務的電腦資源 (CPU、記憶體、OS 執行緒)。

從另一個角度來看,分散式追蹤是事件基礎,持續性設定檔則是統計資料。每項追蹤記錄都有不同的延遲圖表,您需要使用分配等不同格式,才能取得延遲變化的趨勢。

摘要

在本步驟中,您檢查了分散式追蹤和持續剖析之間的差異。

8. 恭喜

您已成功使用 OpenTelemetry 建立分散式追蹤記錄,並在 Google Cloud Trace 中確認微服務的要求延遲時間。

如要進行延伸練習,可以自行嘗試下列主題。

  • 目前的實作方式會傳送健康狀態檢查產生的所有範圍。(grpc.health.v1.Health/Check) 如何從 Cloud Trace 中篩除這些時距?提示請見這裡
  • 將事件記錄與時距相互關聯,並瞭解這項功能在 Google Cloud Trace 和 Google Cloud Logging 中的運作方式。提示請見這裡
  • 以其他語言的服務取代部分服務,並嘗試使用該語言的 OpenTelemetry 進行檢測。

此外,如要進一步瞭解剖析器,請繼續閱讀第 2 部分。在這種情況下,您可以略過下方的清理部分。

清理

完成本程式碼研究室後,請停止 Kubernetes 叢集,並務必刪除專案,以免 Google Kubernetes Engine、Google Cloud Trace 和 Google Artifact Registry 產生非預期的費用。

請先刪除叢集。如果您使用 skaffold dev 執行叢集,只要按下 Ctrl-C 即可。如果叢集是透過 skaffold run 執行,請執行下列指令:

skaffold delete

指令輸出

Cleaning up...
 - deployment.apps "clientservice" deleted
 - service "clientservice" deleted
 - deployment.apps "loadgen" deleted
 - deployment.apps "serverservice" deleted
 - service "serverservice" deleted

刪除叢集後,請依序選取選單窗格中的「IAM & Admin」>「Settings」,然後按一下「SHUT DOWN」按鈕。

45aa37b7d5e1ddd1.png

然後在對話方塊的表單中輸入專案 ID (而非專案名稱),並確認要關閉專案。