Try   HackMD

Instalación y configuración de Redis

Redis

Redis son las siglas de Remote Dictionary Server. Fue escrito en C por Salvatore Sanfilippo en 2006 y actualmente cuenta con muchos colaboradores. Hay clientes de Redis disponibles para más de 30 lenguajes de programación. El proyecto de código abierto se puede obtener desde https://github.com/antirez/redis.
Redis es un proyecto de código abierto bien establecido y ha sido utilizado en producción durante años por grandes empresas, como Twitter, GitHub, Tumblr, Pinterest, Instagram Hulu, Flickr y The New York Times.

Repositorio

$ https://gitlab.siare.gov.py/siare-develop/gestion-entornos.git

Proyecto que contiene las configuraciones de los servicios para los diferentes entornos.

Requerimientos

Para la instalacion se requiren los siguientes componentes:

  • docker version 20.10.14 o superior
  • docker compose 1.29.2 o superior
  • redis-cli 7.0.0 o superior

Pasos de Instalación

Copia local del repositorio

Ejecutar el siguiente comando para clonar el repositorio

$ git clone https://gitlab.siare.gov.py/siare-develop/gestion-entornos.git

El contenido del docker-compose.yml se describe a continuación:

version: '2' services: redis-master: image: redis:7.0.0 restart: always command: redis-server ports: - 6379:6379 redis-slave: image: redis:7.0.0 restart: always command: redis-server --slaveof redis-master 6379 ports: - 7001:6379 depends_on: - redis-master

Acceder al directorio del repositorio ./gestion-entornos/redis/docker-compose/redis-cluster y ejecutar el siguiente comando:

$ docker-compose up -d

El comando inicia los contenedores y lo ejecuta en segundo plano

Para verificar la correcta ejecución de los contenedores, se debe ejecutar el siguiente comando:

$ docker-compose ps

Gestor para Redis

Another Redis Desktop Manager

Gestor de escritorio para redis compatible para Linux, windows y mac.

La aplicación puede ser descargada de la siguiente dirección:

$ https://github.com/qishibo/AnotherRedisDesktopManager $ https://github.com/qishibo/AnotherRedisDesktopManager/releases

Vista inicial de la aplicación

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →


Vista de creación de una nueva conexión

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →


Vista de acceso al Redis

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →


Vista principal del server Redis

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →


Uso de Redis con Spring Data Redis

Spring Data Redis, proporciona las abstracciones de la plataforma Spring Data a Redis

Dependencias Maven necesarias

<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>${spring.boot.version}</version> </dependency> <!-- Lettuce es un cliente de Redis escalable para un uso síncrono, asíncrono y reactivo --> <dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>6.0.2.RELEASE</version> </dependency>

Configuración de Redis

Para definir la configuración de la conexión entre el cliente y el servidor Redis, es necesario la utilización de un cliente de Redis. Hay diversas implementaciones de clientes para Redis disponibles para el lenguaje Java. Para este ejemplo se utilizará Lettuce (Advanced Java Redis client)

Configuración Java

En necesario la creación de una clase para la configuración del Redis.

RedisConfig

Crear la clase RedisConfig dentro del proyecto java con el siguiente contenido:

import java.time.Duration; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisPassword; import org.springframework.data.redis.connection.RedisStaticMasterReplicaConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import io.lettuce.core.ReadFrom; import io.lettuce.core.RedisURI; import lombok.Data; import lombok.extern.slf4j.Slf4j; @Configuration @Data @Slf4j public class RedisConfig { @Value("${siare.redis.url}") private String redisUrl; @Value("${siare.redis.slave:}") private String redisSlave; @Value("${siare.redis.timeout:60}") private long timeout; @Bean public LettuceConnectionFactory redisConnectionFactory() { // RedisURI. Contiene la configuración de contexión para Redis/Sentinel. // La configuración puede contener datos de la base de datos, // el nombre del usuario, contraseña y tiempos de espera dentro del RedisURI. // Ejemplos : // RedisURI.create("redis://localhost/"); // new RedisURI("localhost", 6379, Duration.ofSeconds(60)); RedisURI uri = RedisURI.create(redisUrl); LettuceClientConfiguration clientConfig = LettuceClientConfiguration .builder() .readFrom(ReadFrom.UPSTREAM) .commandTimeout(Duration.ofSeconds(timeout)) .build(); RedisStaticMasterReplicaConfiguration smc = new RedisStaticMasterReplicaConfiguration( uri.getHost(), uri.getPort()); smc.setPassword( RedisPassword.of(uri.getPassword())); if (redisSlave != null && !redisSlave.isEmpty()) { for (String r : redisSlave.split(";")) { String[] node = r.split(":"); smc.addNode(node[0], Integer.parseInt(node[1])); } } return new LettuceConnectionFactory(smc, clientConfig); } @Bean public RedisTemplate<String, Object> redisTemplate() { // Clase utilitaria que facilita el acceso a datos de Redis // Realiza la serialización/deserialización automática entre los // objetos dados y los datos binarios subyacentes en el Almacén de // Redis. // Por defecto, utiliza la serialización de Java para sus objetos // a través de JdkSerializationRedisSerializer RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory()); Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.activateDefaultTyping( mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(mapper); JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer(); template.setDefaultSerializer(serializer); StringRedisSerializer stringSerializer = new StringRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); template.setHashValueSerializer(jackson2JsonRedisSerializer); template.setValueSerializer(jackson2JsonRedisSerializer); return template; } }

La clase tiene la responsabilidad de crear los objetos necesarios para la conexión con el servidor Redis.

  • @Configuration : Anotación que indica al spring que la clase será utilizada como fuente de definición de Bean para el contexto de la aplicación
  • @Data : Anotación de la librería lombok, que permite generación de setter y getter de todos los atributos de la clase en forma automática
  • @Value("${my.app.myProp}") : Anotación que permite cargar datos desde el archivo application.properties del proyecto
  • @Bean : Anotación que nos permite generar Objetos en forma personalizada dentro del contexto de la aplicación.

Uso del Redis

Para la utilización del Redis se ha generado los siguientes archivos :

SiareCache

Anotación que permite identificar donde será requerido el acceso al servidor de Redis

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SiareCache {

       //Tiempo de expiración del dato en el Redis
	int ttl() default 300;
}
  • @Target : Indica donde será aplicado la anotación, en este caso será utilizado a nivel de método unicamente (ElementType.Method).
  • @Retention : Indica si la anotación estará disponible para el JVM en tiempo de ejecución.

SiareCacheAspect

Clase de Aspecto que intercepta las invocaciones de métodos de servicio, todas las clases de Aspecto debe llevar la anotación @Aspect

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.time.Duration;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.hibernate.cache.CacheException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import py.gov.siare.siarecommon.util.FNV;

@Aspect
@Component
@Slf4j
@AllArgsConstructor
public class SiareCacheAspect {

	private RedisTemplate<String, Object> cache;
	
// Hemos anotado nuestro método con @Around
// Este es el bloque del advice, y significa que estamos añadiendo 
// código extra tanto antes 
// como después de la ejecución del método. 
// La anotación @Around recibe un parámetro de punto de corte (pointcut), 
// lo que indica que este código será aplicado a los métodos 
// que tenga la anotación *SiareCache*
	@Around("@annotation(siareCache)")
	public Object cache(ProceedingJoinPoint pjp, final SiareCache siareCache) 
        throws Throwable {

		try {

			byte[] byteArray;
			log.debug(" Preparando los datos para realizar cache {}", pjp);
			log.debug("argumentos {}", pjp.getArgs());
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			ObjectOutputStream oos;

			try {
				oos = new ObjectOutputStream(baos);
				oos.writeObject(pjp.getArgs());
			} catch (IOException e) {
				log.error("{}", e.getMessage());
				log.debug("no se puede guardar en el cache, 
                    no trae valor el argumento, 
                    por lo tanto se pasa la responsabilidad el servicio");
				return pjp.proceed();
			}

			byteArray = baos.toByteArray();
            //Algoritmo de Fowler Noll Vo 
            // Función de hash
			FNV fnv = new FNV();
			BigInteger identificators = fnv.fnv1_64(byteArray);
            // Generación de key
			String target = generateName(
                pjp.getTarget().getClass(), 
                identificators
            );

			Boolean hasKey = cache.hasKey(target);
			Object objetoCache = null;
			log.debug("Target [{}]", target);
            
            // Se verifica la existencia del key en el cache de Redis
            // en caso de exitir toma el objeto almacenado en el cache sino 
            // realiza la consulta a la base de datos relacional

			if (hasKey.equals(false)) {
				objetoCache = pjp.proceed();
				cache.opsForValue()
                    .set(target, 
                         objetoCache, Duration.ofSeconds(siareCache.ttl()
                                                        )
                        );
				
			} else {
				objetoCache = cache.opsForValue().get(target);
			}


			return objetoCache;
		} catch (Throwable e) {
			throw new CacheException(e.getMessage());
		}

	}

	/**
	 * Genera el nombre del cache, basado en el siguiente algoritmo: * SimpleName
	 * del modelo * Id del objeto (debe heredar de la clase base model.
	 *
	 * @param clazz Classe para obtener el valor "SimpleName"
	 * @param id    Id del objeto
	 * @return devuelve el valor del cache con el formado foo:id
	 */
	private String generateName(Class<?> clazz, BigInteger id) {
		
		StringBuilder stb = new StringBuilder();
		stb.append(clazz.getSimpleName());
		stb.append(":");
		stb.append(id.toString());
		return stb.toString();
	}

	private String generateName(Class<?> clazz, String id) {
		
		StringBuilder stb = new StringBuilder();
		stb.append(clazz.getSimpleName());
		stb.append(":");
		stb.append(id);
		return stb.toString();
	}

}
  • @Component : Anotación que indica al spring que la clase será un Bean gestionado dentro del contexto de la aplicación
  • @Slf4j : Anotación de la librería lombok, que permite generar logs
  • @AllArgsConstructor : Anotación de la librería lombok, que permite generar un constructor con todos los atributos de la clase.

FNV

Clase que implementa el algoritmo hash FNV (Fowler Noll Vo)

import java.math.BigInteger; public class FNV { private static final BigInteger INIT32 = new BigInteger("811c9dc5", 16); private static final BigInteger INIT64 = new BigInteger("cbf29ce484222325", 16); private static final BigInteger PRIME32 = new BigInteger("01000193", 16); private static final BigInteger PRIME64 = new BigInteger("100000001b3", 16); private static final BigInteger MOD32 = new BigInteger("2").pow(32); private static final BigInteger MOD64 = new BigInteger("2").pow(64); public BigInteger fnv1_32(byte[] data) { BigInteger hash = INIT32; for (byte b : data) { hash = hash.multiply(PRIME32).mod(MOD32); hash = hash.xor(BigInteger.valueOf((int) b & 0xff)); } return hash; } public BigInteger fnv1_64(byte[] data) { BigInteger hash = INIT64; for (byte b : data) { hash = hash.multiply(PRIME64).mod(MOD64); hash = hash.xor(BigInteger.valueOf((int) b & 0xff)); } return hash; } public BigInteger fnv1a_32(byte[] data) { BigInteger hash = INIT32; for (byte b : data) { hash = hash.xor(BigInteger.valueOf((int) b & 0xff)); hash = hash.multiply(PRIME32).mod(MOD32); } return hash; } public BigInteger fnv1a_64(byte[] data) { BigInteger hash = INIT64; for (byte b : data) { hash = hash.xor(BigInteger.valueOf((int) b & 0xff)); hash = hash.multiply(PRIME64).mod(MOD64); } return hash; } }

Ejemplo de utilización

En las implementaciones de los servicios agregamos la anotacion @SiareCache

@SiareCache @Override public ResponseDto<List<MetodosDto>> obtenerRegistrosPaginadoOrdenado( String filtros, int page, int cantidad, String orderBy, String orderDir) { return super.obtenerRegistrosPaginadoOrdenado( filtros, page, cantidad, orderBy, orderDir); }

Referencias

[1] Redis. Introduction to Redis. Recuperado el 27 de mayo del 2022, de https://redis.io/docs/about/