paint-brush
Desempacar Cinnamon: un nuevo enfoque de resiliencia en Uberpor@bmarquie

Desempacar Cinnamon: un nuevo enfoque de resiliencia en Uber

por Bruno Marquié10m2024/01/22
Read on Terminal Reader

Demasiado Largo; Para Leer

¿Está interesado en descubrir cómo Uber redefine la resiliencia en su arquitectura de microservicios basándose en conceptos centenarios? Exploremos juntos los elementos fundamentales para comprender mejor los problemas que nos ocupan.
featured image - Desempacar Cinnamon: un nuevo enfoque de resiliencia en Uber
Bruno Marquié HackerNoon profile picture
0-item
1-item



El año pasado, el equipo de ingeniería de Uber publicó un artículo sobre su nuevo mecanismo de deslastre de carga diseñado para su arquitectura de microservicios.


Este artículo es muy interesante desde varias perspectivas. Entonces, tomé algunas notas mientras lo leía para captar mi comprensión y escribir cosas en las que me gustaría profundizar más adelante si no obtengo las respuestas al final. He descubierto varias veces que esta es la mejor manera de aprender cosas nuevas.


Lo que me atrapó desde el principio fue la referencia a principios centenarios utilizados para construir esta solución. Eso es algo que me encanta: tomar prestados conceptos/ideas de diferentes campos y adaptarlos para resolver un problema en un dominio diferente.


Si le interesan la resiliencia y la estabilidad del sistema, le recomiendo leer también el excelente libro 'Release It!' por Michael T. Nygard.


Es un libro antiguo pero bueno: un libro que profundiza en estrategias, patrones y orientación práctica para construir sistemas de software resistentes y estables, enfatizando cómo manejar las fallas de manera efectiva.




Uber ha implementado una nueva solución de deslastre de carga llamada Cinnamon que aprovecha un controlador PID (el mecanismo centenario) para decidir qué solicitudes deben ser procesadas o descartadas por un servicio en función de la carga actual del servicio y la prioridad de la solicitud.


No implica ningún ajuste a nivel de servicio (aunque tenía una pregunta al respecto), se adapta automáticamente y es mucho más eficiente que su solución anterior QALM. Recuerde también que la arquitectura de microservicios de Uber no es para los débiles de corazón…



Diagrama de cómo Cinnamon encaja en la red de servicios de Uber. (del artículo de Uber mencionado anteriormente).




¿Qué es este famoso controlador PID y cómo se utiliza?


Un controlador PID es un instrumento utilizado en aplicaciones de control industrial para regular la temperatura, el flujo, la presión, la velocidad y otras variables del proceso. Los controladores PID (derivado integral proporcional) utilizan un mecanismo de retroalimentación de bucle de control para controlar las variables del proceso y son los controladores más precisos y estables.

— https://www.omega.co.uk/prodinfo/pid-controllers.html


Si desea obtener más información sobre este concepto centenario, diríjase a Wikipedia.


Ahora, volvamos al artículo. PID significa Proporcional, Integral y Derivada. En su caso, utilizan un componente conocido como controlador PID para monitorear el estado de un servicio (solicitudes de entrada) en función de tres componentes (o medidas).

Proporcional

El término "proporcional" indica que la acción tomada es proporcional al error actual. En términos simples, esto significa que la corrección aplicada es directamente proporcional a la diferencia entre el estado deseado y el estado real. Si el error es grande, la acción correctiva es proporcionalmente grande.


Cuando un punto final está sobrecargado, la rutina en segundo plano comienza a monitorear la entrada y salida de solicitudes en la cola de prioridad.


Por lo tanto, el componente Proporcional (P) en el deslastre de carga ajusta la tasa de deslastre en función de qué tan lejos está el tamaño de la cola actual del tamaño de cola objetivo o deseado. Si la cola es mayor de lo deseado, se produce más desprendimiento; si es más pequeño, se reduce la muda.


Esa es mi comprensión.

Integral

El trabajo del controlador PID es minimizar la cantidad de solicitudes en cola, mientras que el trabajo del sintonizador automático es maximizar el rendimiento del servicio, sin sacrificar (demasiado) las latencias de respuesta.


Si bien el texto no menciona explícitamente "Integral (I)" en el contexto del tamaño de la cola, indica que la función del controlador PID es minimizar la cantidad de solicitudes en cola. La minimización de solicitudes en cola se alinea con el objetivo del componente Integral de abordar los errores acumulados a lo largo del tiempo.


Para determinar si un punto final está sobrecargado, realizamos un seguimiento de la última vez que la cola de solicitudes estuvo vacía y, si no se ha vaciado en los últimos 10 segundos, consideramos que el punto final está sobrecargado (inspirado en Facebook).


En el deslastre de carga, puede estar asociado con decisiones relacionadas con el comportamiento histórico de la cola de solicitudes, como el tiempo desde la última vez que estuvo vacía.


Sinceramente, eso no lo tengo del todo claro. Es un poco frustrante, debo decir. Si bien mencionan aprovechar un mecanismo centenario, hubiera sido útil si indicaran explícitamente qué parte corresponde a qué o cómo funciona. No quiero disminuir el valor de su increíble artículo. Esa es solo mi queja aquí... Después de todo, soy francés...;)

Derivado

Creo que éste es más fácil de identificar.


En un controlador PID (Proporcional-Integral-Derivativo) clásico, la acción "Derivada (D)" es particularmente útil cuando se desea que el controlador anticipe el comportamiento futuro del sistema en función de la tasa actual de cambio del error. Ayuda a amortiguar las oscilaciones y mejorar la estabilidad del sistema.


En el contexto del deslastre de carga y el controlador PID mencionados en el artículo, es probable que se emplee el componente derivado para evaluar qué tan rápido se está llenando la cola de solicitudes. Al hacerlo, ayuda a tomar decisiones destinadas a mantener un sistema estable y evitar cambios repentinos o impredecibles.


El componente rechazador tiene dos responsabilidades: a) determinar si un punto final está sobrecargado y b), si un punto final está sobrecargado, descartar un porcentaje de las solicitudes para asegurarse de que la cola de solicitudes sea lo más pequeña posible. Cuando un punto final está sobrecargado, la rutina en segundo plano comienza a monitorear la entrada y salida de solicitudes en la cola de prioridad. Con base en estos números, utiliza un controlador PID para determinar una proporción de solicitudes a descartar. El controlador PID es muy rápido (ya que se necesitan muy pocas iteraciones) para encontrar el nivel correcto y una vez que se ha vaciado la cola de solicitudes, el PID garantiza que solo reduzcamos lentamente la relación.


En el contexto mencionado, el controlador PID se utiliza para determinar la proporción de solicitudes que se deben descartar cuando un punto final está sobrecargado y monitorea la entrada y salida de solicitudes. El componente derivado del controlador PID, que responde a la tasa de cambio, está implícitamente involucrado en la evaluación de qué tan rápido se llena o vacía la cola de solicitudes. Esto ayuda a tomar decisiones dinámicas para mantener la estabilidad del sistema.



Tanto el componente Integral como el Derivado participan en el seguimiento del comportamiento de la cola de solicitudes a lo largo del tiempo.

  • Componente integral


En el contexto de determinar la sobrecarga, el componente integral podría estar asociado con el seguimiento de cuánto tiempo ha estado la cola de solicitudes en un estado no vacío. Esto se alinea con la idea de acumular la integral de la señal de error a lo largo del tiempo.

"Integral: según el tiempo que la solicitud ha estado en la cola..."


  • Componente derivado


El componente derivado, por otro lado, está relacionado con la tasa de cambio. Responde a la rapidez con la que cambia el estado de la cola de solicitudes.


“Derivada: rechazo basado en qué tan rápido se llena la cola…”


Si bien tanto el aspecto integral como el derivado implican observar la cola de solicitudes, se centran en diferentes aspectos de su comportamiento.

El componente Integral enfatiza la duración del estado no vacío, mientras que el componente Derivado considera la velocidad a la que cambia la cola.


Al final del juego, utilizan estas tres medidas para determinar el curso de acción para una solicitud.


La pregunta que tengo es cómo combinan estos tres componentes, en todo caso. También tengo curiosidad por saber cómo los monitorean.


Sin embargo, creo que tengo la idea...


¿Qué solicitud deciden archivar en función de estos 3 componentes y cómo?

El punto final en el borde está anotado con la prioridad de la solicitud y esto se propaga desde el borde a todas las dependencias posteriores a través de Jaeger . Al propagar esta información, todos los servicios en la cadena de solicitud conocerán la importancia de la solicitud y cuán crítica es para nuestros usuarios.


El primer pensamiento que me viene a la mente es que se integraría perfectamente en una arquitectura de malla de servicios.


Aprecio el concepto de emplear encabezados y seguimiento de servicios distribuidos para propagar la prioridad de las solicitudes. En este sentido, ¿por qué optar por una biblioteca compartida con esta dependencia agregada a cada microservicio, en lugar de colocarla fuera del servicio, tal vez como un complemento de Istio? Teniendo en cuenta los beneficios que ofrece: ciclos de lanzamiento/implementación independientes, soporte políglota, etc.


Aquí hay algunas ideas adicionales:


  • ¿Existe la posibilidad de que, con el tiempo, la prioridad de la solicitud se degrade o cambie en función de cambios contextuales, como el inicio de una tarea o proceso por lotes no planificado de alta prioridad, o alteraciones a nivel de zona horaria/región?


  • ¿Cómo se relaciona este enfoque con otros enfoques/patrones de gestión de resiliencia "estándar", como disyuntores, tiempos de espera, arrendamientos, mamparos, etc.?


  • Me interesa comprender hasta qué punto un servicio decide (posee la decisión) descartar solicitudes en función de la prioridad de la solicitud de entrada. Mencionan una falta de configuración (un aspecto que me gustaría explorar más a fondo), por lo que no es específico del servicio y es completamente independiente de la implementación del servicio, algo transparente. Esto recupera el punto de la malla de servicios/ciclos de lanzamiento independientes frente a la biblioteca dependiente.


Bueno, soy parcial, ya que no soy un gran admirador de las bibliotecas compartidas, aunque sólo sea porque creo que complican el proceso de lanzamiento/implementación. Sin embargo, no estoy seguro de si hay que considerar un aspecto de configuración específico del servicio. ¿Quizás configuran cuánto tiempo debe esperar el servicio para comenzar a procesar una consulta y completarla?


El diagrama muestra cómo una solicitud atraviesa Cinnamon, antes de enviarse a la lógica empresarial para su procesamiento real. (del artículo de Uber mencionado anteriormente).


Quizás un aspecto que valga la pena probar es el proceso de toma de decisiones del eyector.


Por lo que tengo entendido, determina si se rechaza una solicitud según el controlador PID, que está localizado en el servicio. ¿Existe una opción para un enfoque más global? Por ejemplo, si se sabe que uno de los servicios descendentes en la tubería está sobrecargado (debido a su propio controlador PID), ¿podría cualquier servicio ascendente decidir rechazar la solicitud antes de que llegue a este servicio sobrecargado (que podría estar n pasos más abajo en el proceso)? camino)?


Esta decisión podría basarse en el valor devuelto por el controlador PID o el sintonizador automático del servicio descendente.



Ahora, estoy reflexionando sobre varios aspectos mencionados mientras concluyen el artículo y brindan algunos números para mostrar la eficiencia de su sistema, lo cual es bastante impresionante.


Latencias P50 para solicitudes de alta prioridad, nivel 1, para las tres configuraciones en diferentes RPS entrantes. (del artículo de Uber mencionado anteriormente).


Mencionan en algún momento que "Cada solicitud tiene un tiempo de espera de 1 segundo".


Realizamos pruebas de 5 minutos, donde enviamos una cantidad fija de RPS (por ejemplo, 1000), donde el 50% del tráfico es de nivel 1 y el 50% es de nivel 5. Cada solicitud tiene un tiempo de espera de 1 segundo.


Es común en los sistemas distribuidos asociar una solicitud con un tiempo de vencimiento o fecha límite específica, siendo cada servicio a lo largo de la ruta de procesamiento responsable de hacer cumplir este límite de tiempo. Si se alcanza el tiempo de vencimiento antes de que se complete la solicitud, cualquier servicio de la cadena tiene la opción de cancelar o rechazar la solicitud.


Supongo que este tiempo de espera de 1 segundo está adjunto a la solicitud y que cada servicio, dependiendo de dónde nos encontremos en este plazo, puede decidir cancelar la solicitud. Esta es una medida que es global porque se agrega a través de los servicios. Creo que resuena con el punto que estaba planteando antes acerca de tener una visión global del estado completo del sistema o de las dependencias para decidir cancelar la solicitud lo antes posible si no tiene la posibilidad de completarse debido a uno de los servicios de la lista. camino.


¿Podría devolverse el "estado" de los servicios posteriores (que comprenden datos de sus controladores PID locales) como encabezados adjuntos a las respuestas y usarse para construir un disyuntor/mecanismo de desconexión preventiva temprana más evolucionado?




Finalmente, tengo curiosidad por saber más sobre el enfoque anterior porque, según la descripción dada en este artículo, parece sólido.


Cuando se examinan las medidas de goodput y latencias, no hay duda de cuál, QALM o Cinnamon, funciona mejor. Tenga en cuenta que mencionan un enlace al enfoque QALM en el artículo. Probablemente debería empezar desde allí ;)


Como siempre, estos enfoques no son para todos. La arquitectura y la carga de Uber son propias. De hecho, estoy impaciente por leer los próximos artículos de esta serie, específicamente para aprender más sobre el controlador PID y el sintonizador automático.


Con Cinnamon hemos creado un deslastre de carga eficiente que utiliza técnicas centenarias para establecer dinámicamente umbrales para rechazar y estimar la capacidad de los servicios. Resuelve los problemas que notamos con QALM (y, por lo tanto, con cualquier deslastre de carga basado en CoDel), es decir, que Cinnamon es capaz de:


- Encuentre rápidamente una tasa de rechazo estable


- Ajustar automáticamente la capacidad del servicio.


- Ser utilizado sin establecer ningún parámetro de configuración.


- Incurrir en gastos generales muy bajos


Lo interesante de este enfoque es que consideran todas las solicitudes a procesar para decidir qué hacer con cada nueva solicitud de entrada, ya que utilizan una cola (prioritaria). Como se mencionó, tengo curiosidad por saber si el mecanismo también podría tener en cuenta la salud de todos los servicios dependientes en función de las mismas medidas PID...


Hay otros aspectos interesantes en este artículo, como cómo miden el efecto de sus estrategias y la comparación con el enfoque anterior. Sin embargo, no requiere de mi parte notas más detalladas que las ya presentadas. Por lo tanto, le recomiendo encarecidamente que lea el artículo original .



¿Encontró útil este artículo? ¡Sígueme en Linkedin , Hackernoon y Medium ! ¡Por favor 👏 este artículo para compartirlo!


También publicado aquí.