Video Summary and Transcription
El rastreo distribuido es una técnica poderosa para rastrear solicitudes y operaciones en un sistema, especialmente en aplicaciones de pila completa y microservicios. La reinvención del rastreo distribuido introduce el concepto de un rastro y abarca para capturar datos de depuración. Las mejoras incluyen etiquetas y un campo de estado para un mejor análisis, y la distribución de rastros utilizando un contexto de rastro para un rastreo continuo.
1. Introducción a la Trazabilidad Distribuida
La trazabilidad distribuida es una técnica poderosa que ayuda a rastrear el flujo y el tiempo de las solicitudes y operaciones en un sistema. Es especialmente útil para aplicaciones de pila completa y microservicios, permitiendo una mejor comprensión del rendimiento del sistema e identificación de cuellos de botella. La técnica ha estado presente desde principios de los años 2000 pero ganó popularidad en la década de 2010. A medida que las bibliotecas y los marcos evolucionaron, también lo hicieron las herramientas de depuración, desde los registros en Apache Server hasta el manejo de múltiples solicitudes en un solo proceso con hilos separados. Con la concurrencia avanzada, marcos como Node.js permiten que las solicitudes comiencen y terminen en diferentes hilos.
♪ ♪ Reconstruyendo la trazabilidad distribuida. Hola a todos. Mi nombre es Laza Nikolov, y soy un defensor del desarrollo en Sentry. Hoy en mi charla, vamos a hablar sobre la trazabilidad distribuida. Primero explicaré qué es. Luego vamos a entrar un poco en la historia de las herramientas de depuración para descubrir por qué existía la trazabilidad distribuida en primer lugar. Y luego, para entenderlo mejor, vamos a reconstruir la trazabilidad distribuida desde cero o al menos solo el concepto de ella.
Muy bien, entonces, sumerjámonos. La trazabilidad distribuida es una técnica poderosa que te permite rastrear el flujo y el tiempo de las solicitudes y operaciones a medida que fluyen a través de tu sistema. Esto es especialmente útil para la pila completa y para las aplicaciones de microservicios. La trazabilidad distribuida te ayuda a entender el rendimiento del sistema e identificar cualquier cuello de botella. Es especialmente útil para depurar errores complejos y extraños como los errores de condición de carrera que requieren mucho más que solo un bloqueo de consola y un rastreo de pila. No es nuevo por ningún medio. Hay documentos técnicos que mencionan la trazabilidad desde principios de los años 2000, pero se popularizó durante la década de 2010. Entonces, para entender por qué existe, necesitamos retroceder en el tiempo.
A medida que nuestras bibliotecas y marcos evolucionaron, también lo hicieron nuestras herramientas de depuración. Por ejemplo, en los primeros días de Apache Server, los registros eran uno de los pocos métodos para depurar. A medida que llegaban las solicitudes, Apache generaba un proceso hijo y manejaba las solicitudes. Si querías depurar lo que sucedió durante esa solicitud específica, podrías simplemente extraer los registros del proceso y verás todo el flujo de operación. Y eso funcionó. Estábamos contentos. Luego obtuvimos concurrencia básica. Piensa en IIS en ASP.NET. En lugar de generar un proceso para cada solicitud, comenzamos a manejar múltiples solicitudes en un solo proceso, pero en un hilo separado. Los registros siguen siendo un buen método de depuración, pero para aislar los registros de la solicitud, necesitamos prefijarlos con el nombre del hilo y luego filtrar los mensajes de registro basándonos en él. No es gran cosa, pero lo hicimos funcionar. Luego obtuvimos concurrencia avanzada. Nuestros marcos evolucionaron a ser asíncronos, multihilos, futuros y promesas, marcos basados en bucles de eventos. Esto es Node.js. Así que ahora nuestra solicitud puede comenzar en un hilo, pero terminar en uno diferente, pasando por muchos otros hilos en el camino.
2. Reinventando la Trazabilidad Distribuida
El prefijo de los registros con un ID único para cada solicitud ya no resuelve el problema en un sistema distribuido. Con el auge de los servicios contenerizados, los backends se distribuyen en varias máquinas, lo que dificulta el seguimiento de las operaciones. Para abordar esto, reinventamos la trazabilidad distribuida desde cero. Introdujimos el concepto de una traza, que sigue una solicitud y captura datos de depuración. Dentro de la traza, tenemos spans que representan la unidad más pequeña de trabajo, como una solicitud HTTP o una llamada a una función. Los spans pueden crear spans hijos, lo que nos permite reflejar la estructura de nuestro software. Cada span tiene un ID único y contiene datos como su ID padre.
Prefijarlos con el nombre del hilo realmente no resuelve nuestro problema ahora. Necesitamos prefijarlos con algo único para la solicitud en sí, y eso es lo que hicimos. Generamos un ID único para cada solicitud y lo prefijamos, nuestros registros.
Pero nuestros frameworks no dejaron de evolucionar. Hace unos 10 años, Docker y AWS dieron paso a los servicios contenerizados. Y ahora nuestros backends ni siquiera viven en una sola máquina. Cada contenedor y microservicio manejaba múltiples solicitudes y producía sus propios registros. Nuestros registros están por todas partes ahora. Era muy difícil entender el flujo de operaciones, por lo que necesitábamos una mejor herramienta de depuración que pudiera rastrear las operaciones a medida que saltan entre containers y servicios. Ahí es cuando la trazabilidad distribuida se convirtió en una herramienta necesaria para la depuración.
Para entender cómo funciona, vamos a reinventarlo desde cero. Dado que nuestros backends ahora tienen una naturaleza muy distribuida, necesitábamos definir un vehículo para cada solicitud que la seguirá y capturará los data de depuración en el camino. Llamemos a eso una traza. La traza comenzará cuando comience el flujo de operaciones, y va a tener un ID único. Eso puede ser el frontend, por ejemplo.
Si pensamos en los registros, generalmente nos dicen qué sucedió en un momento particular. Intentan imitar la estructura de nuestro código. Así que inventemos eso ahora. Inventemos algo que vaya a describir la unidad más pequeña de trabajo, como una solicitud HTTP o una llamada a una función o cualquier cosa específica que nuestro software haga en un momento específico. Vamos a llamar a eso un span, y vamos a crear uno inmediatamente cuando comience la traza. Eso va a ser nuestro span raíz.
Así que al igual que los registros, los spans van a imitar la estructura de nuestro software. Pero como lo estamos reinventando, hagámoslo mucho más inteligente que simples mensajes. Entonces, como los spans son la unidad más pequeña de trabajo, como una sola función, y sabemos que una función puede invocar a otra función, que a su vez también puede invocar a una tercera función, vamos a design nuestros spans para que puedan crear spans hijos, que pueden crear sus propios spans hijos y así sucesivamente. Ahora realmente podemos reflejar la estructura de nuestro software con esto. Tenemos una jerarquía de spans, pero necesitamos recordar qué span es hijo de qué span. Para hacer eso, vamos a necesitar algo para identificar cada span. Así que asignaremos un ID a cada span a medida que los creamos. También necesitamos guardar el ID del span padre. Así que vamos a crear un espacio dentro de cada span para que pueda contener data como su ID y su ID padre.
3. Mejorando los Datos de Trazabilidad y Distribuyendo la Trazabilidad
Además de capturar datos básicos sobre los spans, también podemos mantener etiquetas y un campo de estado para proporcionar más contexto y permitir un mejor análisis. Al introducir un método de finalización, podemos calcular la duración de los spans e identificar cuellos de botella de rendimiento. Para distribuir la traza en el backend, creamos un contexto de traza que concatena el ID de la traza y el ID del último span en una cadena. Esta cadena puede ser fácilmente transferida y analizada por diferentes componentes, permitiendo una trazabilidad continua.
¿Pero por qué detenernos ahí? Tenemos espacio para más data. Mantengamos un conjunto de etiquetas para que podamos buscarlas, agregarlas y agruparlas más tarde. También mantengamos un campo de estado que va a indicar si los spans funcionan, terminan con éxito o no. Básicamente podemos mantener cualquier tipo de data que pueda ser útil más adelante.
Como sabemos cuándo los creamos, introduzcamos un método de finalización que anotará cuándo terminaron los spans. Así que ahora podemos calcular cuánto tiempo tomaron los spans. Ahora tenemos suficiente información para graficarlos. Y si lo hacemos, vamos a poder identificar fácilmente los cuellos de botella de performance. Quiero decir, será obvio que ese span no debería tomar tanto tiempo.
Pero aún así, ¿cómo distribuimos esto ahora? ¿Cómo podemos continuar esta traza en el backend? Teníamos la traza y su ID. También tenemos un montón de spans adjuntos a ella. Creemos un contexto de traza que va a concatenar el ID de la traza y el ID del último span en una cadena. Ahora podemos transferir esta cadena para que nuestro backend o las próximas unidades de procesamiento puedan analizarla y continuar la trazabilidad, comenzando desde el último span. Como va a ser una cadena, podemos transferirla fácilmente, ya sea un cliente, un microservicio, un trabajo cron, o esté en JavaScript o Python o PHP, siempre y cuando pueda analizar y leer una cadena, puede continuar nuestra traza. Y eso es trazabilidad distribuida. Así es como podemos trazar todo nuestro sistema, sin importar cuán complejo sea.
Comments