Como se ha comentado en secciones previas, las reglas de balanceo son las encargadas de escoger la instancia a la que se enviará la petición, pero entre qué instancias se aplicará la regla está determinado previamente por el balanceador y los filtros de balanceo.

Este balanceador y sus filtros implementan lógica de zonas, es decir, su función es determinar qué zona o zonas son válidas para enviar las peticiones. Una vez escogidas las zonas se aplicará las reglas de balanceo entre todas las instancias que componen dichas zonas. La lógica de selección de zonas, en la que profundizaremos más adelante, se basa en descartar zonas que no cumplan unas condiciones mínimas (carga, número de instancias...) y en intentar fomentar la afinidad de zona, esto es, escoger la zona en la que se encuentra la instancia que va a realizar la petición.

El proceso de selección de zonas se compone básicamente de dos subprocesos:

  1. Cada treinta segundos DynamicServerListLoadBalancer, clase padre de ZoneAwareLoadBalancer, lanza un proceso que ejecutará el filtro ZonePreferenceServerListFilter de cara a dar preferencia a la zona en la que se encuentra la instancia que realiza la petición. Como resultado de este proceso tendremos un listado de zonas que se utilizará en el siguiente paso.
  2. Cada vez que se realiza una petición ésta pasará por el ZoneAwareLoadBalancer que utilizará el resultado realizado en el proceso 1, para, en caso de que se haya seleccionado más de una zona, evaluar cada una de ellas de cara a descartar las que no cumplan unas condiciones mínimas. De entre las zonas restantes se seleccionará una por medio de un proceso pseudoaleatorio y se aplicará la regla de balanceo entre las instancias que compongan dicha zona.

En esta sección analizaremos el proceso anteriormente mencionado en el punto 1 por el que se dará prioridad a la zona en la que se encuentra la instancia que realiza la petición.

ZonePreferenceServerListFilter extiende de la clase ZoneAffinityServerListFilter. Aquí es importante distinguir que la primera fue desarrollada por el equipo OSS de Spring Cloud-Netflix y la segunda por el equipo de Netflix, ya que proporcionan una lógica ligeramente diferente.

Este proceso de selección de zona es lanzado cada 30 segundos por el DynamicServerListLoadBalancer, clase padre del ZoneAwareLoadBalancer.

ZonePreferenceServerListFilter en la primera fase del proceso pedirá a su clase padre ZoneAffinityServerListFilter que obtenga la lista de servidores en base a afinidad de zona. ZoneAffinityServerListFilter, en primera instancia utilizará el predicado ZoneAffinityPredicate para encontrar de entre todas las instancias del microservicio destino las que se encuentran en la misma zona que la instancia que realiza la petición. A partir de ahí el filtro evalúa la zona en base a tres criterios:

  1. Cantidad de instancias disponibles en la zona (propiedad security.ribbon.zoneAffinity.minAvailableServers). Por defecto exige tener dos instancias disponibles como mínimo.
  2. Carga media de las instancias (propiedad security.ribbon.zoneAffinity.maxLoadPerServer). Por defecto exige que sea inferior al 60%.
  3. Porcentaje de instancias no disponibles (propiedad security.ribbon.zoneAffinity.maxBlackOutServesrPercentage). Por defecto exige que esté por debajo del 80%.

En caso de que la zona no cumpla los mínimos determinados no se aplicará la afinidad de zona y en dicho caso se devolverán todas las instancias disponibles correspondientes a todas las zonas. En caso de que la zona sí que cumpla estos criterios mínimos, el listado de instancias disponibles se compondrá con las existentes en dicha zona.

Una vez obtenido este resultado ZonePreferenceServerListFilter aplica una segunda fase de filtrado sobre el resultado devuelto por ZoneAffinityServerListFilter que consiste en que solo se escogerán las instancias correspondientes a la misma zona de la instancia que realiza la petición, o se devolverán todas sin filtrar en caso de que no haya ninguna en la misma zona.

Aquí conviene pararse un momento a analizar las diferencias entre ambos. ZoneAffinityServerListFilter es el filtro desarrollado por Netflix que busca la afinidad de zona, pero exigiendo unos mínimos criterios a dicha zona y el cual se puede deshabilitar (propiedad security.ribbon.EnableZoneAffinity). En contraposición, ZonePreferenceServerListFilter fue desarrollado por Spring Cloud-Netflix (como se puede ver por su nombre de paquete) y escogerá siempre la zona en la que se encuentra la instancia que realiza la petición en caso de tener alguna instancia del microservicio invocado, independientemente de si esta zona cumple o no ciertos criterios. Además, no es configurable, por lo que siempre se dará preferencia a la zona de la instancia que realiza la llamada. Así ZonePreferenceServerListFilter sobreescribirá el comportamiento de ZoneAffinityServerListFilter por mucho que internamente lo invoque.

Esto provoca que si tenemos una o varias instancias en la misma zona que la que realiza la petición todas las peticiones se dirigirán a esas instancias, descartando las de las demás zonas. Tiene sus ventajas, para las cuales fue diseñada, como la reducción del tiempo de respuesta debido a la red. Pero también presenta defectos como es el hecho de que al no tener en cuenta ningún otro factor más allá de la localización, podríamos encontrarnos en una situación con las instancias de una zona sobrecargadas por peticiones mientras no se deriva ninguna petición a las otras zonas. Por tanto consideramos que este comportamiento no es el óptimo y que lo mejor será utilizar directamente como filtro ZoneAffinityServerListFilter que sí que tiene en cuenta otras consideraciones (en la sección ‘Configuración avanzada de Ribbon’ se explica cómo indicar qué filtro utilizar).

Su finalidad es evaluar las diferentes zonas disponibles en base al número medio de peticiones activas y al número de instancias disponibles, y en función de dichas métricas descartar la peor zona.

ZoneAwareLoadBalancer extiende la clase DynamicServerListLoadBalancer que se caracteriza por ser un balanceador que permite un cambio dinámico del listado de instancias que componen un microservicio. Dicho cambio dinámico es la evaluación periódica que comentamos en el apartado anterior realizada por los filtros. Así mismo, DynamicServerListLoadBalancer extiende BaseLoadBalancer que extiende a su vez AbstractLoadBalancer que implementará la interfaz ILoadBalancer conformando así la jerarquía de clases del balanceador.

ZoneAwareLoadBalancer se ejecutará cada vez que se recibe una petición. Utiliza como entrada el listado de zonas resultado del proceso realizado previamente por los filtros. De esta forma nos encontramos con dos posibles situaciones a la entrada:

  1. El proceso de filtrado ha devuelto como resultado una única zona: ya sea porque es la zona afín, porque solo hay instancias del microservicio destino en esa zona o porque las demás zonas han sido descartadas.
  2. El proceso de filtrado ha devuelto como resultado varias zonas: en este caso podemos asumir que no se ha aplicado la afinidad de zona, ya que si fuera así solo podría haber una.

Si nos encontramos en la situación 1 el ZoneAwareLoadBalancer no aplicará ningún tipo de lógica y derivará directamente todas las instancias que componen esa zona a la regla de balanceo para que decida a cual invocar.

Si nos encontramos en la situación 2, ZoneAwareLoadBalancer aplicará lógica de selección de zonas. Para ello utilizará la clase ZoneAvoidanceRule (ya explicada en la sección ‘Reglas de balanceo’) para determinar la zona a descartar en base al porcentaje de instancias no disponibles y al número de peticiones activas por instancia para cada zona (esta lógica es aplicada independientemente de cual sea luego la regla configurada para elegir entre instancias).

El porcentaje de instancias no disponibles se puede establecer con la propiedad ZoneAwareNIWSDiscoveryLoadBalancer.security.avoidZoneWithBlackoutPercetage que por defecto tiene el valor 0.99999 de forma que todas las instancias tendrán que estar no disponibles para que se descarte la zona. El número de peticiones activas por instancia se establece con la propiedad ZoneAwareNIWSDiscoveryLoadBalancer.security.triggeringLoadPerServerThreshold cuyo valor por defecto es 0.2. Este proceso descartará las zonas que no cumplan dichas condiciones. En caso de que ninguna lo haga se descartará la peor.

Si el resultado posterior a este proceso está compuesto por una única zona, se enviarán las instancias de ésta a la regla de balanceo para determinar cual invocar. En caso de que el resultado sean varias zonas se escogerá una de las mismas de forma aleatoria distribuyendo las posibilidades de forma acorde al número de instancias existentes en cada zona.

La lógica de evaluación y descarte de zonas se puede deshabilitar con la propiedad ZoneAwareNIWSDiscoveryLoadBalancer.enabled, de esta forma la regla de balanceo siempre se aplicará sobre las zonas resultantes del proceso realizado por los filtros.

MicroS4 ZALB-animated 390 +acelerado5

Como hemos comentado anteriormente, este comportamiento de descarte de zonas no podrá ser evaluado con la configuración por defecto, ya que el ZonePreferenceServerListFilter escogerá siempre la zona en la que se encuentre la instancia que ha realizado la petición. Para evitar esto existen dos posibilidades, o que todas las instancias del microservicio destino estén en zonas que no sean la de la instancia del microservicio que va a realizar la petición o, teniendo en cuenta que el ZonePreferenceServerListFilter no se puede deshabilitar, no configurar ningún filtro. La imagen que se muestra a continuación contiene la configuración necesaria para no utilizar ningún filtro. De esta forma se podrá probar la lógica de descarte de zonas:

MicroS4 12

De la misma forma, si se quieren realizar pruebas, se recomienda cambiar la configuración por defecto, para que por ejemplo no sea necesario tener como mínimo dos instancias por zona para que ésta sea considerada como válida por el ZoneAffinityServerListFilter. La siguiente imagen muestra una configuración con la finalidad de identificar propiedades relevantes de cara a la gestión de zonas:

MicroS4 13

Sobre esta configuración hay que mencionar que en las propiedades que se encuentran definidas a partir de la línea 12, las referencias a security se deben a que nuestro microservicio destino se llama security, por lo que cada uno deberá indicar el nombre del microservicio al que se va a invocar. Esto nos permite definir configuración de balanceo diferentes para cada microservicio que queremos invocar. En caso de querer establecer una configuración común para todos los microservicios a invocar obviaremos el nombre del microservicio en la propiedad (ribbon.EnableZoneAffinity en lugar de security.ribbon.EnableZoneAffinity).

Conclusiones

Como hemos visto Ribbon está diseñado completamente con una filosofía cloud, teniendo en cuenta que la lista de instancias puede cambiar dinámicamente, considerando el concepto de zona y cómo éste afecta a las peticiones realizadas. Además se integra con otras librerías y microservicios de Spring Cloud-Netflix como Eureka e Hystrix. La variedad de reglas de balanceo existentes y la posibilidad de definir nuestras propias reglas, filtros y balanceadores convierten a Ribbon en una poderosa y versátil librería para realizar balanceo de carga en cliente.

Fuentes

Analizamos Ribbon a fondo en 3 entregas:

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete

Estamos comprometidos.

Tecnología, personas e impacto positivo.