Introducción a la cache distribuida con Hazelcast & Spring Boot

Una de las características más deseables de los sistemas es su capacidad de respuesta en corto tiempo. La velocidad de respuesta es uno de los elementos que comúnmente se miden en el APDEX de las aplicaciones.

La realidad es que en la actualidad los sistemas cada vez son más distribuidos para soportar los grandes volúmenes de tráfico y ofrecer una respuesta cada vez más cercana a tiempo real.

En la época de los microservicios, es común el uso de balanceadores de carga con implementaciones (por ejemplo) de tipo Round Robbin, que a partir de un pool de instancias de un mismo microservicio reparten peticiones de forma pareja para balancear las operaciones.

Es común además que muchos datos que se entregan por las APIs en ocasiones tienen una variabilidad poco probable en el corto tiempo, o simplemente no varían como pueden ser datos de información de los usuarios, información de productos en venta, noticias publicadas, etc.

Cuando la información es poco variable, lo lógico para mejorar tiempos de respuesta es “no buscarla” siempre al sistema de persistencia, en su lugar se trae una vez y se guarda en una memoria temporal llamada “Cache”, dicho sistema es uno de los patrones usados en una arquitectura de microservicios, se denomina Cache Aside.

Hazelcast

Muchas son las soluciones que se pueden emplear para implementar un sistema de cache, probablemente las más populares sean Memcache y Redis, las cuales ofrecen un buen rendimiento. En este artículo vamos a hablar de una un poco menos popular, pero que ofrece un gran performance y una excelente integración con Spring Boot. Por cierto el rendimiento en cache con hazelcast en pequeñas pruebas realizadas es superior a memcache, nos falta escalarlo a ver los resultados con cargas considerables.

Lo cierto es que hoy hablaremos de cache, pero Hazelcast es mucho más que un sistema para manejar cache, todas las características de Hazelcast, pueden ser revisadas en su web oficial.

Integración con Spring Boot, al código

Como otros sistemas de gestión de cache, la integración con Spring Boot se realiza a través de la anotación @Cacheable de org.springframework.cache.annotation.Cacheable, para enlazar y decirle a Spring Boot que use como Cache a Hazelcast debemos añadir las siguientes dependencias.

<dependency>
  <groupId>com.hazelcast</groupId>
  <artifactId>hazelcast</artifactId>
</dependency>
<dependency>
  <groupId>com.hazelcast</groupId>
  <artifactId>hazelcast-spring</artifactId>
</dependency>

A nivel de configuración de properties de Spring para lo básico podemos no hacer nada, solo debemos añadir una clase de configuración @Configuration e inyectar el Bean de configuración.

package com.yoandypv.hazelcast.cache;
import com.hazelcast.config.Config;
import com.hazelcast.config.EvictionPolicy;
import com.hazelcast.config.MapConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HazelcastConfig {
    @Bean
    public Config hazelCastConfig() {
        Config config = new Config(); //1
        config.setInstanceName("hazelcast-cache"); //1
        config.setProperty("hazelcast.rest.enabled", "true"); //1
        MapConfig allUsersCache = new MapConfig(); //2
        allUsersCache.setTimeToLiveSeconds(10); //2
        allUsersCache.setEvictionPolicy(EvictionPolicy.LFU); //2
        config.getMapConfigs().put("map",allUsersCache); //2
        return config;
    }
}

En el bloque 1 creamos las configuraciones generales de la cache donde definimos la instancia.

En el bloque 2 definimos las configuraciones especificas de un elemento de cache, en el ejemplo para la cache que crearemos bajo la llave “map”, para esta cache definimos que su tiempo de vida será de 10 segundos y que la política será EvictionPolicy.LFU, que significa la memoria frecuente usada será desalojada al pasar ese tiempo.

Guardando en cache dentro de nuestro código

Veamos un servicio ahora, donde hacemos una operación y cacheamos el resultado, este servicio tiene una función que cachea a “map”, por lo que la política definida a map se le aplica.

package com.yoandypv.hazelcast.service;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.UUID;
@Service
@CacheConfig(cacheNames = "map")
public class PruebaService {
    @Cacheable(value = "map")
    public String getData(String key) {
        return generateUUID(key).toString();
    }
    public UUID generateUUID(String key){
        System.out.println("Generando cache para " + key);
        return UUID.randomUUID();
    }
}

Es un ejemplo muy sencillo, como podemos ver se está cacheando a getData(String key), lo que significa que una vez tengamos un resultado este será sostenido por la cache, y cada vez que llamemos a este método y la haya transcurrido el tiempo de desalojo de la cache se devolverá sin necesidad de llamar al método generateUUID (String key).

Por supuesto que podemos cachear cualquier elemento que deseemos, este ejemplo es muy sencillo, solo para ilustrar.

Recuerden que hazelcast funciona distribuido, si ponemos en producción otra instancia de este mismo sistema dicha cache sería compartida y ambos accederán al mismo dato. ¿Eficiente verdad?

Este ejemplo se complementa con una API que ejecuta peticiones y loguea los resultados para que puedas ver como funciona, puedes ver el ejemplo completo en GitHub. Si quieres ver el funcionamiento distribuido ejecuta la misma API por diferentes puertos.

3 thoughts on “Introducción a la cache distribuida con Hazelcast & Spring Boot

Comments are closed.