1. Introducción
Última actualización: 14/07/2022
Observabilidad de la aplicación
Observabilidad y generador de perfiles continuos
Observabilidad es el término que se usa para describir un atributo de un sistema. Un sistema con observabilidad permite a los equipos depurar activamente su sistema. En ese contexto, tres pilares de observabilidad: los registros, las métricas y los seguimientos son la instrumentación fundamental para que el sistema adquiera observabilidad.
Además de los tres pilares de observabilidad, la creación de perfiles continua es otro componente clave para la observabilidad y expande la base de usuarios en el sector. Cloud Profiler es uno de los creadores y proporciona una interfaz sencilla para desglosar las métricas de rendimiento en las pilas de llamadas de la aplicación.
Este codelab es la segunda parte de la serie y abarca la instrumentación de un agente de generador de perfiles continuo. En la parte 1, se aborda el seguimiento distribuido con OpenTelemetry y Cloud Trace, y aprenderás más sobre cómo identificar mejor el cuello de botella de los microservicios en la parte 1.
Qué compilarás
En este codelab, instrumentarás un agente de generador de perfiles continuo en el servicio del servidor de la "aplicación de Shakespeare" (también conocido como Shakesapp) que se ejecuta en un clúster de Google Kubernetes Engine. A continuación, se describe la arquitectura de Shakesapp:
- Loadgen envía una cadena de consulta al cliente en HTTP
- Los clientes pasan la consulta de loadgen al servidor en gRPC.
- El servidor acepta la consulta del cliente, recupera todos los trabajos de Shakespare en formato de texto de Google Cloud Storage, busca las líneas que contienen la consulta y devuelve el número de la línea que coincidió con el cliente.
En la parte 1, descubriste que el cuello de botella existe en algún lugar del servicio del servidor, pero no pudiste identificar la causa exacta.
Qué aprenderás
- Cómo incorporar el agente de Profiler
- Cómo investigar el cuello de botella en Cloud Profiler
En este codelab, se explica cómo instrumentar un agente de generador de perfiles continuo en tu aplicación.
Requisitos
- Conocimientos básicos de Go
- Conocimientos básicos de Kubernetes
2. Configuración y requisitos
Configuración del entorno de autoaprendizaje
Si aún no tienes una Cuenta de Google (Gmail o Google Apps), debes crear una. Accede a Google Cloud Platform Console (console.cloud.google.com) y crea un proyecto nuevo.
Si ya tienes un proyecto, haz clic en el menú desplegable de selección de proyectos en la parte superior izquierda de la Console:
y haz clic en el botón “PROYECTO NUEVO” en el diálogo resultante para crear un proyecto nuevo:
Si aún no tienes un proyecto, deberías ver un cuadro de diálogo como este para crear el primero:
El cuadro de diálogo de creación posterior del proyecto te permite ingresar los detalles de tu proyecto nuevo:
Recuerda el ID del proyecto, que es un nombre único en todos los proyectos de Google Cloud (el nombre anterior ya se encuentra en uso y no lo podrá usar). Se mencionará más adelante en este codelab como PROJECT_ID.
A continuación, si aún no lo hiciste, deberás habilitar la facturación en Developers Console para usar los recursos de Google Cloud y habilitar la API de Cloud Trace.
Ejecutar este codelab debería costar solo unos pocos dólares, pero su costo podría aumentar si decides usar más recursos o si los dejas en ejecución (consulta la sección “Limpiar” al final de este documento). Los precios de Google Cloud Trace, Google Kubernetes Engine y Google Artifact Registry se indican en la documentación oficial.
- Precios de Google Cloud's operations suite | Operations suite
- Precios | Documentación de Kubernetes Engine
- Precios de Artifact Registry | Documentación de Artifact Registry
Los usuarios nuevos de Google Cloud Platform están aptas para obtener una prueba gratuita de $300, por lo que este codelab es completamente gratuito.
Configuración de Google Cloud Shell
Si bien Google Cloud y Google Cloud Trace se pueden operar de forma remota desde tu laptop, en este codelab usaremos Google Cloud Shell, un entorno de línea de comandos que se ejecuta en la nube.
Esta máquina virtual basada en Debian está cargada con todas las herramientas de desarrollo que necesitarás. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que permite mejorar considerablemente el rendimiento de la red y la autenticación. Esto significa que todo lo que necesitarás para este Codelab es un navegador (sí, funciona en una Chromebook).
Para activar Cloud Shell desde la consola de Cloud, simplemente haz clic en Activar Cloud Shell (el aprovisionamiento y la conexión al entorno debería tardar solo unos momentos).
Una vez conectado a Cloud Shell, debería ver que ya se autenticó y que el proyecto ya se configuró con tu PROJECT_ID
:
gcloud auth list
Resultado del comando
Credentialed accounts: - <myaccount>@<mydomain>.com (active)
gcloud config list project
Resultado del comando
[core] project = <PROJECT_ID>
Si, por algún motivo, el proyecto no está configurado, solo emite el siguiente comando:
gcloud config set project <PROJECT_ID>
Si no conoce su PROJECT_ID
, Observa el ID que usaste en los pasos de configuración o búscalo en el panel de la consola de Cloud:
Cloud Shell también configura algunas variables de entorno de forma predeterminada, lo que puede resultar útil cuando ejecutas comandos futuros.
echo $GOOGLE_CLOUD_PROJECT
Resultado del comando
<PROJECT_ID>
Establece la zona predeterminada y la configuración del proyecto.
gcloud config set compute/zone us-central1-f
Puedes elegir una variedad de zonas diferentes. Para obtener más información, consulta Regiones y zonas.
Configuración de idioma en Go
En este codelab, usamos Go para todo el código fuente. Ejecuta el siguiente comando en Cloud Shell y confirma si la versión de Go es 1.17 o superior.
go version
Resultado del comando
go version go1.18.3 linux/amd64
Configura un clúster de Google Kubernetes
En este codelab, ejecutarás un clúster de microservicios en Google Kubernetes Engine (GKE). El proceso de este codelab es el siguiente:
- Descarga el proyecto de referencia en Cloud Shell
- Compila microservicios en contenedores
- Sube contenedores a Google Artifact Registry (GAR)
- Implementa contenedores en GKE
- Modifica el código fuente de los servicios para la instrumentación de seguimiento
- Ir al paso 2
Habilitar Kubernetes Engine
Primero, configuramos un clúster de Kubernetes en el que Shakesapp se ejecuta en GKE, así que debemos habilitar GKE. Navega al menú “Kubernetes Engine”. y presiona el botón HABILITAR.
Ya está todo listo para crear un clúster de Kubernetes.
Crea un clúster de Kubernetes
En Cloud Shell, ejecuta el siguiente comando para crear un clúster de Kubernetes. Confirma que el valor de la zona esté debajo de la región que usarás para la creación del repositorio de Artifact Registry. Cambia el valor de zona us-central1-f
si la región de tu repositorio no cubre 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
Resultado del 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
Configuración de Artifact Registry y Skaffold
Ahora tenemos un clúster de Kubernetes listo para la implementación. A continuación, prepararemos un registro de contenedores para implementar y enviar contenedores. Para estos pasos, debemos configurar Artifact Registry (GAR) y Skaffold para usarlo.
Configuración de Artifact Registry
Navega al menú de “Artifact Registry” y presiona el botón HABILITAR.
Después de un momento, verás el navegador del repositorio de GAR. Haz clic en "CREAR REPOSITORIO". e ingresa el nombre del repositorio.
En este codelab, asignaré el nombre trace-codelab
al nuevo repositorio. El formato del artefacto es “Docker”. y el tipo de ubicación es "Región". Elige una región cercana a la que estableciste para la zona predeterminada de Google Compute Engine. Por ejemplo, este ejemplo eligió “us-central1-f” arriba, así que aquí elegimos "us-central1 (Iowa)". Luego, haz clic en "CREATE" .
Ahora verás "trace-codelab" en el navegador del repositorio.
Regresaremos más adelante para verificar la ruta de registro.
Configuración de Skaffold
Skaffold es una herramienta útil cuando trabajas en la compilación de microservicios que se ejecutan en Kubernetes. Controla el flujo de trabajo de compilación, envío e implementación de contenedores de aplicaciones con un pequeño conjunto de comandos. De forma predeterminada, Skaffold usa Docker Registry como registro de contenedores, por lo que debes configurar Skaffold para que reconozca GAR cuando se envían contenedores.
Vuelve a abrir Cloud Shell y confirma si Skaffold está instalado. (Cloud Shell instala Skaffold en el entorno de forma predeterminada). Ejecuta el siguiente comando y consulta la versión de Skaffold.
skaffold version
Resultado del comando
v1.38.0
Ahora, puedes registrar el repositorio predeterminado para que lo use Skaffold. Para obtener la ruta de acceso del registro, navega hasta el panel de Artifact Registry y haz clic en el nombre del repositorio que acabas de configurar en el paso anterior.
Luego, verás los recorridos de las rutas de navegación en la parte superior de la página. Haz clic en el ícono para copiar la ruta de registro en el portapapeles.
Cuando hagas clic en el botón de copiar, verás un diálogo en la parte inferior del navegador con el siguiente mensaje:
"us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab" se copió
Regresa a Cloud Shell. Ejecuta el comando skaffold config set default-repo
con el valor que acabas de copiar del panel.
skaffold config set default-repo us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab
Resultado del 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
Además, debes establecer el registro en la configuración de Docker. Ejecuta el siguiente comando:
gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
Resultado del 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
Ya está todo listo para el próximo paso que te permitirá configurar un contenedor de Kubernetes en GKE.
Resumen
En este paso, configurarás el entorno de tu codelab:
- Configura Cloud Shell
- Creaste un repositorio de Artifact Registry para Container Registry.
- Configura Skaffold para usar Container Registry
- Creaste un clúster de Kubernetes en el que se ejecutan los microservicios del codelab.
Cuál es el próximo paso
En el siguiente paso, instrumentarás un agente de generador de perfiles continuo en el servicio del servidor.
3. Compila, envía e implementa los microservicios
Descarga el material del codelab
En el paso anterior, configuramos todos los requisitos previos para este codelab. Ahora está todo listo para ejecutar microservicios completos sobre ellos. El material del codelab está alojado en GitHub, así que descárgalo en el entorno de Cloud Shell con el siguiente comando de Git.
cd ~ git clone https://github.com/ymotongpoo/opentelemetry-trace-codelab-go.git cd opentelemetry-trace-codelab-go
La estructura de directorios del proyecto es la siguiente:
. ├── 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
- Manifiestos: Archivos de manifiesto de Kubernetes
- proto: definición de proto para la comunicación entre el cliente y el servidor
- src: Directorios para el código fuente de cada servicio
- skaffold.yaml: Archivo de configuración de Skaffold
En este codelab, actualizarás el código fuente ubicado en la carpeta step4
. También puedes consultar el código fuente en las carpetas step[1-6]
para ver los cambios desde el principio. (La parte 1 abarca los pasos 0 y 4, y la parte 2 abarca los pasos 5 y 6).
Ejecuta el comando de Skaffold
Por último, estarás listo para compilar, enviar e implementar todo el contenido en el clúster de Kubernetes que acabas de crear. Parece que contiene varios pasos, pero Skaffold hace todo por ti. Probemos eso con el siguiente comando:
cd step4 skaffold dev
En cuanto ejecutes el comando, verás el resultado del registro de docker build
y podrás confirmar que se enviaron correctamente al registro.
Resultado del 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
Después de enviar todos los contenedores de servicio, las implementaciones de Kubernetes se inician automáticamente.
Resultado del 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
Después de la implementación, verás los registros de aplicación reales emitidos a stdout en cada contenedor de la siguiente manera:
Resultado del 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
Ten en cuenta que, en este punto, quieres ver los mensajes del servidor. Ya puedes comenzar a instrumentar tu aplicación con OpenTelemetry para el seguimiento distribuido de los servicios.
Antes de comenzar a instrumentar el servicio, cierra tu clúster con Ctrl+C.
Resultado del 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
Resumen
En este paso, preparaste el material del codelab en tu entorno y confirmaste que Skaffold se ejecuta como se esperaba.
Cuál es el próximo paso
En el siguiente paso, modificarás el código fuente del servicio loadgen para instrumentar la información de seguimiento.
4. Instrumentación del agente de Cloud Profiler
Concepto de la generación de perfiles continua
Antes de explicar el concepto de generación de perfiles continua, primero debemos comprender el de generación de perfiles. La generación de perfiles es una de las formas de analizar la aplicación de forma dinámica (análisis de programa dinámico) y, por lo general, se realiza durante el desarrollo de la aplicación, en el proceso de prueba de carga y así sucesivamente. Se trata de una actividad de un solo intento para medir las métricas del sistema, como los usos de CPU y memoria, durante el período específico. Después de recopilar los datos de perfil, los desarrolladores los analizan y desprenden del código.
La generación de perfiles continua es el enfoque extendido de la generación de perfiles normal: ejecuta perfiles de ventana corta en la aplicación de larga duración de forma periódica y recopila una gran cantidad de datos de perfil. Luego, genera automáticamente el análisis estadístico basado en un atributo determinado de la aplicación, como el número de versión, la zona de implementación, la hora de medición, etcétera. Encontrarás más detalles sobre este concepto en nuestra documentación.
Debido a que el destino es una aplicación en ejecución, hay una manera de recopilar datos de perfil periódicamente y enviarlos a algún backend que procese posteriormente los datos estadísticos. Ese es el agente de Cloud Profiler, que pronto lo incorporarás al servicio del servidor.
Incorporar el agente de Cloud Profiler
Para abrir el editor de Cloud Shell, presiona el botón ubicado en la parte superior derecha de Cloud Shell. Abre
step4/src/server/main.go
desde el explorador en el panel izquierdo y busca la función principal.
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) } }
En la función main
, verás un código de configuración para OpenTelemetry y gRPC, como se hizo en la parte 1 del codelab. Ahora agregarás instrumentación para el agente de Cloud Profiler aquí. Al igual que hicimos con initTracer()
, puedes escribir una función llamada initProfiler()
para facilitar la lectura.
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) } }
Observemos en detalle las opciones especificadas en el objeto profiler.Config{}
.
- Servicio: Nombre del servicio que puedes seleccionar y activar el panel del generador de perfiles
- ServiceVersion: el nombre de la versión del servicio Puedes comparar conjuntos de datos de perfil según este valor.
- NoHeapProfiling: Inhabilita la creación de perfiles de consumo de memoria.
- NoAllocProfiling: Inhabilita la creación de perfiles de asignación de memoria.
- NoGoroutineProfiling: Inhabilita la generación de perfiles de goroutine.
- NoCPUProfiling: inhabilita la generación de perfiles de CPU
En este codelab, solo habilitamos la generación de perfiles de CPU.
Ahora, lo que debes hacer es llamar a esta función en la función main
. Asegúrate de importar el paquete de Cloud Profiler en el bloque de importación.
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 ... }
Ten en cuenta que llamas a la función initProfiler()
con la palabra clave go
. Porque profiler.Start()
bloquea, por lo que debes ejecutarlo en otra goroutine. Ya está listo para la compilación. Asegúrate de ejecutar go mod tidy
antes de la implementación.
go mod tidy
Ahora implementa tu clúster con tu nuevo servicio de servidor.
skaffold dev
Por lo general, el gráfico tipo llama en Cloud Profiler tarda un par de minutos. Escribe "Generador de perfiles" en el cuadro de búsqueda de la parte superior y haz clic en el ícono de Profiler.
Luego, verás el siguiente gráfico tipo llama.
Resumen
En este paso, incorporaste el agente de Cloud Profiler al servicio del servidor y confirmaste que generaba un gráfico tipo llama.
Cuál es el próximo paso
En el siguiente paso, investigarás la causa del cuello de botella en la aplicación con el gráfico tipo llama.
5. Analiza el gráfico tipo llama de Cloud Profiler
¿Qué es Flame Graph?
Flame Graph es una de las formas de visualizar los datos del perfil. Para obtener una explicación detallada, consulte nuestro documento. Sin embargo, el resumen breve es el siguiente:
- Cada barra expresa la llamada al método o a la función en la aplicación
- La dirección vertical es la pila de llamadas. la pila de llamadas crece de arriba abajo
- La dirección horizontal es el uso de recursos. cuanto más largas sean, peores.
Por consiguiente, veamos el gráfico tipo llama obtenido.
Análisis del gráfico tipo llama
En la sección anterior, aprendiste que cada barra del gráfico tipo llama expresa la llamada a función o al método, y su longitud significa el uso de recursos en la función o el método. El gráfico tipo llama de Cloud Profiler ordena la barra en orden descendente o por su longitud de izquierda a derecha. Puedes comenzar a mirar primero la parte superior izquierda del gráfico.
En nuestro caso, queda explícito que grpc.(*Server).serveStreams.func1.2
consume la mayor parte del tiempo de CPU y, cuando se analiza la pila de llamadas de arriba abajo, se pasa la mayor parte del tiempo en main.(*serverService).GetMatchCount
, que es el controlador del servidor gRPC en el servicio del servidor.
En GetMatchCount, puedes ver una serie de funciones regexp: regexp.MatchString
y regexp.Compile
. Provienen del paquete estándar, es decir, deben probarse correctamente desde muchos puntos de vista, incluido el rendimiento. Sin embargo, el resultado muestra que el uso de recursos de tiempo de CPU es alto en regexp.MatchString
y regexp.Compile
. Teniendo en cuenta estos hechos, la suposición aquí es que el uso de regexp.MatchString
está relacionado con problemas de rendimiento. Leamos el código fuente en el que se usa la función.
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 }
Este es el lugar donde se llama a regexp.MatchString
. Al leer el código fuente, puedes observar que se llama a la función dentro del bucle for anidado. Por lo tanto, el uso de esta función puede ser incorrecto. Busquemos el GoDoc de regexp.
Según el documento, regexp.MatchString
compila el patrón de expresiones regulares en cada llamada. Así que la causa del gran consumo de recursos suena así:
Resumen
En este paso, realizaste la suposición de la causa del consumo de recursos mediante el análisis del gráfico tipo llama.
Cuál es el próximo paso
En el siguiente paso, actualizarás el código fuente del servicio de servidor y confirmarás el cambio de la versión 1.0.0.
6. Actualiza el código fuente y compara los gráficos tipo llama
Actualiza el código fuente
En el paso anterior, suponiste que el uso de regexp.MatchString
tiene algo que ver con el gran consumo de recursos. Resolvamos esto. Abre el código y cambia un poco esa 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 }
Como puedes ver, ahora el proceso de compilación de patrones de expresiones regulares se extrae de regexp.MatchString
y se quita del bucle for anidado.
Antes de implementar este código, asegúrate de actualizar la cadena de versión en la función 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) } }
Veamos cómo funciona. Implementa el clúster con el comando de Skaffold.
skaffold dev
Después de un tiempo, vuelve a cargar el panel de Cloud Profiler y observa cómo es.
Asegúrate de cambiar la versión a "1.1.0"
para que solo veas los perfiles de la versión 1.1.0. Como puedes ver, se redujo la longitud de la barra de GetMatchCount y la proporción de uso del tiempo de CPU (es decir, la barra se acortó).
No solo si observas el gráfico tipo llama de una sola versión, también puedes comparar las diferencias entre dos versiones.
Cambiar el valor de "Comparar con" lista desplegable a "Versión". y cambia el valor de "Comparada versión" a "1.0.0", la versión original.
Verás este tipo de gráfico tipo llama. La forma del gráfico es la misma que la de 1.1.0, pero el color es diferente. En el modo de comparación, el significado del color es el siguiente:
- Azul: el valor (consumo de recursos) reducido
- Naranja: El valor (consumo de recursos) obtenido
- Gris: neutro
Dada la leyenda, analicemos con más detalle la función. Si haces clic en la barra que quieres acercar, verás más detalles dentro de la pila. Haz clic en la barra main.(*serverService).GetMatchCount
. Además, si colocas el cursor sobre la barra, verás los detalles de la comparación.
Dice que el tiempo de CPU total se reduce de 5.26 s a 2.88 s (el total es 10 s = ventana de muestreo). Es una gran mejora.
Ahora, puedes mejorar el rendimiento de tu aplicación a partir del análisis de los datos de perfil.
Resumen
En este paso, realizaste una edición en el servicio de servidor y confirmaste la mejora en el modo de comparación de Cloud Profiler.
Cuál es el próximo paso
En el siguiente paso, actualizarás el código fuente del servicio de servidor y confirmarás el cambio de la versión 1.0.0.
7. Paso adicional: Confirma la mejora en la cascada de Trace
Diferencia entre el seguimiento distribuido y la generación de perfiles continua
En la parte 1 del codelab, confirmaste que podías determinar el servicio de cuello de botella en los microservicios para una ruta de solicitud y que no podías determinar la causa exacta del cuello de botella en el servicio específico. En este codelab de la parte 2, aprendiste que la generación de perfiles continua te permite identificar el cuello de botella dentro de un único servicio a partir de las pilas de llamadas.
En este paso, revisaremos el grafo de cascada del seguimiento distribuido (Cloud Trace) y veremos la diferencia en la generación de perfiles continua.
Este gráfico de cascada es uno de los seguimientos con la consulta "love". Esto está tardando alrededor de 6.7 s (6,700 ms) en total.
Y esto ocurre después de la mejora para la misma consulta. Como puedes ver, la latencia total ahora es de 1.5 s (1,500 ms), lo que es una gran mejora con respecto a la implementación anterior.
Lo importante aquí es que, en el gráfico de cascada de seguimiento distribuido, la información de la pila de llamadas no está disponible a menos que el instrumento abarque todas partes. Además, los seguimientos distribuidos solo se enfocan en la latencia entre los servicios, mientras que la generación de perfiles continua se enfoca en los recursos informáticos (CPU, memoria, subprocesos del SO) de un solo servicio.
En otro aspecto, el seguimiento distribuido es la base de eventos; el perfil continuo es estadístico. Cada seguimiento tiene un gráfico de latencia diferente, y necesitas un formato diferente, como la distribución, para conocer la tendencia de los cambios de latencia.
Resumen
En este paso, verificaste la diferencia entre el seguimiento distribuido y la generación de perfiles continua.
8. Felicitaciones
Creaste correctamente seguimientos distribuidos con OpenTelemery y confirmaste latencias de solicitud en el microservicio de Google Cloud Trace.
Para los ejercicios extendidos, puedes probar los siguientes temas por tu cuenta.
- La implementación actual envía todos los intervalos que genera la verificación de estado. (
grpc.health.v1.Health/Check
) ¿Cómo se filtran esos intervalos de Cloud Trace? Puedes ver una pista aquí. - Correlaciona los registros de eventos con los intervalos y descubre cómo funcionan en Google Cloud Trace y Google Cloud Logging. Puedes ver una pista aquí.
- Reemplaza algún servicio por uno en otro idioma y trata de instrumentarlo con OpenTelemetry para ese idioma.
Además, si deseas aprender sobre el generador de perfiles después de esto, continúa con la parte 2. En ese caso, puedes omitir la sección de limpieza que aparece más abajo.
Realiza una limpieza
Después de este codelab, detén el clúster de Kubernetes y asegúrate de borrar el proyecto para no recibir cargos inesperados de Google Kubernetes Engine, Google Cloud Trace o Google Artifact Registry.
Primero, borra el clúster. Si ejecutas el clúster con skaffold dev
, solo debes presionar Ctrl + C. Si ejecutas el clúster con skaffold run
, ejecuta el siguiente comando:
skaffold delete
Resultado del comando
Cleaning up... - deployment.apps "clientservice" deleted - service "clientservice" deleted - deployment.apps "loadgen" deleted - deployment.apps "serverservice" deleted - service "serverservice" deleted
Después de borrar el clúster, en el panel de menú, selecciona “IAM & Administrador > “Configuración” y, luego, haz clic en “APAGAR” .
Luego, ingresa el ID del proyecto (no el nombre del proyecto) en el formulario del diálogo y confirma el cierre.