Strumento per migliorare le prestazioni nella tua app in Go (parte 2: profiler)

1. Introduzione

e0509e8a07ad5537.png

Ultimo aggiornamento: 2022-07-14

Osservabilità dell'applicazione

Osservabilità e profiler continuo

Osservabilità è il termine utilizzato per descrivere un attributo di un sistema. Un sistema con osservabilità consente ai team di eseguire attivamente il debug del sistema. In questo contesto, esistono tre pilastri dell'osservabilità: log, metriche e tracce sono la strumentazione fondamentale che il sistema utilizza per acquisire l'osservabilità.

Inoltre, oltre ai tre pilastri dell'osservabilità, la profilazione continua è un altro componente fondamentale per l'osservabilità e sta espandendo la base utenti nel settore. Cloud Profiler è uno dei originatori e fornisce un'interfaccia semplice per visualizzare in dettaglio le metriche delle prestazioni negli stack di chiamate delle applicazioni.

Questo codelab è la parte 2 della serie e riguarda la strumentazione di un agente profiler continuo. La Parte 1 tratta il tracciamento distribuito con OpenTelemetry e Cloud Trace e nella parte 1 scoprirai di più su come identificare meglio il collo di bottiglia dei microservizi.

Cosa creerai

In questo codelab, utilizzerai l'agente profiler continuo dello strumento nel servizio server dell'applicazione Shakespeare (Shakesapp) in esecuzione su un cluster Google Kubernetes Engine. L'architettura di Shakesapp è descritta di seguito:

44e243182ced442f.png

  • Loadgen invia una stringa di query al client in HTTP
  • I client passano attraverso la query dal loadgen al server in gRPC
  • Il server accetta la query dal client, recupera tutte le opere di Shakespare in formato testo da Google Cloud Storage, cerca le righe che contengono la query e restituisce il numero della riga corrispondente al client

Nella parte 1, hai scoperto che il collo di bottiglia esiste da qualche parte nel servizio server, ma non è stato possibile identificarne la causa esatta.

Cosa imparerai a fare

  • Come incorporare l'agente profiler
  • Come esaminare il collo di bottiglia su Cloud Profiler

Questo codelab spiega come instrumentare un agente profiler continuo nella tua applicazione.

Che cosa ti serve

  • Conoscenza di base di Go
  • Conoscenza di base di Kubernetes

2. Configurazione e requisiti

Configurazione dell'ambiente da seguire in modo autonomo

Se non disponi già di un account Google (Gmail o Google Apps), devi crearne uno. Accedi alla console della piattaforma Google Cloud ( console.cloud.google.com) e crea un nuovo progetto.

Se hai già un progetto, fai clic sul menu a discesa per la selezione del progetto in alto a sinistra nella console:

7a32e5469db69e9.png

e fai clic su "NUOVO PROGETTO" nella finestra di dialogo risultante per creare un nuovo progetto:

7136b3ee36ebaf89.png

Se non hai ancora un progetto, dovresti visualizzare una finestra di dialogo come questa per crearne uno:

870a3cbd6541ee86.png

La finestra di dialogo di creazione del progetto successiva ti consente di inserire i dettagli del nuovo progetto:

affdc444517ba805.png

Ricorda l'ID progetto, che è un nome univoco tra tutti i progetti Google Cloud (il nome precedente è già in uso e non funzionerà per te). Verrà indicato più avanti in questo codelab come PROJECT_ID.

Successivamente, se non l'hai ancora fatto, dovrai abilitare la fatturazione in Developers Console per utilizzare le risorse Google Cloud e abilitare l'API Cloud Trace.

15d0ef27a8fbab27.png

L'esecuzione di questo codelab non dovrebbe costare più di qualche euro, ma potrebbe essere più costoso se decidi di utilizzare più risorse o se le lasci in esecuzione (consulta la sezione relativa alla pulizia alla fine di questo documento). I prezzi di Google Cloud Trace, Google Kubernetes Engine e Google Artifact Registry sono riportati nella documentazione ufficiale.

I nuovi utenti della piattaforma Google Cloud hanno diritto a una prova senza costi di 300$, che dovrebbe rendere questo codelab completamente senza costi.

Configurazione di Google Cloud Shell

Mentre Google Cloud e Google Cloud Trace possono essere gestiti da remoto dal tuo laptop, in questo codelab utilizzeremo Google Cloud Shell, un ambiente a riga di comando in esecuzione nel cloud.

Questa macchina virtuale basata su Debian viene caricata con tutti gli strumenti di sviluppo necessari. Offre una home directory permanente da 5 GB e viene eseguita in Google Cloud, migliorando notevolmente le prestazioni di rete e l'autenticazione. Ciò significa che per questo codelab sarà sufficiente un browser (sì, funziona su Chromebook).

Per attivare Cloud Shell dalla console Cloud, fai clic su Attiva Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (il provisioning e la connessione all'ambiente dovrebbero richiedere solo pochi minuti).

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

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

Dopo la connessione a Cloud Shell, dovresti vedere che hai già eseguito l'autenticazione e che il progetto è già impostato su PROJECT_ID.

gcloud auth list

Output comando

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

Output comando

[core]
project = <PROJECT_ID>

Se, per qualche motivo, il progetto non è impostato, invia semplicemente il seguente comando:

gcloud config set project <PROJECT_ID>

Stai cercando il tuo PROJECT_ID? Controlla l'ID utilizzato nei passaggi di configurazione o cercalo nella dashboard della console Cloud:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Cloud Shell imposta anche alcune variabili di ambiente per impostazione predefinita, cosa che può essere utile quando eseguirai comandi futuri.

echo $GOOGLE_CLOUD_PROJECT

Output comando

<PROJECT_ID>

Infine, imposta la zona e la configurazione del progetto predefinite.

gcloud config set compute/zone us-central1-f

Puoi scegliere zone diverse. Per ulteriori informazioni, consulta la sezione Regioni e Zone.

Configurazione della lingua di Go

In questo codelab, utilizziamo Go per tutto il codice sorgente. Esegui questo comando su Cloud Shell e verifica che la versione di Go sia 1.17 o successiva

go version

Output comando

go version go1.18.3 linux/amd64

Configura un cluster Google Kubernetes

In questo codelab, eseguirai un cluster di microservizi su Google Kubernetes Engine (GKE). Il processo di questo codelab è il seguente:

  1. Scarica il progetto di base in Cloud Shell
  2. Creazione di microservizi in container
  3. Caricare container in Google Artifact Registry (GAR)
  4. esegui il deployment dei container in GKE
  5. Modifica il codice sorgente dei servizi per la strumentazione di traccia
  6. Vai al passaggio 2

Abilita Kubernetes Engine

Per prima cosa, abbiamo configurato un cluster Kubernetes in cui Shakesapp viene eseguito su GKE, quindi dobbiamo abilitare GKE. Vai al menu "Kubernetes Engine". e premi il pulsante ABILITA.

548cfd95bc6d344d.png

Ora è tutto pronto per creare un cluster Kubernetes.

Crea un cluster Kubernetes

Su Cloud Shell, esegui questo comando per creare un cluster Kubernetes. Verifica che il valore della zona si trovi al di sotto della regione che utilizzerai per la creazione del repository Artifact Registry. Modifica il valore della zona us-central1-f se la regione del repository non copre la zona.

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

Output comando

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

Configurazione di Artifact Registry e skaffold

Ora il cluster Kubernetes è pronto per il deployment. Poi ci prepariamo per un Container Registry per il push e il deployment dei container. Per questi passaggi, dobbiamo configurare un Artifact Registry (GAR) e uno skaffold per utilizzarlo.

Configurazione di Artifact Registry

Vai al menu di "Artifact Registry". e premi il pulsante ABILITA.

45e384b87f7cf0db.png

Dopo qualche istante, vedrai il browser del repository di GAR. Fai clic sul pulsante "CREA REPOSITORY" e inserisci il nome del repository.

d6a70f4cb4ebcbe3.png

In questo codelab, assegno il nome trace-codelab al nuovo repository. Il formato dell'artefatto è "Docker" e il tipo di località è "Region". Scegli la regione vicina a quella impostata per la zona predefinita di Google Compute Engine. Ad esempio, per questo esempio è stato scelto "us-central1-f" in alto, quindi qui scegliamo "us-central1 (Iowa)". Poi fai clic sul pulsante "CREA" .

9c2d1ce65258ef70.png

Ora vedi "trace-codelab" sul browser del repository.

7a3c1f47346bea15.png

Torneremo qui più tardi per controllare il percorso del registro.

Configurazione di Skaffold

Skaffold è uno strumento molto utile per la creazione di microservizi in esecuzione su Kubernetes. Gestisce il flusso di lavoro di creazione, push e deployment di container di applicazioni con un piccolo insieme di comandi. Per impostazione predefinita, Skaffold utilizza Docker Registry come Container Registry, quindi devi configurare skaffold in modo che riconosca GAR al momento del push dei container.

Apri di nuovo Cloud Shell e verifica che skaffold sia installato. Cloud Shell installa skaffold nell'ambiente per impostazione predefinita. Esegui questo comando e visualizza la versione di skaffold.

skaffold version

Output comando

v1.38.0

Ora puoi registrare il repository predefinito per l'utilizzo di skaffold. Per ottenere il percorso del registro, vai alla dashboard di Artifact Registry e fai clic sul nome del repository che hai appena configurato nel passaggio precedente.

7a3c1f47346bea15.png

Nella parte superiore della pagina verranno visualizzate le tracce dei breadcrumb. Fai clic sull'icona e157b1359c3edc06.png per copiare il percorso del Registro di sistema negli appunti.

e0f2ae2144880b8b.png

Facendo clic sul pulsante Copia, viene visualizzata la finestra di dialogo nella parte inferiore del browser con un messaggio simile al seguente:

&quot;us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab&quot; è stato copiato

Torna a Cloud Shell. Esegui il comando skaffold config set default-repo con il valore che hai appena copiato dalla dashboard.

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

Output comando

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

Inoltre, devi configurare il registro in base alla configurazione Docker. Esegui questo comando:

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

Output comando

{
  "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

Ora puoi iniziare il passaggio successivo per configurare un container Kubernetes su GKE.

Riepilogo

In questo passaggio devi configurare il tuo ambiente codelab:

  • Configura Cloud Shell
  • Creazione di un repository Artifact Registry per il Container Registry
  • Configurare skaffold per utilizzare Container Registry
  • Creazione di un cluster Kubernetes in cui vengono eseguiti i microservizi del codelab

A seguire

Nel passaggio successivo, implementerai l'agente profiler continuo nel servizio server.

3. Crea ed esegui il push e il deployment dei microservizi

Scarica il materiale del codelab

Nel passaggio precedente, abbiamo configurato tutti i prerequisiti per questo codelab. Ora è tutto pronto per eseguire interi microservizi sopra i microservizi. Il materiale del codelab è ospitato su GitHub, quindi scaricalo nell'ambiente Cloud Shell con il seguente comando git.

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

La struttura di directory del progetto è la seguente:

.
├── 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
  • manifest: i file manifest di Kubernetes
  • proto: definizione proto per la comunicazione tra client e server
  • src: directory per il codice sorgente di ciascun servizio
  • skaffold.yaml: file di configurazione per skaffold

In questo codelab, aggiornerai il codice sorgente che si trova nella cartella step4. Puoi anche fare riferimento al codice sorgente nelle cartelle step[1-6] per conoscere le modifiche dall'inizio. (La Parte 1 copre i passaggi da 0 a 4, mentre la Parte 2 copre i punti 5 e 6)

Esegui comando skaffold

Infine, sei pronto per creare, eseguire il push e il deployment di interi contenuti nel cluster Kubernetes che hai appena creato. Sembra che contenga più passaggi, ma quello effettivo è skaffold che fa tutto per te. Proviamo con questo comando:

cd step4
skaffold dev

Non appena esegui il comando, viene visualizzato l'output di log di docker build e puoi confermare che il push è stato eseguito correttamente al registro.

Output comando

...
---> 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

Dopo il push di tutti i container di servizio, i deployment di Kubernetes vengono avviati automaticamente.

Output comando

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

Dopo il deployment, vedrai i log effettivi dell'applicazione emessi nello stdout in ogni container, in questo modo:

Output comando

[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

Tieni presente che, a questo punto, desideri visualizzare tutti i messaggi del server. Ok, finalmente puoi iniziare a dotare la tua applicazione di OpenTelemetry per il tracciamento distribuito dei servizi.

Prima di iniziare a utilizzare gli strumenti per il servizio, arresta il cluster con Ctrl-C.

Output comando

...
[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

Riepilogo

In questo passaggio, hai preparato il materiale del codelab nel tuo ambiente e hai confermato che skaffold funziona come previsto.

A seguire

Nel passaggio successivo modificherai il codice sorgente del servizio loadgen per instrumentare le informazioni di traccia.

4. Strumentazione dell'agente Cloud Profiler

Concetto di profilazione continua

Prima di spiegare il concetto di profilazione continua, dobbiamo capire anzitutto quello della profilazione. La profilazione è uno dei modi per analizzare l'applicazione in modo dinamico (analisi del programma dinamico) e di solito viene eseguita durante lo sviluppo dell'applicazione nel processo di test di carico e così via. Si tratta di una singola attività per misurare le metriche di sistema, come l'utilizzo di CPU e memoria, durante un periodo specifico. Dopo aver raccolto i dati del profilo, gli sviluppatori li analizzano a partire dal codice.

La profilazione continua è l'approccio esteso della normale profilazione: esegue periodicamente profili di finestre brevi sull'applicazione a lunga esecuzione e raccoglie una serie di dati di profilo. Quindi genera automaticamente l'analisi statistica in base a un determinato attributo dell'applicazione, come numero di versione, zona di deployment, ora di misurazione e così via. Troverai ulteriori dettagli sul concetto nella nostra documentazione.

Poiché la destinazione è un'applicazione in esecuzione, esiste un modo per raccogliere periodicamente i dati del profilo e inviarli a un backend che post-elabora i dati statistici. Si tratta dell'agente Cloud Profiler che a breve lo incorporerai nel servizio server.

Incorpora l'agente Cloud Profiler

Apri l'editor di Cloud Shell premendo il pulsante 776a11bfb2122549.pngin alto a destra in Cloud Shell. Apri step4/src/server/main.go da Explorer nel riquadro a sinistra e trova la funzione principale.

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)
        }
}

Nella funzione main, puoi vedere un codice di configurazione per OpenTelemetry e gRPC, che è stato eseguito nella parte 1 del codelab. Ora aggiungerai la strumentazione per l'agente Cloud Profiler qui. Come per initTracer(), puoi scrivere una funzione chiamata initProfiler() per migliorare la leggibilità.

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)
        }
}

Diamo un'occhiata più da vicino alle opzioni specificate nell'oggetto profiler.Config{}.

  • Servizio: il nome del servizio che puoi selezionare e attivare nella dashboard del profiler
  • ServiceVersion: il nome della versione del servizio. Puoi confrontare i set di dati del profilo in base a questo valore.
  • NoHeapProfiling: disabilita la profilazione del consumo di memoria
  • NoAllocProfiling: disabilita la profilazione dell'allocazione della memoria
  • NoGoroutineProfiling: disattiva la profilazione goroutine
  • NoCPUProfiling: disabilita la profilazione CPU

In questo codelab, attiviamo solo la profilazione della CPU.

A questo punto, devi semplicemente richiamare questa funzione nella funzione main. Assicurati di importare il pacchetto Cloud Profiler nel blocco di importazione.

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
        ...
}

Tieni presente che stai chiamando la funzione initProfiler() con la parola chiave go. Perché profiler.Start() si blocca, quindi devi eseguirlo in un'altra goroutine. Ora è pronto per la creazione. Assicurati di eseguire go mod tidy prima del deployment.

go mod tidy

Ora esegui il deployment del cluster con il nuovo servizio server.

skaffold dev

In genere sono necessari un paio di minuti per visualizzare il grafico a fiamme su Cloud Profiler. Digita "profiler" nella casella di ricerca in alto e fai clic sull'icona di Profiler.

3d8ca8a64b267a40.png

Quindi, vedrai il seguente grafico a fiamme.

7f80797dddc0128d.png

Riepilogo

In questo passaggio, hai incorporato l'agente Cloud Profiler nel servizio server e hai confermato che genera un grafico a fiamme.

A seguire

Nel passaggio successivo, indagherai sulla causa del collo di bottiglia nell'applicazione con il grafico a fiamme.

5. Analizza il grafico a fiamme di Cloud Profiler

Cos'è il grafico a fiamma?

Il grafico a fiamme è uno dei modi per visualizzare i dati del profilo. Per una spiegazione dettagliata, fai riferimento al nostro documento, ma il breve riepilogo è:

  • Ogni barra esprime la chiamata al metodo/funzione nell'applicazione
  • La direzione verticale è lo stack di chiamate, lo stack di chiamate cresce dall'alto verso il basso
  • La direzione orizzontale indica l'utilizzo delle risorse; più a lungo, meglio è.

Detto questo, diamo un'occhiata al grafico della fiamma ottenuto.

7f80797dddc0128d.png

Analisi del grafico delle fiamme

Nella sezione precedente, hai appreso che ogni barra nel grafico a fiamme esprime la chiamata di funzione/metodo e la sua lunghezza indica l'utilizzo di risorse nella funzione/nel metodo. Il grafico a fiamme di Cloud Profiler ordina la barra in ordine decrescente o la lunghezza da sinistra a destra. Puoi iniziare a guardare dall'angolo in alto a sinistra del grafico.

6d90760c6c1183cd.png

Nel nostro caso, è chiaro che grpc.(*Server).serveStreams.func1.2 sta consumando la maggior parte del tempo di CPU e, esaminando lo stack di chiamate dall'alto verso il basso, trascorre la maggior parte del tempo in main.(*serverService).GetMatchCount, che è il gestore del server gRPC nel servizio server.

In GetMatchCount, puoi vedere una serie di funzioni regexp: regexp.MatchString e regexp.Compile. Provengono dal pacchetto standard, vale a dire che devono essere ben testati da molti punti di vista, tra cui le prestazioni. Tuttavia, il risultato mostra che l'utilizzo delle risorse di tempo di CPU è elevato in regexp.MatchString e regexp.Compile. Alla luce di questi fatti, il presupposto qui è che l'utilizzo di regexp.MatchString abbia qualcosa a che fare con problemi di prestazioni. Leggiamo quindi il codice sorgente in cui viene utilizzata la funzione.

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
}

Questo è il luogo in cui viene chiamato regexp.MatchString. Leggendo il codice sorgente, puoi notare che la funzione viene chiamata all'interno del for-loop nidificato. Pertanto, l'utilizzo di questa funzione potrebbe non essere corretto. Cerchiamo il GoDoc di regexp.

80b8a4ba1931ff7b.png

Secondo il documento, regexp.MatchString compila il pattern dell'espressione regolare in ogni chiamata. La causa dell'elevato consumo di risorse è simile a questa.

Riepilogo

In questo passaggio, hai effettuato l'ipotesi della causa del consumo di risorse analizzando il grafico a fiamme.

A seguire

Nel passaggio successivo, aggiornerai il codice sorgente del servizio server e confermerai la modifica rispetto alla versione 1.0.0.

6. Aggiorna il codice sorgente e confronta i grafici a fiamma

Aggiornare il codice sorgente

Nel passaggio precedente, hai dato per scontato che l'utilizzo di regexp.MatchString abbia a che fare con il grande consumo di risorse. Risolviamo il problema. Apri il codice e modifica un po' quella parte.

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
}

Come puoi vedere, ora il processo di compilazione di pattern regexp viene estratto da regexp.MatchString e rimosso dal loop for nidificato.

Prima di eseguire il deployment di questo codice, assicurati di aggiornare la stringa di versione nella funzione 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)
        }
}

Vediamo come funziona. Eseguire il deployment del cluster con il comando skaffold.

skaffold dev

Dopo qualche istante, ricarica la dashboard di Cloud Profiler per vedere com'è.

283cfcd4c13716ad.png

Accertati di modificare la versione in "1.1.0" in modo da visualizzare solo i profili della versione 1.1.0. Come si può vedere, la lunghezza della barra di GetMatchCount si è ridotta e il rapporto di utilizzo del tempo di CPU (ossia, la barra si è ridotta).

e3a1456b4aada9a5.png

Non solo osservando il grafico a fiamme di una singola versione, puoi anche confrontare le differenze tra due versioni.

841dec77d8ba5595.png

Modificare il valore di "Confronta con" dall'elenco a discesa su "Versione" e modifica il valore di "Versione confrontata" "1.0.0", la versione originale.

5553844292d6a537.png

Vedrai questo tipo di grafico a fiamme. La forma del grafico è la stessa della versione 1.1.0, ma la colorazione è diversa. In modalità di confronto, il significato del colore è:

  • Blu: il valore (consumo di risorse) ridotto
  • Arancione: il valore (consumo di risorse) acquisito.
  • Grigio: neutro

Data la legenda, esaminiamo più da vicino la funzione. Se fai clic sulla barra su cui vuoi aumentare lo zoom, puoi visualizzare ulteriori dettagli all'interno della pila. Fai clic sulla barra main.(*serverService).GetMatchCount. Puoi anche passare il mouse sopra la barra per visualizzare i dettagli del confronto.

ca08d942dc1e2502.png

Dice che il tempo totale di CPU è ridotto da 5,26 secondi a 2,88 secondi (il totale è 10 secondi = finestra di campionamento). È un enorme miglioramento.

Ora puoi migliorare le prestazioni della tua applicazione analizzando i dati del profilo.

Riepilogo

In questo passaggio, hai apportato una modifica al servizio server e hai confermato il miglioramento nella modalità di confronto di Cloud Profiler.

A seguire

Nel passaggio successivo, aggiornerai il codice sorgente del servizio server e confermerai la modifica rispetto alla versione 1.0.0.

7. Passaggio aggiuntivo: conferma il miglioramento della struttura a cascata Trace

Differenza tra traccia distribuita e profilazione continua

Nella parte 1 del codelab, hai confermato di poter capire il servizio collo di bottiglia tra i microservizi per un percorso di richiesta e che non riuscivi a capire la causa esatta del collo di bottiglia nel servizio specifico. In questa parte 2 del codelab, hai imparato che la profilazione continua ti consente di identificare il collo di bottiglia all'interno del singolo servizio dagli stack di chiamate.

In questo passaggio, esaminiamo il grafico a cascata della traccia distribuita (Cloud Trace) e vediamo la differenza rispetto alla profilazione continua.

Questo grafico a cascata è una delle tracce con la query "love". Prende circa 6,7 s (6700 ms) in totale.

e2b7dec25926ee51.png

Questo dopo il miglioramento della stessa query. Come hai detto, la latenza totale è ora di 1,5 secondi (1500 ms), il che è un enorme miglioramento rispetto all'implementazione precedente.

feeb7207f36c7e5e.png

Il punto importante in questo caso è che nel grafico a cascata delle tracce distribuite, le informazioni sullo stack di chiamate non sono disponibili a meno che lo strumento non si trovi ovunque. Inoltre, le tracce distribuite si concentrano solo sulla latenza tra i servizi, mentre la profilazione continua si concentra sulle risorse informatiche (CPU, memoria, thread del sistema operativo) di un singolo servizio.

In un altro aspetto, la traccia distribuita è la base degli eventi, mentre il profilo continuo è statistico. Ogni traccia ha un grafico della latenza diverso e hai bisogno di un formato diverso, come la distribuzione, per ottenere la tendenza delle variazioni di latenza.

Riepilogo

In questo passaggio, hai controllato la differenza tra traccia distribuita e profilazione continua.

8. Complimenti

Hai creato tracce distribuite con OpenTelemery e hai confermato le latenze delle richieste nel microservizio su Google Cloud Trace.

Per esercizi estesi, puoi provare autonomamente i seguenti argomenti.

  • L'implementazione attuale invia tutti gli intervalli generati dal controllo di integrità. (grpc.health.v1.Health/Check) Come si filtrano questi intervalli da Cloud Traces? Il suggerimento è qui.
  • Correla i log eventi con gli intervalli e osserva come funziona su Google Cloud Trace e Google Cloud Logging. Il suggerimento è qui.
  • Sostituisci qualche servizio con quello in un'altra lingua e prova a instrumentarlo con OpenTelemetry per quella lingua.

Inoltre, se dopo questo passaggio vuoi saperne di più sul profiler, passa alla parte 2. In questo caso, puoi saltare la sezione dedicata alla pulizia.

Pulizia

Dopo questo codelab, arresta il cluster Kubernetes e assicurati di eliminare il progetto in modo da non ricevere addebiti imprevisti su Google Kubernetes Engine, Google Cloud Trace e Google Artifact Registry.

Elimina innanzitutto il cluster. Se esegui il cluster con skaffold dev, devi solo premere Ctrl-C. Se esegui il cluster con skaffold run, esegui questo comando:

skaffold delete

Output comando

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

Dopo aver eliminato il cluster, nel riquadro di menu seleziona "IAM e Amministratore" &gt; "Impostazioni", quindi fai clic su "SPEGNI" .

45aa37b7d5e1ddd1.png

Quindi inserisci l'ID progetto (non il nome del progetto) nel modulo della finestra di dialogo e conferma l'arresto.