Инструмент для повышения производительности вашего приложения на Go (часть 2: профилировщик)

1. Введение

e0509e8a07ad5537.png

Последнее обновление: 14 июля 2022 г.

Наблюдаемость приложения

Наблюдаемость и непрерывный профилировщик

Наблюдаемость — это термин, используемый для описания атрибута системы. Система с возможностью наблюдения позволяет командам активно отлаживать свою систему. В этом контексте три столпа наблюдаемости; журналы, метрики и трассировки — это фундаментальные инструменты, позволяющие системе обеспечить наблюдаемость.

Кроме того, в дополнение к трем столпам наблюдения, непрерывное профилирование является еще одним ключевым компонентом наблюдаемости и расширяет базу пользователей в отрасли. Cloud Profiler является одним из создателей и предоставляет простой интерфейс для детализации показателей производительности в стеках вызовов приложений.

Эта кодовая лаборатория является второй частью серии и посвящена инструментированию агента непрерывного профилирования. В части 1 рассматривается распределенная трассировка с помощью OpenTelemetry и Cloud Trace, а в части 1 вы узнаете больше о том, как лучше выявлять узкие места микросервисов.

Что ты построишь

В этой лаборатории кода вы собираетесь внедрить агент непрерывного профилирования в серверную службу «приложения Shakespeare» (также известного как Shakesapp), которое работает в кластере Google Kubernetes Engine. Архитектура Shakesapp описана ниже:

44e243182ced442f.png

  • Loadgen отправляет строку запроса клиенту по HTTP.
  • Клиенты передают запрос от генератора нагрузки на сервер в gRPC.
  • Сервер принимает запрос от клиента, извлекает все произведения Шекспира в текстовом формате из Google Cloud Storage, ищет строки, содержащие запрос, и возвращает номер строки, соответствующей клиенту.

В части 1 вы обнаружили, что где-то в серверной службе существует узкое место, но не смогли определить точную причину.

Что вы узнаете

  • Как встроить агент профилировщика
  • Как исследовать узкое место в Cloud Profiler

В этой кодовой лаборатории объясняется, как использовать агент непрерывного профилирования в вашем приложении.

Что вам понадобится

  • Базовые знания Го
  • Базовые знания Kubernetes

2. Настройка и требования

Самостоятельная настройка среды

Если у вас еще нет учетной записи Google (Gmail или Google Apps), вам необходимо ее создать . Войдите в консоль Google Cloud Platform ( console.cloud.google.com ) и создайте новый проект.

Если у вас уже есть проект, щелкните раскрывающееся меню выбора проекта в левом верхнем углу консоли:

7a32e5469db69e9.png

и нажмите кнопку «НОВЫЙ ПРОЕКТ» в появившемся диалоговом окне, чтобы создать новый проект:

7136b3ee36ebaf89.png

Если у вас еще нет проекта, вы должны увидеть подобное диалоговое окно, чтобы создать свой первый:

870a3cbd6541ee86.png

Последующий диалог создания проекта позволяет вам ввести детали вашего нового проекта:

affdc444517ba805.png

Запомните идентификатор проекта, который является уникальным именем для всех проектов Google Cloud (имя, указанное выше, уже занято и не подойдет вам, извините!). Позже в этой лаборатории он будет называться 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 , среду командной строки, работающую в облаке.

Эта виртуальная машина на базе Debian оснащена всеми необходимыми инструментами разработки. Он предлагает постоянный домашний каталог объемом 5 ГБ и работает в Google Cloud, что значительно повышает производительность сети и аутентификацию. Это означает, что все, что вам понадобится для этой лаборатории кода, — это браузер (да, он работает на Chromebook).

Чтобы активировать Cloud Shell из Cloud Console, просто нажмите «Активировать Cloud Shell». gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (подготовка и подключение к среде займет всего несколько минут).

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

Снимок экрана 14.06.2017, 22.13.43.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 ? Узнайте, какой идентификатор вы использовали на этапах настройки, или найдите его на панели управления Cloud Console:

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 (GAR).
  4. Развертывание контейнеров в GKE
  5. Измените исходный код служб для инструментов трассировки.
  6. Перейти к шагу 2

Включить движок Kubernetes

Сначала мы настраиваем кластер Kubernetes, в котором Shakesapp работает на GKE, поэтому нам нужно включить GKE. Перейдите в меню «Kubernetes Engine» и нажмите кнопку ВКЛЮЧИТЬ.

548cfd95bc6d344d.png

Теперь вы готовы создать кластер Kubernetes.

Создать кластер Kubernetes

В Cloud Shell выполните следующую команду, чтобы создать кластер Kubernetes. Подтвердите, что значение зоны соответствует региону , который вы будете использовать для создания репозитория реестра артефактов. Измените значение зоны 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

Реестр артефактов и настройка скаффолда

Теперь у нас есть кластер Kubernetes, готовый к развертыванию. Затем мы готовим реестр контейнеров для отправки и развертывания контейнеров. Для этих шагов нам нужно настроить реестр артефактов (GAR) и скаффолд для его использования.

Настройка реестра артефактов

Перейдите в меню «Реестр артефактов» и нажмите кнопку ВКЛЮЧИТЬ.

45e384b87f7cf0db.png

Через несколько секунд вы увидите браузер репозитория GAR. Нажмите кнопку «СОЗДАТЬ РЕПОЗИТОРИЙ» и введите имя репозитория.

d6a70f4cb4ebcbe3.png

В этой кодовой лаборатории я называю новый репозиторий trace-codelab . Формат артефакта — «Docker», а тип местоположения — «Регион». Выберите регион, близкий к тому, который вы установили для зоны по умолчанию Google Compute Engine. Например, в этом примере выше выбрано «us-central1-f», поэтому здесь мы выбираем «us-central1 (Айова)». Затем нажмите кнопку «СОЗДАТЬ».

9c2d1ce65258ef70.png

Теперь вы видите «trace-codelab» в браузере репозитория.

7a3c1f47346bea15.png

Мы вернемся сюда позже, чтобы проверить путь к реестру.

Установка строительных лесов

Skaffold — удобный инструмент, когда вы работаете над созданием микросервисов, работающих в Kubernetes. Он управляет рабочим процессом создания, отправки и развертывания контейнеров приложений с помощью небольшого набора команд. Skaffold по умолчанию использует реестр Docker в качестве реестра контейнеров, поэтому вам необходимо настроить skaffold для распознавания GAR при отправке контейнеров.

Снова откройте Cloud Shell и убедитесь, что скаффолд установлен. (Cloud Shell по умолчанию устанавливает скаффолд в среду.) Выполните следующую команду и посмотрите версию скаффолда.

skaffold version

Вывод команды

v1.38.0

Теперь вы можете зарегистрировать репозиторий по умолчанию для использования скаффолдом. Чтобы получить путь к реестру, перейдите на панель мониторинга реестра артефактов и щелкните имя репозитория, который вы только что настроили на предыдущем шаге.

7a3c1f47346bea15.png

Затем вы увидите следы хлебных крошек в верхней части страницы. Нажмите e157b1359c3edc06.png значок, чтобы скопировать путь реестра в буфер обмена.

e0f2ae2144880b8b.png

При нажатии кнопки копирования вы увидите диалоговое окно в нижней части браузера с сообщением типа:

«us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab» скопирован.

Вернитесь в облачную оболочку. Запустите команду 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

Теперь можно перейти к следующему шагу — настройке контейнера Kubernetes в GKE.

Краткое содержание

На этом этапе вы настраиваете среду кодовой лаборатории:

  • Настройте Cloud Shell
  • Создан репозиторий Artifact Registry для реестра контейнеров.
  • Настройте skaffold для использования реестра контейнеров.
  • Создан кластер Kubernetes, в котором работают микросервисы Codelab.

Дальше

На следующем этапе вы подключите агент непрерывного профилирования к серверной службе.

3. Создавайте, распространяйте и развертывайте микросервисы.

Загрузите материал codelab

На предыдущем шаге мы создали все необходимые условия для этой лаборатории кода. Теперь вы готовы запускать поверх них целые микросервисы. Материалы codelab размещены на GitHub, поэтому загрузите их в среду Cloud Shell с помощью следующей команды git.

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: определение протокола для связи между клиентом и сервером.
  • src: каталоги с исходным кодом каждого сервиса.
  • skaffold.yaml: файл конфигурации для skaffold.

В этой лаборатории кода вы обновите исходный код, расположенный в папке step4 . Вы также можете обратиться к исходному коду в папках step[1-6] для изменений с самого начала. (Часть 1 охватывает шаги с 0 по 4, а часть 2 — шаги 5 и 6)

Запустить команду скаффолда

Наконец, вы готовы собирать, отправлять и развертывать весь контент в только что созданном кластере 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

После развертывания вы увидите фактические журналы приложений, отправляемые на стандартный вывод в каждом контейнере, например:

Вывод команды

[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

Краткое содержание

На этом этапе вы подготовили материал codelab в своей среде и подтвердили ожидаемое выполнение скаффолда.

Дальше

На следующем шаге вы измените исходный код службы loadgen для инструментирования информации трассировки.

4. Инструментарий агента Cloud Profiler

Концепция непрерывного профилирования

Прежде чем объяснять концепцию непрерывного профилирования, нам необходимо сначала понять, что такое профилирование. Профилирование является одним из способов динамического анализа приложения (динамического анализа программы) и обычно выполняется при разработке приложения, в процессе нагрузочного тестирования и т.п. Это разовое действие для измерения системных показателей, таких как использование ЦП и памяти, в течение определенного периода. После сбора данных профиля разработчики анализируют их из кода.

Непрерывное профилирование — это расширенный подход обычного профилирования: он периодически запускает профили коротких окон для долго работающего приложения и собирает кучу данных профиля. Затем он автоматически генерирует статистический анализ на основе определенного атрибута приложения, такого как номер версии, зона развертывания, время измерения и т. д. Более подробную информацию о концепции вы найдете в нашей документации .

Поскольку целью является работающее приложение, существует способ периодически собирать данные профиля и отправлять их на какой-либо сервер, который выполняет постобработку статистических данных. Это агент Cloud Profiler, и вы скоро собираетесь внедрить его в серверную службу.

Встроить агент Cloud Profiler

Откройте редактор Cloud Shell, нажав кнопку 776a11bfb2122549.png в правом верхнем углу Cloud Shell. Откройте step4/src/server/main.go в проводнике на левой панели и найдите функцию main.

шаг 4/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() для удобства чтения.

шаг 4/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 : отключить профилирование горутины.
  • NoCPUProfiling : отключить профилирование ЦП.

В этой кодовой лаборатории мы включаем только профилирование ЦП.

Теперь вам нужно просто вызвать эту функцию в main функции. Обязательно импортируйте пакет Cloud Profiler в блок импорта.

шаг 4/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
        ...
}

Обратите внимание, что вы вызываете функцию initProfiler() с ключевым словом go . Поскольку profiler.Start() блокируется, вам нужно запустить его в другой горутине. Теперь он готов к сборке. Обязательно запустите go mod tidy перед развертыванием.

go mod tidy

Теперь разверните свой кластер с новой серверной службой.

skaffold dev

Обычно требуется несколько минут, чтобы увидеть график пламени в Cloud Profiler. Введите «профилировщик» в поле поиска вверху и щелкните значок «Профилировщик».

3d8ca8a64b267a40.png

Затем вы увидите следующий график пламени.

7f80797dddc0128d.png

Краткое содержание

На этом этапе вы внедрили агент Cloud Profiler в службу сервера и подтвердили, что он генерирует график пламени.

Дальше

На следующем этапе вы исследуете причину узкого места в приложении с помощью графика пламени.

5. Анализ графика пламени Cloud Profiler

Что такое график пламени?

Flame Graph — один из способов визуализации данных профиля. Подробное объяснение можно найти в нашем документе , но краткое изложение таково:

  • Каждая полоса выражает вызов метода/функции в приложении.
  • Вертикальное направление — это стек вызовов; стек вызовов растет сверху вниз
  • Горизонтальное направление — использование ресурсов; чем дольше, тем хуже.

Учитывая это, давайте посмотрим на полученный график пламени.

7f80797dddc0128d.png

Анализ графика пламени

В предыдущем разделе вы узнали, что каждая полоска на графике пламени отражает вызов функции/метода, а ее длина означает использование ресурса функцией/методом. График пламени Cloud Profiler сортирует полосу в порядке убывания или по длине слева направо. Сначала вы можете начать смотреть в верхнем левом углу графика.

6d90760c6c1183cd.png

В нашем случае очевидно, что grpc.(*Server).serveStreams.func1.2 потребляет большую часть процессорного времени, и, просматривая стек вызовов сверху вниз, большая часть времени тратится на main.(*serverService).GetMatchCount — обработчик сервера gRPC в серверной службе.

В разделе GetMatchCount вы видите ряд функций регулярного выражения : regexp.MatchString и regexp.Compile . Они из стандартной упаковки: то есть должны быть хорошо протестированы со многих точек зрения, в том числе и производительности. Но результат здесь показывает, что использование ресурсов процессорного времени высоко в regexp.MatchString и regexp.Compile . Учитывая эти факты, предполагается, что использование regexp.MatchString как-то связано с проблемами производительности. Итак, давайте прочитаем исходный код, в котором используется функция.

шаг 4/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. Поэтому использование этой функции может быть неправильным. Давайте посмотрим GoDoc регулярного выражения .

80b8a4ba1931ff7b.png

Согласно документу, regexp.MatchString компилирует шаблон регулярного выражения при каждом вызове. Итак, причина большого потребления ресурсов звучит так.

Краткое содержание

На этом этапе вы сделали предположение о причине потребления ресурсов, проанализировав график пламени.

Дальше

На следующем шаге вы обновите исходный код службы сервера и подтвердите изменение с версии 1.0.0.

6. Обновите исходный код и сравните графики пламени.

Обновите исходный код

На предыдущем шаге вы предположили, что использование regexp.MatchString как-то связано с большим потреблением ресурсов. Итак, давайте решим это. Откройте код и немного измените эту часть.

шаг 4/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() .

шаг 4/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 уменьшилась, а коэффициент использования процессорного времени (т.е. полоса стала короче).

e3a1456b4aada9a5.png

Не только просматривая график пламени одной версии, вы также можете сравнить различия между двумя версиями.

841dec77d8ba5595.png

Измените значение раскрывающегося списка «Сравнить с» на «Версия» и измените значение «Сравниваемая версия» на «1.0.0», исходную версию.

5553844292d6a537.png

Вы увидите такой график пламени. Форма графика такая же, как в 1.1.0, но расцветка другая. В режиме сравнения цвет означает следующее:

  • Синий : значение (потребление ресурсов) уменьшено.
  • Оранжевый : полученная ценность (потребление ресурсов).
  • Серый : нейтральный

Учитывая легенду, давайте более подробно рассмотрим функцию. Нажав на полосу, которую хотите увеличить, вы сможете увидеть более подробную информацию внутри стека. Нажмите кнопку main.(*serverService).GetMatchCount . Также, наведя курсор на полосу, вы увидите детали сравнения.

ca08d942dc1e2502.png

В нем говорится, что общее время процессора уменьшено с 5,26 с до 2,88 с (всего 10 с = окно выборки). Это огромное улучшение!

Теперь вы можете повысить производительность своего приложения за счет анализа данных профиля.

Краткое содержание

На этом этапе вы внесли изменения в службу сервера и подтвердили улучшение режима сравнения Cloud Profiler.

Дальше

На следующем шаге вы обновите исходный код службы сервера и подтвердите изменение с версии 1.0.0.

7. Дополнительный шаг: подтвердите улучшение водопада трассировки.

Разница между распределенной трассировкой и непрерывным профилированием

В первой части лабораторной работы по коду вы подтвердили, что можете определить узкое место во всех микросервисах для пути запроса, но не можете выяснить точную причину узкого места в конкретной службе. В ходе этой лабораторной работы по коду части 2 вы узнали, что непрерывное профилирование позволяет выявлять узкие места внутри отдельного сервиса по стекам вызовов.

На этом этапе давайте рассмотрим каскадный график распределенной трассировки (Cloud Trace) и увидим отличие от непрерывного профилирования.

Этот водопадный граф является одной из трасс с запросом «любовь». Всего это занимает около 6,7 с (6700 мс).

e2b7dec25926ee51.png

И это после улучшения того же запроса. Как вы сказали, общая задержка теперь составляет 1,5 с (1500 мс), что является огромным улучшением по сравнению с предыдущей реализацией.

feeb7207f36c7e5e.png

Важным моментом здесь является то, что в каскадной диаграмме распределенной трассировки информация стека вызовов недоступна, если вы не инструментируете диапазоны повсюду. Кроме того, распределенные трассировки фокусируются только на задержке между службами, тогда как непрерывное профилирование фокусируется на компьютерных ресурсах (ЦП, память, потоки ОС) одной службы.

В другом аспекте распределенная трасса является базой событий, непрерывный профиль является статистическим. Каждая трасса имеет свой график задержки, и вам нужен другой формат, например распределение , чтобы получить тенденцию изменения задержки.

Краткое содержание

На этом этапе вы проверили разницу между распределенной трассировкой и непрерывным профилированием.

8. Поздравления

Вы успешно создали распределенные трассировки с помощью OpenTelemery и подтвердили задержки запросов в микросервисе в Google Cloud Trace.

Для расширенных упражнений вы можете самостоятельно попробовать следующие темы.

  • Текущая реализация отправляет все промежутки, созданные в ходе проверки работоспособности. ( grpc.health.v1.Health/Check ) Как отфильтровать эти промежутки из Cloud Traces? Подсказка здесь .
  • Сопоставьте журналы событий с интервалами и посмотрите, как это работает в 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 и администратор» > «Настройки», а затем нажмите кнопку «ЗАВЕРШИТЬ».

45aa37b7d5e1ddd1.png

Затем введите идентификатор проекта (а не имя проекта) в форму в диалоговом окне и подтвердите закрытие.