Arquitectura de microservicios - Parte 4: Load Balancing & Circuit Breaker

Publicado por Daniel Sánchez el

Arquitectura de SolucionesMicroserviciosLoad BalancingCircuit Breaker

En esta entrega vamos a implementar 2 patrones característicos en arquitecturas de microservicios:

  • Load Balancing: El objetivo es consumir un servicio de forma balanceada entre varias instancias desplegadas del mismo. La idea es que en un escalado elástico el consumo balanceado sea transparente para nosotros. En este punto Eureka será la pieza clave.
  • Circuit Breaker: Mediante Histryx implementaremos nuestro mecanismo de tolerancia a fallos.



Para ilustrar estas ideas vamos a implementar el siguiente escenario basándonos en los componentes construidos en los anteriores posts:



El microservicio Deneb consumirá a Sadr (implementado en el segundo post) utilizando RESTtemplate de Spring que integra Ribbon como cliente con capacidad de balanceo. A continuación desplegaremos 2 o más instancias de Sadr para comprobar el balanceo de carga.
Ambos servicios obtendrán su configuración remotamente del Config Server y se registrarán en Eureka en tiempo de bootstrap.

Todo el código puede descargarse desde el repositorio cygnus y los ficheros de configuración de   cygnus-config.

Implementaremos el microservicio Deneb de igual manera que Sadr, mediante Spring boot y Spring MVC. El siguiente será el controlador:

@Api
@RestController
@RequestMapping("deneb/")
public class DenebController {

    private static Logger logger = LoggerFactory.getLogger(DenebController.class);

    @Autowired
    private SadrClient sadrClient;

    @RequestMapping(method=RequestMethod.POST, value="pingSadr")
    @ApiOperation(value = "pingSadr", nickname = "pingSadr",response = PingResponse.class)
    @ApiResponses(value = { 
            @ApiResponse(code = 200, message = "Success", response = String.class),
            @ApiResponse(code = 400, message = "Bad Request"),
            @ApiResponse(code = 404, message = "Not Found"), 
            @ApiResponse(code = 500, message = "Failure") 
            })
    public PingResponse pingSadr(@ApiParam(value = "request", required = true) @RequestBody(required=true) PingRequest request) {

        logger.debug("--> ping received - id: {} - content: {}", request.getId(), request.getMessage());
        return sadrClient.pingSadr(request);
    }
}

Hemos definido el bean SadrClient que implementa el cliente para consumir a Sadr:

@ConfigurationProperties(prefix="cygnus.sadr")
public class SadrClient {

    private static Logger logger = LoggerFactory.getLogger(SadrClient.class);

    private String pingUrl;

    @Autowired
    private RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod="retrieveFallbackPingSadr")
    public PingResponse pingSadr(PingRequest pingRequest){

        logger.debug("--> pingSadr received - id: {} - content: {}", pingRequest.getId(), pingRequest.getMessage());
        logger.debug("--> sadr endpoint: {}",pingUrl);
        return restTemplate.postForObject(pingUrl, pingRequest, PingResponse.class);
    }

    public PingResponse retrieveFallbackPingSadr(PingRequest pingRequest){
        return new PingResponse("Error pinging sadr. This is a fallback message");
    }
}

Hemos anotado el método pingSadr con @HystrixCommand para proteger con circuit breaker nuestro servicio cuando Sadr esté caído. Tenemos que definir un método de fallback que se ejecutará cuando esto ocurra.
Para utilizar Hystrix tenemos que añadir la siguiente dependencia a nuestro pom:

<dependency>  
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>  

La url de la operación que queremos invocar será inyectada en el environment de Spring por configuración gracias a la anotación @ConfigurationProperties. Este es su fichero de configuración:

deneb-service.yml:

server:  
  port: ${PORT:2222}

eureka:  
  client:
    serviceUrl:
      defaultZone: http://discovery-server:1111/eureka/

cygnus:  
  sadr:
    name: sadr-service
    pingUrl: http://${cygnus.sadr.name}/sadr/ping/
  • server.port: indica el puerto donde queremos que escuche el servicio. Su valor será provisionado por una variable de sistema o por parámetro (en ausencia de valor tomará 2222 por defecto).
  • eureka.client.serviceUrl.defaultZone: define el endpoint de Eureka.
  • cygnus.sadr.ping.url: establece la url de la operacion ping de sadr que queremos invocar.

Hemos utilizado RestTemplate de Spring que ya integra Ribbon como cliente con capacidad de balanceo. Para ello utilizamos la anotación @LoadBalanced sobre restTemplate:

@Configuration
class DenebConfig {

    @Bean 
    SadrClient sadrClient(){
        return new SadrClient();
    }

    @Bean
    @LoadBalanced
    RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

Quien realiza el balanceo realmente es el cliente (Ribbon), a través de un algoritmo round robbin sobre una lista de endpoints que ha obtenido previamente consultando a Eureka. Todo esto será transparente para nosotros.

Play Time!

A continuación vamos comprobar que Deneb es capaz de consumir de forma balanceada varias instancias de Sadr.



Arrancamos Configserver y Eureka: mvn spring-boot:run, en sus respectivos directorios.

Arrancamos un par de instancias de Sadr pasando el puerto por parámetro: mvn spring-boot:run -DPORT=3331 y mvn spring-boot:run -DPORT=3332

Arrancamos Deneb: mvn spring-boot:run (por defecto arrancará en el puerto 2222)

Podemos echar un vistazo al dashboard de Eureka para comprobar que los servicios se han registrado:
http://localhost:1111/



Ahora podemos utilizar la interface Swagger de Deneb para invocar la operación pingSadr:
http://localhost:2222/swagger-ui.html

En el response body podemos ver que ha respondido la instancia de Sadr que está escuchando en el puerto 3331. Si lanzamos la request sucesivas veces veremos como nos responden ambas instancias alternativamente.
De igual manera, si paramos las instancias de Sadr veremos el mensaje que hemos definido en el método de fallback con Hystrix.

En sucesivos posts iremos complicando y completando el escenario añadiendo más componentes de la infraestructura de Netflix como:

  • Monitorización (Hystrix & Turbine)
  • Edge Server (Zuul)


Bonus:

En el repositorio cygnus se incluye el proyecto adminServer basado en spring-boot-admin que se integra con Eureka y explotando la información proporcionada por los endpoints de Actuator en los microservicios, provee de una interface web rica, amigable y para toda la familia donde se puede consultar el estado de los microservicios, métricas, environment, etc...



Si te gusta esta serie, ¡no te olvides de seguirnos en Twitter!