--- title: 'Instalacion y configuracion de Redis' --- 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 ``` shell= $ 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 ``` shell= $ git clone https://gitlab.siare.gov.py/siare-develop/gestion-entornos.git ``` El contenido del docker-compose.yml se describe a continuación: ``` shell= 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: ``` shell= $ 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: ``` shell= $ 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: ``` shell= $ https://github.com/qishibo/AnotherRedisDesktopManager $ https://github.com/qishibo/AnotherRedisDesktopManager/releases ``` > Vista inicial de la aplicación ![](https://i.imgur.com/bV0UIWY.png) --- > Vista de creación de una nueva conexión ![](https://i.imgur.com/YbmY8Rc.png) --- > Vista de acceso al Redis ![](https://i.imgur.com/rOfUpNZ.png) --- > Vista principal del server Redis ![](https://i.imgur.com/inbSWvH.png) --- # Uso de Redis con Spring Data Redis Spring Data Redis, proporciona las abstracciones de la plataforma Spring Data a Redis ## Dependencias Maven necesarias ```xml= <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: ```java= 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 ```java 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** ```java 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) ```java= 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** ```java= @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/