---
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

---
> Vista de creación de una nueva conexión

---
> Vista de acceso al Redis

---
> Vista principal del server Redis

---
# 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/