Zwiększanie wydajności aplikacji w Go (część 2: narzędzie do profilowania)

1. Wprowadzenie

e0509e8a07ad5537.png

Ostatnia aktualizacja: 14.07.2022 r.

Dostrzegalność aplikacji

Dostrzegalność i ciągłe profilowanie

Dostrzegalność to termin opisujący atrybut układu. System z obserwowalnością umożliwia zespołom aktywne debugowanie systemu. W tym kontekście 3 filary dostrzegalności: logi, wskaźniki i logi czasu to podstawowe narzędzia umożliwiające systemowi uzyskiwanie dostrzegalności.

Oprócz 3 filarów obserwowalności kolejną kluczową funkcją jest ciągłe profilowanie, które poszerza bazę użytkowników w branży. Narzędzie Cloud Profiler jest jednym z pierwszych i zapewnia łatwy interfejs do analizowania danych o wydajności w zbiorach wywołań aplikacji.

Ćwiczenie w Codelabs jest częścią 2 tej serii i opisuje konfigurowanie agenta do profilowania ciągłego. Część 1 dotyczy śledzenia rozproszonego za pomocą OpenTelemetry i Cloud Trace, a w części 1 dowiesz się więcej o tym, jak lepiej identyfikować wąskie gardła mikroserwisów.

Co utworzysz

W tym ćwiczeniu w Codelabs dowiesz się, jak wdrożyć agenta ciągłego profilowania w usłudze serwera aplikacji Szekspira. (inaczej Shakesapp), która działa w klastrze Google Kubernetes Engine. Architektura Shakesapp:

44e243182ced442f.png

  • Loadgen wysyła ciąg zapytania do klienta w HTTP
  • Klienci przekazują zapytanie od generatora obciążenia do serwera w gRPC
  • Serwer akceptuje zapytanie od klienta, pobiera wszystkie dzieła Szekspira w formacie tekstowym z Google Cloud Storage, wyszukuje wiersze zawierające zapytanie i zwraca numer wiersza dopasowanego do klienta.

W części 1. stwierdziliśmy, że wąskie gardło występuje gdzieś w usłudze serwera, ale nie udało się nam ustalić dokładnej przyczyny.

Czego się nauczysz

  • Jak umieścić agenta profilującego
  • Jak zbadać szyję butelki w Cloud Profiler

Dzięki temu ćwiczeniu w Codelabs dowiesz się, jak wdrożyć w aplikacji agenta ciągłego profilującego.

Czego potrzebujesz

  • Podstawowa znajomość języka Go
  • podstawową wiedzę o Kubernetes;

2. Konfiguracja i wymagania

Samodzielne konfigurowanie środowiska

Jeśli nie masz jeszcze konta Google (w Gmailu lub Google Apps), musisz je utworzyć. Zaloguj się w konsoli Google Cloud Platform (console.cloud.google.com) i utwórz nowy projekt.

Jeśli masz już projekt, kliknij menu wyboru projektu w lewym górnym rogu konsoli:

7a32e5469db69e9.png

i kliknij przycisk „NOWY PROJEKT” w wyświetlonym oknie, aby utworzyć nowy projekt:

7136b3ee36ebaf89.png

Jeśli nie masz jeszcze projektu, zobaczysz takie okno dialogowe umożliwiające utworzenie pierwszego:

870a3cbd6541ee86.png

W kolejnym oknie tworzenia projektu możesz wpisać szczegóły nowego projektu:

affdc444517ba805.png

Zapamiętaj identyfikator projektu, który jest unikalną nazwą we wszystkich projektach Google Cloud (podane powyżej imię i nazwisko jest już zajęte i nie będzie działać). W dalszej części tego ćwiczenia z programowania będzie on określany jako PROJECT_ID.

Następnie musisz włączyć płatności w Developers Console, aby korzystać z zasobów Google Cloud i włączyć Cloud Trace API.

15d0ef27a8fbab27.png

Wykonanie tych ćwiczeń w programie nie powinno kosztować więcej niż kilka dolarów, ale może być droższe, jeśli zdecydujesz się na więcej zasobów lub pozostawisz je włączone (patrz sekcja „Czyszczenie” na końcu tego dokumentu). Ceny Google Cloud Trace, Google Kubernetes Engine i Google Artifact Registry są podane w oficjalnej dokumentacji.

Nowi użytkownicy Google Cloud Platform mogą skorzystać z bezpłatnej wersji próbnej o wartości 300 USD, dzięki czemu te ćwiczenia z programowania będą całkowicie bezpłatne.

Konfiguracja Google Cloud Shell

Usługi Google Cloud i Google Cloud Trace można obsługiwać zdalnie z poziomu laptopa, ale w tym ćwiczeniu z programowania wykorzystamy Google Cloud Shell – środowisko wiersza poleceń działające w chmurze.

Ta maszyna wirtualna oparta na Debianie zawiera wszystkie potrzebne narzędzia dla programistów. Zawiera stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie poprawia wydajność sieci i uwierzytelnianie. Oznacza to, że do tego ćwiczenia będziesz potrzebować tylko przeglądarki (tak, działa to na Chromebooku).

Aby aktywować Cloud Shell z poziomu konsoli Cloud, kliknij Aktywuj Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (udostępnienie środowiska i połączenie z nim powinno zająć tylko chwilę).

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

Zrzut ekranu 2017-06-14 o 10.13.43 PM.png

Po nawiązaniu połączenia z Cloud Shell powinno pojawić się potwierdzenie, że użytkownik jest już uwierzytelniony, a projekt jest już ustawiony na PROJECT_ID.

gcloud auth list

Dane wyjściowe polecenia

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

Wynik polecenia

[core]
project = <PROJECT_ID>

Jeśli z jakiegoś powodu projekt nie jest skonfigurowany, uruchom po prostu to polecenie:

gcloud config set project <PROJECT_ID>

Szukasz urządzenia PROJECT_ID? Sprawdź, jakiego identyfikatora użyto w procesie konfiguracji, lub odszukaj go w panelu Cloud Console:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Cloud Shell domyślnie ustawia też niektóre zmienne środowiskowe, co może być przydatne podczas wykonywania kolejnych poleceń.

echo $GOOGLE_CLOUD_PROJECT

Dane wyjściowe polecenia

<PROJECT_ID>

Na koniec ustaw domyślną strefę i konfigurację projektu.

gcloud config set compute/zone us-central1-f

Możesz wybrać różne strefy. Więcej informacji znajdziesz w artykule Regiony i Strefy.

Skonfiguruj język

W tym ćwiczeniu w Codelabs cały kod źródłowy używamy w języku Go. Uruchom to polecenie w Cloud Shell i sprawdź, czy wersja Go to 1.17 lub nowsza.

go version

Dane wyjściowe polecenia

go version go1.18.3 linux/amd64

Konfigurowanie klastra Google Kubernetes

W tym praktycznym laboratorium uruchomisz klaster mikroserwisów w Google Kubernetes Engine (GKE). Proces w tym ćwiczeniu obejmuje te czynności:

  1. Pobierz projekt bazowy do Cloud Shell
  2. Tworzenie mikroserwisów w kontenerach
  3. Przesyłanie kontenerów do Google Artifact Registry (GAR)
  4. Wdrażanie kontenerów w GKE
  5. zmodyfikować kod źródłowy usług w celu przeprowadzenia instrumentacji śledzonej,
  6. Przejdź do kroku 2

Włącz Kubernetes Engine

Najpierw skonfigurowaliśmy klaster Kubernetes, w którym aplikacja Shakesapp działa w GKE, więc musimy włączyć GKE. Przejdź do menu „Kubernetes Engine” i naciśnij przycisk WŁĄCZ.

548cfd95bc6d344d.png

Teraz możesz utworzyć klaster Kubernetes.

Tworzenie klastra Kubernetes

Aby utworzyć klaster Kubernetes, uruchom w Cloud Shell to polecenie: Potwierdź, że wartość strefy znajduje się w regionie, który będzie używany do tworzenia repozytorium Artifact Registry. Zmień wartość strefy us-central1-f, jeśli region repozytorium nie obejmuje danej strefy.

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

Dane wyjściowe polecenia

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

Konfiguracja Artifact Registry i skaffolda

Klaster Kubernetes jest teraz gotowy do wdrożenia. W kolejnym kroku przygotujemy rejestr kontenerów do wypychania i wdrażania kontenerów. Aby wykonać te czynności, musimy skonfigurować Artifact Registry (GAR) i skaffold, aby z niego korzystać.

Konfiguracja Artifact Registry

Przejdź do menu „Artifact Registry” i naciśnij przycisk WŁĄCZ.

45e384b87f7cf0db.png

Po chwili zobaczysz przeglądarkę repozytoriów GAR. Kliknij „UTWÓRZ REPOZYTORIUM”. i wpisz nazwę repozytorium.

d6a70f4cb4ebcbe3.png

W ramach tego ćwiczenia w Codelabs nadam nowemu repozytorium nazwę trace-codelab. Format artefaktu to „Docker” a typ lokalizacji to „Region”. Wybierz region bliski temu, który został ustawiony dla domyślnej strefy Google Compute Engine. W tym przykładzie wybrano „us-central1-f”. powyżej, więc tutaj wybieramy „us-central1 (Iowa)”. Następnie kliknij przycisk „Utwórz”.

9c2d1ce65258ef70.png

Teraz widzisz „trace-codelab”. w przeglądarce repozytoriów.

7a3c1f47346bea15.png

Wrócimy tu później, aby sprawdzić ścieżkę rejestru.

Konfiguracja Skaffold

Skaffold to przydatne narzędzie podczas tworzenia mikroserwisów działających w Kubernetes. Obsługuje przepływ pracy związany z tworzeniem, wypychaniem i wdrażaniem kontenerów aplikacji za pomocą niewielkiego zestawu poleceń. Skaffold domyślnie używa Docker Registry jako rejestru kontenerów, dlatego musisz skonfigurować skaffold tak, aby rozpoznawał GAR przy przekazywaniu kontenerów do kontenerów.

Ponownie otwórz Cloud Shell i sprawdź, czy narzędzie skaffold jest zainstalowane. (Cloud Shell domyślnie instaluje skaffold w środowisku). Uruchom to polecenie, aby sprawdzić wersję skaffold.

skaffold version

Dane wyjściowe polecenia

v1.38.0

Teraz możesz zarejestrować domyślne repozytorium, z którego będzie korzystać skaffold. Aby uzyskać ścieżkę rejestru, otwórz panel Artifact Registry i kliknij nazwę repozytorium skonfigurowanego w poprzednim kroku.

7a3c1f47346bea15.png

Następnie u góry strony zobaczysz elementy menu nawigacyjnego. Kliknij ikonę e157b1359c3edc06.png, aby skopiować ścieżkę rejestru do schowka.

e0f2ae2144880b8b.png

Po kliknięciu przycisku kopiowania na dole okna przeglądarki pojawi się okno z komunikatem takim jak:

&quot;us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab&quot; została skopiowana

Wróć do Cloud Shell. Uruchom polecenie skaffold config set default-repo z wartością skopiowaną właśnie z panelu.

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

Dane wyjściowe polecenia

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

Musisz też skonfigurować rejestr pod kątem konfiguracji Dockera. Uruchom to polecenie:

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

Dane wyjściowe polecenia

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

Teraz możesz przejść do następnego kroku konfigurowania kontenera Kubernetes w GKE.

Podsumowanie

W tym kroku skonfigurujesz środowisko ćwiczeń z programowania:

  • Konfigurowanie Cloud Shell
  • Utworzono repozytorium Artifact Registry dla rejestru kontenerów
  • Skonfiguruj skaffold, aby korzystać z rejestru kontenerów
  • Utworzono klaster Kubernetes, w którym działają mikroserwisy ćwiczeń z programowania

Dalsze czynności

W następnym kroku przyłączysz agenta ciągłego profilowania w usłudze serwera.

3. Tworzenie, przekazywanie i wdrażanie mikroserwisów

Pobierz materiały z ćwiczeń z programowania

W poprzednim kroku skonfigurowaliśmy wszystkie wymagania wstępne tego ćwiczenia z programowania. Teraz możesz uruchomić na nich całe mikroserwisy. Materiały do ćwiczeń z programowania są hostowane na GitHubie, więc pobierz je do środowiska Cloud Shell za pomocą tego polecenia git.

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

Struktura katalogów projektu jest następująca:

.
├── 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
  • pliki manifestu: pliki manifestu Kubernetes.
  • proto: definicja protokołu dla komunikacji między klientem a serwerem
  • src: katalogi kodu źródłowego poszczególnych usług
  • skaffold.yaml: plik konfiguracji dla skaffold

W ramach tego ćwiczenia w Codelabs zaktualizujesz kod źródłowy znajdujący się w folderze step4. Możesz też zapoznać się z kodem źródłowym w step[1-6] folderach, aby uzyskać informacje o zmianach od początku. (część 1 obejmuje kroki 0–4, a część 2 – kroki 5–6)

Uruchom polecenie skaffold

Teraz możesz skompilować, przesłać i wdrożyć wszystkie treści do utworzonego właśnie klastra Kubernetes. Wygląda na to, że składa się z wielu kroków, ale w rzeczywistości skaffold robi wszystko za Ciebie. Spróbujmy to zrobić za pomocą tego polecenia:

cd step4
skaffold dev

Natychmiast po uruchomieniu polecenia zostaną wyświetlone dane wyjściowe dziennika docker build i będziesz mieć pewność, że zostały one przekazane do rejestru.

Dane wyjściowe polecenia

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

Po przekazaniu wszystkich kontenerów usługi wdrożenia Kubernetes uruchamiają się automatycznie.

Dane wyjściowe polecenia

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

Po wdrożeniu zobaczysz rzeczywiste logi aplikacji wysyłane do stdout w poszczególnych kontenerach w następujący sposób:

Dane wyjściowe polecenia

[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

Na tym etapie chcesz wyświetlić wszystkie wiadomości z serwera. Właśnie możesz zacząć instrumentować aplikację za pomocą OpenTelemetry w celu rozproszonego śledzenia usług.

Zanim zaczniesz instrumentować usługę, wyłącz klaster, naciskając klawisze Ctrl + C.

Wynik polecenia

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

Podsumowanie

Na tym etapie masz już przygotowane materiały z ćwiczeń z programowania w swoim środowisku oraz potwierdzone uruchomienia skaffold zgodnie z oczekiwaniami.

Dalsze czynności

W następnym kroku zmodyfikujesz kod źródłowy usługi loadgen, aby dostosować dane śledzenia.

4. Instrumentacja agenta Cloud Profiler

Pojęcie profilowania ciągłego

Zanim objaśnimy pojęcie profilowania ciągłego, musimy zrozumieć, czym jest profilowanie. Profilowanie to jeden ze sposobów dynamicznej analizy aplikacji (dynamiczna analiza programu). Jest ono zwykle wykonywane podczas tworzenia aplikacji w ramach testowania obciążeniowego itp. To jednorazowe działanie mające na celu pomiar wskaźników systemowych, takich jak wykorzystanie procesora i pamięci, w wybranym okresie. Po zebraniu danych do profilu deweloperzy analizują je bezpośrednio w kodzie.

Profilowanie ciągłe to rozszerzona metoda profilowania normalnego: okresowo uruchamia profile krótkich okien w przypadku długotrwałej aplikacji i gromadzi sporą ilość danych profilowych. Następnie automatycznie generuje analizę statystyczną na podstawie określonego atrybutu aplikacji, takiego jak numer wersji, strefa wdrożenia, czas pomiaru itd. Więcej informacji na ten temat znajdziesz w naszej dokumentacji.

Ponieważ celem jest uruchomiona aplikacja, można okresowo gromadzić dane profilowe i wysyłać je do backendu, który następnie przetwarza dane statystyczne. To jest agent Cloud Profiler, który wkrótce zostanie wbudowany w usługę serwera.

Umieszczanie agenta Cloud Profiler

Otwórz edytor Cloud Shell, naciskając przycisk 776a11bfb2122549.png w prawym górnym rogu Cloud Shell. Otwórz step4/src/server/main.go w eksploratorze w panelu po lewej stronie i znajdź funkcję główną.

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

W funkcji main jest widoczny kod konfiguracji dla OpenTelemetry i gRPC, który został wykonany w części 1 ćwiczenia w Codelabs. Teraz dodasz instrumentację dla agenta Cloud Profiler. Podobnie jak w przypadku funkcji initTracer(), możesz napisać funkcję o nazwie initProfiler(), aby uzyskać czytelność.

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

Przyjrzyjmy się opcjom określonym w obiekcie profiler.Config{}.

  • Usługa: nazwa usługi, którą możesz wybrać i włączyć w panelu narzędzia do profilowania.
  • ServiceVersion: nazwa wersji usługi. Na podstawie tej wartości możesz porównywać zbiory danych profilu.
  • NoHeapProfiling: wyłącz profilowanie wykorzystania pamięci
  • NoAllocProfiling: wyłącza profilowanie alokacji pamięci.
  • NoGoroutineProfiling: wyłączanie profilowania goroutine
  • NoCPUProfiling: wyłącz profilowanie procesora

W tym ćwiczeniu w Codelabs włączamy tylko profilowanie procesora.

Teraz musisz wywołać tę funkcję w funkcji main. Pamiętaj, aby zaimportować pakiet Cloud Profiler do bloku importu.

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

Pamiętaj, że wywołujesz funkcję initProfiler() za pomocą słowa kluczowego go. Ponieważ zasada profiler.Start() blokuje działanie, trzeba je uruchomić w innym kodzie. Możesz go teraz utworzyć. Pamiętaj, aby przed wdrożeniem uruchomić go mod tidy.

go mod tidy

Teraz wdróż klaster z nową usługą serwera.

skaffold dev

Wykres płomienia w Cloud Profiler pojawia się zwykle po kilku minutach. Wpisz „profiler” w polu wyszukiwania u góry i kliknij ikonę programu profilującego.

3d8ca8a64b267a40.png

Następnie zobaczysz następujący wykres płomieni.

7f80797dddc0128d.png

Podsumowanie

W tym kroku dodałaś lub dodałeś agenta Cloud Profiler do usługi serwera i potwierdził(a)eś, że generuje on wykres płomienia.

Dalsze czynności

W następnym kroku za pomocą wykresu płomieniowego zbadasz przyczynę wąskiego gardła w aplikacji.

5. Analizowanie wykresu płomieniowego Cloud Profiler

Czym jest wykres płomieniowy?

Wykres płomieniowy to jeden ze sposobów wizualizacji danych profilowych. Szczegółowe wyjaśnienie można znaleźć w naszym dokumencie. Jego krótkie podsumowanie to:

  • Każdy słupek opisuje wywołanie metody/funkcji w aplikacji.
  • Kierunek pionowy to stos wywołań. stos wywołań rośnie od góry do dołu
  • Kierunek poziomy to wykorzystanie zasobów. im dłuższe, tym gorsze.

Spójrzmy na uzyskany wykres płomieniowy.

7f80797dddc0128d.png

Analiza wykresu płomieniowego

W poprzedniej sekcji wiemy, że każdy słupek na wykresie płomieniowym wyraża wywołanie funkcji/metody, a jego długość oznacza wykorzystanie zasobów przez funkcję/metodę. Wykres płomienia w Cloud Profiler sortuje słupek w porządku malejącym lub według długości od lewej do prawej. Możesz zacząć od lewego górnego rogu wykresu.

6d90760c6c1183cd.png

W naszym przypadku funkcja grpc.(*Server).serveStreams.func1.2 zużywa najwięcej czasu procesora. Przeglądając od góry do dołu stos wywołań, widać, że większość czasu jest poświęcana na funkcję main.(*serverService).GetMatchCount, która jest modułem obsługi serwera gRPC w usłudze serwera.

W sekcji GetMatchCount znajdziesz szereg funkcji regexp: regexp.MatchString i regexp.Compile. Pochodzą one z pakietu standardowego: oznacza to, że powinny być dobrze przetestowane pod wieloma względami, w tym także pod względem skuteczności. Wynik pokazuje jednak, że wykorzystanie zasobów czasu procesora jest wysokie w regexp.MatchString i regexp.Compile. Biorąc pod uwagę te fakty, zakładamy, że użycie właściwości regexp.MatchString ma związek z problemami z wydajnością. Spójrzmy więc na kod źródłowy, w którym jest używana ta funkcja.

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
}

To miejsce, w którym nazywa się regexp.MatchString. Czytając kod źródłowy, możesz zauważyć, że funkcja jest wywoływana wewnątrz zagnieżdżonej pętli for. Dlatego jej użycie może być nieprawidłowe. Wyszukajmy obiekt GoDoc dotyczący elementu regexp.

80b8a4ba1931ff7b.png

Zgodnie z dokumentem w każdym wywołaniu funkcja regexp.MatchString kompiluje wzorzec wyrażenia regularnego. Tak więc przyczyna dużego zużycia zasobów brzmi tak.

Podsumowanie

W tym kroku przyjęliśmy założenie o przyczynie zużycia zasobów, analizując graf płomieniowy.

Dalsze czynności

W następnym kroku zaktualizujesz kod źródłowy usługi serwera i potwierdzisz zmianę z wersji 1.0.0.

6. Zaktualizować kod źródłowy i rozróżnić grafy płomieniowe

Aktualizowanie kodu źródłowego

W poprzednim kroku założyliśmy, że użycie funkcji regexp.MatchString ma związek z dużą ilością wykorzystywanych zasobów. Postarajmy się rozwiązać ten problem. Otwórz kod i trochę go zmień.

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
}

Jak widać, proces kompilacji wzorca wyrażeń regularnych jest teraz wyodrębniany z tabeli regexp.MatchString i wycofywany z zagnieżdżonej pętli „for”.

Zanim wdrożysz ten kod, pamiętaj o zaktualizowaniu ciągu znaków wersji w funkcji 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)
        }
}

Zobaczmy, jak to działa. Wdróż klaster za pomocą polecenia skaffold.

skaffold dev

Po chwili załaduj ponownie panel Cloud Profiler i sprawdź, jak to działa.

283cfcd4c13716ad.png

Pamiętaj, aby zmienić wersję na "1.1.0", tak aby widoczne były tylko profile z wersji 1.1.0. Jak widać, długość paska funkcji GetMatchCount się zmniejszyła, a współczynnik wykorzystania czasu procesora (czyli długość paska) się skrócił.

e3a1456b4aada9a5.png

Nie tylko patrząc na wykres płomieniowy pojedynczej wersji, możesz także porównać różnice między dwiema wersjami.

841dec77d8ba5595.png

Zmiana wartości opcji „Porównaj z” z listy „Wersja” i zmienić wartość „Porównywana wersja” do wersji oryginalnej „1.0.0”.

5553844292d6a537.png

Zobaczysz taki wykres płomieniowy. Kształt wykresu jest taki sam jak w wersji 1.1.0, ale kolorystyka jest inna. W trybie porównawczym kolory to:

  • Niebieski: wartość (zużycie zasobów) zmniejszona.
  • Pomarańczowy: wartość (wykorzystanie zasobów)
  • Szary: neutralny.

Mając na uwadze legendę, przyjrzyjmy się bliżej tej funkcji. Po kliknięciu paska, który chcesz powiększyć, zobaczysz więcej szczegółów w grupie. Kliknij pasek main.(*serverService).GetMatchCount. Najeżdżając kursorem na słupek, zobaczysz także szczegóły porównania.

ca08d942dc1e2502.png

Oznacza to, że łączny czas pracy procesora został zmniejszony z 5,26 s do 2,88 s (łącznie 10 s = okno próbkowania). To ogromna poprawa.

Teraz możesz zwiększyć wydajność swojej aplikacji dzięki analizie danych profilowych.

Podsumowanie

W tym kroku wprowadziliśmy zmianę w usłudze serwera i potwierdziliśmy poprawę w trybie porównania Cloud Profiler.

Dalsze czynności

W następnym kroku zaktualizujesz kod źródłowy usługi serwera i potwierdzisz zmianę z wersji 1.0.0.

7. Dodatkowy krok: sprawdź ulepszenie kaskady śledzenia

Różnica między logiem rozproszonym a profilowaniem ciągłym

W części 1 ćwiczeń z programowania udało Ci się potwierdzić, że jesteś w stanie znaleźć usługę wąskiego gardła we wszystkich mikroserwisach na ścieżce żądań i że nie jesteś w stanie ustalić dokładnej przyczyny wąskiego gardła w konkretnej usłudze. Z tego drugiego ćwiczenia w Codelab dowiesz się, że ciągłe profilowanie umożliwia identyfikowanie wąskich gardeł w pojedynczej usłudze na podstawie stertów wywołań.

W tym kroku przyjrzyjmy się wykresowi kaskadowemu na podstawie rozproszonego logu czasu (Cloud Trace) i zobaczmy różnicę w stosunku do profilowania ciągłego.

Ten wykres kaskadowy to jeden z śladów z zapytaniem „love”. Łącznie zajmuje to 6,7 s (6700 ms).

e2b7dec25926ee51.png

A tak wygląda ta sama wyszukiwarka po wprowadzeniu ulepszeń. Całkowity czas oczekiwania wynosi teraz 1, 5 s (1500 ms), co jest ogromnym usprawnieniem w porównaniu z poprzednią implementacją.

feeb7207f36c7e5e.png

Ważne jest, że na wykresie kaskadowym rozproszonej analizy informacji o zbiorze wywołań nie ma, chyba że masz włączone śledzenie w każdym miejscu. Ponadto rozproszone ścieżki koncentrują się tylko na opóźnieniu w usługach, podczas gdy ciągłe profilowanie koncentruje się na zasobach komputera (procesor, pamięć, wątki systemu operacyjnego) w pojedynczej usłudze.

W innym aspekcie rozproszony log czasu stanowi podstawę zdarzeń, a profil ciągły ma charakter statystyczny. Każdy log czasu ma inny wykres czasu oczekiwania. Aby poznać trend zmian czasu oczekiwania, potrzebujesz innego formatu, np. dystrybucji.

Podsumowanie

W tym kroku sprawdzisz różnicę między logiem rozproszonym a profilowaniem ciągłym.

8. Gratulacje

Udało Ci się utworzyć rozproszone logi czasu przy użyciu OpenTelemery i potwierdzone opóźnienia żądań w mikroserwisie w Google Cloud Trace.

W przypadku dłuższych ćwiczeń możesz samodzielnie wypróbować poniższe tematy.

  • Obecna implementacja wysyła wszystkie spany wygenerowane przez kontrolę stanu. (grpc.health.v1.Health/Check) W jaki sposób odfiltrowujesz te spany z Cloud Trace? Wskazówka znajdziesz tutaj.
  • Skoreluj logi zdarzeń ze spanami i sprawdź, jak działa to w Google Cloud Trace i Google Cloud Logging. Wskazówka znajdziesz tutaj.
  • Zastąp jakąś usługę usługą w innym języku i spróbuj dopasować ją do usługi OpenTelemetry dla tego języka.

Jeśli chcesz dowiedzieć się więcej o narzędziu do profilowania, przejdź do części 2. W takim przypadku możesz pominąć sekcję Czyszczenie znajdującą się poniżej.

Czyszczenie danych

Po ukończeniu tego ćwiczenia w Codelabs zatrzymaj klaster Kubernetes i pamiętaj o usunięciu projektu, aby nie otrzymywać nieoczekiwanych opłat w Google Kubernetes Engine, Google Cloud Trace czy Google Artifact Registry.

Najpierw usuń klaster. Jeśli używasz klastra skaffold dev, wystarczy nacisnąć Ctrl + C. Jeśli używasz klastra z wersją skaffold run, uruchom to polecenie:

skaffold delete

Dane wyjściowe polecenia

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

Po usunięciu klastra w panelu menu wybierz „Uprawnienia Administrator &gt; „Ustawienia”, a następnie kliknij „WYŁĄCZ” Przycisk

45aa37b7d5e1ddd1.png

Następnie wpisz identyfikator projektu (nie nazwę projektu) w formularzu w oknie i potwierdź zamknięcie.