En una aplicación monolítica todo el sistema está centrado en un mismo lugar/nodo. En este tipo de arquitectura es común monitorear el sistema de llamadas método a método. Se verifica el stack: método 1 → método 2 → método 3 → *, este proceso en una misma aplicación con técnicas como la programación orientada a aspectos (por ejemplo), posibilita poder tracear el monolito para detectar problemas de rendimiento, demoras en ejecución de llamadas, etc.

Cuando el monolito se descompone es necesario aplicar otras técnicas. En los microservicios una petición entra por un primero servicio y en dependencia del grado de relaciones puede recorrer docenas de servicios antes de devolver una respuesta a la aplicación cliente. Este camino de llamadas servicio a servicio requiere ser monitoreado para detectar anomalías, especialmente latencias entre las llamadas.

Para ayudar en la detección de estos problemas que tienen lugar en la red, se aplica el patrón de traceo distribuido.

Los orígenes de esta práctica inician en el 2010 con un primer artículo de Google sobre Dapper, que es la base de la mayoría de los sistemas de traceo distribuido del mercado.

Si deseas ver un video explicativo y con ejemplos, te dejo por acá.

Los principales conceptos de traceo distribuido son:

  • Trace: Es el elemento que marca el hilo conductor de una traza, cuando se inicia una petición contra un microservicio se crea un identificador de traza que acompaña ese request/response hasta que se da una respuesta final.
  • Span: Dentro de la trayectoria que pasa una petición por cada llamada se coloca un identificador de aplicación, llamado spanId.
  • Anotaciones: Son usadas para grabar un evento que tiene lugar en la comunicación. Las anotaciones empleadas son:
    • Client Sent (cs): El cliente a iniciado una petición (se inicia el span).
    • Server Received (sr): El servidor (microservicio invocado) recibe la petición, en este punto se calcula la latencia de la red, resultando ser la diferencia entre el tiemposr – tiempocs.
    • Server Sent (ss): Marca la respuesta, osea envío al cliente de la respuesta desde el servidor. Esta anotación permite calcular el tiempo de procesamiento de la operación, que es tiemposs – tiemposr.
    • Client Received (cr): Pone un fin al span, determine que el cliente a recibido la petición y permite calcular el tiempo total de la llamada, siendo tiempocr-tiempocs.

Cuando el servicio MS1 llama a MS2 el sistema de traceo distribuido lo que está midiendo son los parámetros asociados a los tiempos de respuesta de MS2. MS1 actúa como cliente y MS2 actúa como servidor.

En la siguiente tabla se muestra un resumen de los valores calculados por el sistema de traceo.

Resumen de anotaciones

Una llamada posee un traceId y un spanId por cada par de servicios, antes y después de producirse una invocación entre servicios se anotan las peticiones para conocer exactamente cuándo tienen lugar.

La siguiente figura muestra el proceso de llamada y cómo interviene el traceo distribuido.

Elementos del traceo en llamadas servicio/servicio

Para que el traceId y el spanId se propaguen correctamente entre el origen y el destino se usan normalmente las cabeceras HTTP, añadiendo estos elementos en los headers. Librerías de traceo especializadas en algunas plataformas realizan este proceso de forma casi transparente.

Adicionalmente para implementar este patrón comúnmente se tienen que tener en cuenta cuatro factores fundamentales:

  1. Librería de comunicación: Es el elemento que realiza la llamada de servicio a servicio, el cual debe tener la capacidad de transmitir el traceId y spanId entre los servicios, en Java y compatibles con Spring Cloud tenemos RestTemplate (cliente rest nativo de Spring) y Feign (para invocar servicios al estilo declarativo), este último con capacidades de balanceo de carga del lado del cliente, integrable con el servicio de descubrimiento y muchas otras ventajas.
  2. Librería de traceo: Es, dentro de cada servicio el componente que añade las anotaciones y realiza los cálculos mostrados en la tabla anterior. Este es el componente principal del traceo distribuido. Dentro del ecosistema de Spring Cloud se cuenta con Sleuth.
  3. Mecanismo de comunicación: Es el encargado de transmitir el flujo de datos obtenido del traceo hacia un lugar para su almacenamiento y visualización. Aunque este proceso puede ser síncrono es común usar un broker de comunicación de cola de mensajes para encolar los datos. La librería de traceo que captura los datos debe enviarlos hacia este mecanismo de comunicación. En ocasiones el mecanismo de comunicación puede evitarse y conectar directamente la librería de traceo con el receptor.
  4. Receptor: Es el lugar donde se reciben los datos, puede incluir una herramienta de visualización especializada o ser parte independiente.

La implementación del sistema de traceo distribuido puede variar en dependencia de las tecnologías elegidas. Se recomienda en aplicaciones que posean un alto grado de throughput usar un broker de mensajería para desacoplar y realizar de forma asíncrona el envío de la operaciones de traceo.

Cuando usar este patrón

El uso de traceo distribuido añade nuevos elementos a la infraestructura, y puede llegar a ser costoso de mantener. El traceo distribuido aunque es opcional su implementación debe ser tenida en cuenta siempre cuando crece el número de servicios.

Software recomendado

Existen muchas aplicaciones para llevar a cabo el traceo distribuido, por la experiencia recomendamos: Spring Cloud Sleuth, Spring Cloud Sleuth Stream, RabbitMQ (broker), ElasticSearch (almacenamiento), Zipkin (monitoreo y visualización). De manera general debe cumplirse siempre con el estándar de OpenTracing. Otras tecnologías a evaluar son Jaeger (de Uber) y ElasticAPM.

Espero te haya gustado el artículo si quieres aprender mas te recomiendo nuestro libro que habla sobre este y otros +20 patrones de microservicios, lee todo al respecto acá.