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

1. 簡介

e0509e8a07ad5537.png

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

應用程式的觀測能力

觀測能力和持續分析器

「觀測能力」是指用來描述系統屬性的字詞。具備觀測能力的系統可讓團隊主動偵錯。在這些情境下,觀測能力的三大要素;記錄、指標和追蹤記錄是系統取得觀測能力的基本檢測標準。

除了觀測能力的三大要素以外,持續分析是觀測能力的另一個關鍵要素,同時在業界擴大使用者族群。Cloud Profiler 是來源系統之一,您可透過簡單易用的介面細查應用程式呼叫堆疊中的效能指標。

本程式碼研究室是本系列文章的第 2 部分,說明如何檢測連續分析器代理程式。第 1 部分涵蓋利用 OpenTelemetry 和 Cloud Trace 的分散式追蹤功能,第 1 部分會說明如何更妥善地找出微服務的瓶頸。

建構項目

在本程式碼研究室中,您要在「Shakespeare application」的伺服器服務中檢測連續分析器代理程式。(又稱「Shakesapp」) 使用 Google Kubernetes Engine 叢集Shakesapp 的架構如下:

44e243182ced442f.png

  • Loadgen 使用 HTTP 將查詢字串傳送至用戶端
  • 用戶端將查詢從 loadgen 傳遞至 gRPC 中的伺服器
  • 伺服器接受從用戶端查詢、從 Google Cloud Storage 擷取所有 Shakespare 都能以文字格式運作、搜尋包含查詢的行,並傳回與用戶端相符的該行數

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

課程內容

  • 如何嵌入分析器代理程式
  • 如何調查 Cloud Profiler 中的瓶頸

本程式碼研究室說明如何在應用程式中檢測連續分析器代理程式。

軟硬體需求

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

2. 設定和需求

自修環境設定

如果您還沒有 Google 帳戶 (Gmail 或 Google Apps),請先建立帳戶。登入 Google Cloud Platform 控制台 ( console.cloud.google.com),並建立新專案。

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

7a32e5469db69e9.png

並點選 [新增專案]按鈕,用於建立新專案:

7136b3ee36ebaf89.png

如果您還沒有專案,系統會顯示如下的對話方塊,讓您建立第一個專案:

870a3cbd6541ee86.png

後續的專案建立對話方塊可讓您輸入新專案的詳細資料:

affdc444517ba805.png

請記住,專案 ID 在所有的 Google Cloud 專案中是不重複的名稱 (已經有人使用上述名稱,目前無法為您解決問題!)。稍後在本程式碼研究室中會稱為 PROJECT_ID。

接下來,如果您尚未在 Developers Console 中啟用計費功能,您需要先在 Developers Console 中啟用計費功能,才能使用 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 為基礎的虛擬機器,搭載各種您需要的開發工具。提供永久的 5 GB 主目錄,而且在 Google Cloud 中運作,大幅提高網路效能和驗證能力。換言之,本程式碼研究室只需要在 Chromebook 上運作即可。

如要透過 Cloud 控制台啟用 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 後,您應會發現自己通過驗證,且專案已設為 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 語言設定

在本程式碼研究室中,我們會針對所有原始碼使用 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 叢集。接下來,我們要針對推送和部署容器的 Container Registry 做好準備。為執行這些步驟,我們必須設定 Artifact Registry (GAR) 和 Skaffold,

Artifact Registry 設定

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

45e384b87f7cf0db.png

一段時間後,您會看到 GAR 的存放區瀏覽器。按一下 [建立存放區]按鈕,然後輸入存放區名稱。

d6a70f4cb4ebcbe3.png

在本程式碼研究室中,我會將新存放區命名為 trace-codelab。構件的格式為「Docker」地點類型為「區域」選擇接近您為 Google Compute Engine 預設可用區設定的區域。以下範例選擇「us-central1-f」上方,我們選擇「us-central1 (愛荷華州)」接著按一下「建立」按鈕。

9c2d1ce65258ef70.png

現在您會看到複製到存放區瀏覽器

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

按一下「複製」按鈕後,您會在瀏覽器底部看到含有下列訊息的對話方塊:

&quot;us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab&quot;已複製

返回 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
  • 已為 Container Registry 建立 Artifact Registry 存放區
  • 設定 skaffold 以使用 Container Registry
  • 建立用於執行程式碼研究室微服務的 Kubernetes 叢集

下一步

在下一個步驟中,您要在伺服器服務中檢測連續分析器代理程式。

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 Deployment 就會自動啟動。

指令輸出

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 和記憶體用量) 的單樣本活動。收集設定檔資料後,開發人員分析這些資訊,

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

由於目標是執行中的應用程式,因此可定期收集設定檔資料,並傳送至某些後端處理統計資料的後端。這是 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() 會封鎖,所以您必須在另一個 Go 常式中執行。現在可以開始建構了。請務必在部署前執行 go mod tidy

go mod tidy

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

skaffold dev

通常會在幾分鐘內看到 Cloud Profiler 的火焰圖。輸入「profiler」,然後按一下 Profiler 圖示。

3d8ca8a64b267a40.png

然後,您會看見以下火焰圖。

7f80797dddc0128d.png

摘要

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

下一步

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

5. 分析 Cloud Profiler 火焰圖

什麼是 Flame Graph?

火焰圖是視覺化呈現設定檔資料的方法之一。如需詳細的解釋,請參閱我們的文件,但摘要內容如下:

  • 每個長條都代表應用程式中的方法/函式呼叫
  • 垂直方向為呼叫堆疊;從上到下增長
  • 水平方向是指資源用量;時間越長越好

有鑑於此,我們來看看取得的火焰圖。

7f80797dddc0128d.png

分析火焰圖

在上一節中,您瞭解火焰圖中的每個長條都代表函式/方法呼叫,而其長度代表函式/方法中的資源用量。Cloud Profiler 的火焰圖會以遞減順序或由左至右為長度排序長條,因此請先檢視圖表的左上角。

6d90760c6c1183cd.png

在我們的例子中,明確表示 grpc.(*Server).serveStreams.func1.2 耗用了大部分的 CPU 作業時間,且藉由從頂端到底部檢視呼叫堆疊,所花費的時間大多是在 main.(*serverService).GetMatchCount 中,也就是伺服器服務中的 gRPC 伺服器處理常式。

在 GetMatchCount 下方,您會看到一系列規則運算式函式: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-loop 中呼叫。因此這個函式的使用方式可能有誤。我們來查詢 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 中擷取規則運算式模式編譯程序,並移出巢狀 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. 額外步驟:確認 Trace 刊登序列中的改善程度

分散式追蹤記錄和持續剖析的差異

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

在這個步驟中,我們要透過分散式追蹤記錄 (Cloud Trace) 查看瀑布圖,瞭解持續剖析的成效差異。

此瀑布圖是包含查詢「love」的追蹤記錄之一。總共需要約 6.7 秒 (6700 毫秒)。

e2b7dec25926ee51.png

這已改善同一個查詢的服務品質。如您所知,總延遲時間現在為 1.5 秒 (1500 毫秒),與先前的實作相比,是極大的進步。

feeb7207f36c7e5e.png

重點在於,在分散式追蹤記錄刊登序列圖表中,您必須檢測周圍的跨範圍,才能取得呼叫堆疊資訊。另外,分散式追蹤記錄只會著重在服務之間的延遲,而持續剖析則著重於單一服務的電腦資源 (CPU、記憶體、OS 執行緒)。

另一方面,分散式追蹤記錄是事件基礎,連續設定檔屬於統計資料。每個追蹤記錄的延遲時間圖表都不同,因此您需要採用分佈等其他格式,才能掌握延遲變化的趨勢。

摘要

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

8. 恭喜

您已成功透過 OpenTelemery 建立分散式追蹤記錄,並確認 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 與管理員」>,然後按一下「SHUT DOWN」按鈕。

45aa37b7d5e1ddd1.png

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