Implementación de un Sistema de Caché de Dos Niveles con ConcurrentHashMap y Redis

Implementación de un Sistema de Caché de Dos Niveles con ConcurrentHashMap y Redis

A continuación se presenta un ejemplo práctico de cómo implementar un sistema de caché de dos niveles utilizando ConcurrentHashMap y Redis en una aplicación Spring:


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import redis.clients.jedis.JedisPool;

import java.util.Map;
import java.util.concurrent.ConcurrentMap;

@Service
public class SistemaCacheDobleNivel {
    private final ConcurrentMap<string object=""> cacheLocal = new ConcurrentHashMap<>();
    private static final String PREFIJO_REDIS = "datos:";
    
    @Autowired
    private JedisPool poolConexiones;

    @Cacheable(value = "almacenDatos", key = "#clave")
    public Object obtenerDato(String clave) {
        // Primero buscar en la caché local
        Object resultado = cacheLocal.get(clave);
        if (resultado != null) {
            return resultado;
        }
        
        // Si no está en local, buscar en Redis
        try (Jedis jedis = poolConexiones.getResource()) {
            String datoRedis = jedis.get(PREFIJO_REDIS + clave);
            if (datoRedis != null) {
                // Guardar en caché local para futuras solicitudes
                cacheLocal.put(clave, datoRedis);
                return datoRedis;
            }
            return null;
        }
    }

    @CachePut(value = "almacenDatos", key = "#clave")
    public void almacenarDato(String clave, Object valor) {
        // Actualizar caché local
        cacheLocal.put(clave, valor);
        
        // Actualizar Redis
        try (Jedis jedis = poolConexiones.getResource()) {
            jedis.set(PREFIJO_REDIS + clave, valor.toString());
        }
    }

    public void eliminarDato(String clave) {
        // Eliminar de caché local
        cacheLocal.remove(clave);
        
        // Eliminar de Redis
        try (Jedis jedis = poolConexiones.getResource()) {
            jedis.del(PREFIJO_REDIS + clave);
        }
    }
}
</string>

En el ejemplo anterior, la clase SistemaCacheDobleNivel implementa un servicio de caché de dos niveles. Utiliza anotaciones de Spring como @Cacheable y @CachePut para manejar el comportamiento de la caché.

La variable cacheLocal es un ConcurrentHashMap que funciona como caché en memoria, mientras que poolConexiones es un pool de conexiones a Redis inyectado a través de Spring.

El método obtenerDato primero busca en la caché local. Si el dato no existe, lo recupera de Redis y lo almacena lcoalmente para futuras solicitudes.

El método almacenarDato actualiza tanto la caché local como Redis con el nuevo dato.

Es importante destacar que este ejemplo es con fines de demostración y podría requerir ajustes según las necesidades específicas del proyecto. Además, es necesario asegurar una correcta configuración e inicialización de la conexión a Redis, así como habilitar las funcionalidades de caché de Spring.

Implementación de Caché Distribuido con ConcurrentHashMap y Redis

En arquitecturas distribuidas, la implementación de un sistema de caché de dos niveles con ConcurrentHashMap y Redis requiere considerar la consistencia de datos y el acceso concurrente entre múltiples nodos. A continuación se muestra un ejemplo basado en bloqueos distributiovs:


import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.ConcurrentHashMap;

@Service
public class CacheDistribuido {
    private final ConcurrentHashMap<string object=""> memoriaCache = new ConcurrentHashMap<>();
    private static final String ESPACIO_MEMORIA = "memoria_compartida:";
    
    @Autowired
    private RedissonClient clienteRedisson;

    public Object recuperarDato(String identificador) {
        // Verificar primero en la caché local
        Object elemento = memoriaCache.get(identificador);
        if (elemento != null) {
            return elemento;
        }
        
        // Si no existe, obtener bloqueo distribuido
        RLock bloqueo = clienteRedisson.getLock(ESPACIO_MEMORIA + identificador);
        bloqueo.lock();
        try {
            // Verificación doble para evitar recargas simultáneas
            elemento = memoriaCache.get(identificador);
            if (elemento == null) {
                // Obtener datos de Redis
                RMap<string object=""> mapaDistribuido = clienteRedisson.getMap("datos_cache");
                elemento = mapaDistribuido.get(identificador);
                
                if (elemento != null) {
                    // Almacenar en caché local para accesos futuros
                    memoriaCache.put(identificador, elemento);
                }
            }
            return elemento;
        } finally {
            bloqueo.unlock();
        }
    }

    public void guardarDato(String identificador, Object contenido) {
        // Almacenar en caché local
        memoriaCache.put(identificador, contenido);
        
        // Almacenar en caché distribuida
        RMap<string object=""> mapaDistribuido = clienteRedisson.getMap("datos_cache");
        mapaDistribuido.put(identificador, contenido);
    }

    public void removerDato(String identificador) {
        // Eliminar de caché local
        memoriaCache.remove(identificador);
        
        // Eliminar de caché distribuida
        RMap<string object=""> mapaDistribuido = clienteRedisson.getMap("datos_cache");
        mapaDistribuido.remove(identificador);
    }
}
</string></string></string></string>

En este ejemplo, se utiliza Redisson como cliente de Redis para implementar bloqueos distributivos. La clase CacheDistribuido emplea un patrón de verificación doble con bloqueo para asegurar la consistencia de datos entre múltiples nodos.

En el método recuperarDato, primero se busca en la caché local. Si el dato no existe, se obtiene un bloqueo distributivo y se vuelve a verificar la caché local para evitar que múltiples hilos recarguen simultáneamente los datos. Si el dato aún no existe, se recupera de Redis y se almacena en la caché local.

El método guardarDato almacena datos tanto en la caché local como en la caché distribuida a través de Redis.

El método removerDato elimina los datos tanto de la caché local como de la caché distribuida.

Es importante mencionar que la implementación de bloqueos distributivos en este ejemplo utiliza Redisson, pero existen otras alternativas basadas en Redis. Además, es fundamental una correcta configuración de la conexión a Redis y del cliente Redisson.

Etiquetas: ConcurrentHashMap Redis Caché distribuido Spring Cache Redisson

Publicado el 6-1 21:15