Spring Boot con Redis: operaciones de canalización

En este tutorial, realizaremos la canalización de operaciones de Redis para aplicaciones Spring Boot Java para mejorar la eficiencia y el rendimiento.

Introducción

Redis es un almacén de datos en memoria, que se puede usar como base de datos NoSQL, caché o como un intermediario de mensajes típico. Está escrito en ANSI C, que se compila en un código de máquina significativamente eficiente y su capacidad para almacenar datos como pares clave-valor hace que el almacenamiento en caché en memoria sea un caso de uso atractivo para Redis, además de la persistencia de datos en un disco.

En este artículo, usaremos Pipelining para permitir que una aplicación Spring Boot envíe múltiples solicitudes al servidor Redis, sin bloqueos.

Caso de uso de canalización en Redis

Redis se basa en una arquitectura Cliente/Servidor (Solicitud/Respuesta). En estas arquitecturas, un cliente normalmente envía una consulta o solicitud al servidor y espera una respuesta. Esto generalmente se hace de manera de bloqueo, de modo que no se puede enviar una nueva solicitud hasta que se envíe la respuesta a la última:

1
2
3
4
5
6
Client: <command 1>
Server: Response for <command 1>
Client: <command 2>
Server: Response for <command 2>
Client: <command 3>
Server: Response for <command 3>

Esto puede generar ineficiencias masivas, con una alta latencia entre la recepción de los comandos y su procesamiento.

Pipelining nos permite enviar múltiples comandos, como una sola operación por parte del cliente, sin esperar la respuesta del servidor entre cada comando. Luego, todas las respuestas se leen juntas en su lugar:

1
2
3
4
5
6
Client: <command 1>
Client: <command 2>
Client: <command 3>
Server: Response for <command 1>
Server: Response for <command 2>
Server: Response for <command 3>

Dado que el cliente no espera la respuesta del servidor antes de emitir otro comando, se reduce la latencia, lo que a su vez mejora el rendimiento de la aplicación.

Nota: Los comandos aquí se colocan en una cola. Esta cola debe permanecer en un tamaño razonable. Si está tratando con decenas de miles de comandos, es mejor enviarlos y procesarlos en lotes, para que los beneficios de la canalización no se vuelvan redundantes.

Implementación

Avancemos y hagamos una pequeña aplicación Spring Boot que funcione con Redis y canalice múltiples comandos. Esto se hace más fácil con la ayuda del proyecto Redis de datos de primavera.

Configuración de Spring Boot

La forma más fácil de comenzar con una aplicación Spring Boot en blanco es usar Spring Initializr:

spring boot initializr

Alternativamente, también puede usar la CLI de arranque de primavera para arrancar la aplicación:

1
$ spring init --dependencies=spring-boot-starter-data-redis redis-spring-boot-demo

Comenzamos con la dependencia spring-boot-starter-data-redis, ya que incluye spring-data-redis, spring-boot-starter y lettuce-core.

Si ya tiene una aplicación Maven/Spring, agregue la dependencia a su archivo pom.xml:

1
2
3
4
5
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>${version}</version>
</dependency>

O si estás usando Gradle:

1
compile group: 'org.springframework.data', name: 'spring-data-redis', version: '${version}'

También usaremos Jedis como cliente de conexión, en lugar de Lettuce:

1
2
3
4
5
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>${version}</version>
</dependency>

Configuración de Redis

Alojaremos Redis en Cuadrícula de escala, que proporciona una cuenta de prueba gratuita, para alojar una instancia de servidor Redis. Alternativamente, puede descargar el servidor y alojarlo en su propia computadora en Linux y MacOS. Windows requiere un poco de piratería y es complicado de configurar.

Configuremos JedisConnectionFactory para que nuestra aplicación pueda conectarse a la instancia del servidor Redis. En su clase @Configuration, anote el @Bean adecuado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Configuration
public class Config {
    @Bean
    public JedisConnectionFactory redisConnectionFactory() {
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
        jedisConnectionFactory.setHostName("<server-hostname-here>");
        jedisConnectionFactory.setPort(6379);
        jedisConnectionFactory.setPassword("<server-password-here>");
        jedisConnectionFactory.afterPropertiesSet();
        return jedisConnectionFactory;
    }
}

RedisTemplate es una clase de entrada proporcionada por Spring Data a través de la cual interactuamos con el servidor Redis.

Le pasaremos nuestra fábrica de conexiones configurada:

1
2
3
4
5
6
7
8
@Bean
public RedisTemplate<String, String> redisTemplate() {
    RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(redisConnectionFactory());
    redisTemplate.setDefaultSerializer(RedisSerializer.string());
    redisTemplate.afterPropertiesSet();
    return redisTemplate;
}

Hemos configurado el serializador predeterminado para almacenar claves y valores como String. Como alternativa, puede definir su propio serializador personalizado.

Canalización usando RedisTemplate

Agreguemos algunos elementos de una lista al servidor Redis. Haremos esto sin canalización, usando RedisTemplate. Específicamente, usaremos la interfaz ListOperations, adquirida de opsForList():

1
2
List<String> values = Arrays.asList("value-1", "value-2", "value-3", "value-4", "value-5");
redisTemplate.opsForList().leftPushAll("pipeline-list", values);

Ejecutar este código dará como resultado:

redis operation results

Ahora, eliminemos estos. Imaginando que esto puede ser una operación costosa, canalizaremos cada comando rPop() para que se envíen juntos y los resultados se sincronicen cuando se eliminen todos los elementos. Luego, recibiremos estos resultados de vuelta. Para canalizar los comandos, usamos el método executedPipeline().

Acepta un RedisCallback o SessionCallback que le proporcionemos. El método executedPipeline() devuelve los resultados que luego podemos capturar y revisar. Si esto no es necesario, y solo desea ejecutar los comandos, puede usar el método execute() y pasar true como el argumento pipeline en su lugar:

1
2
3
4
5
6
7
8
9
List<Object> results = redisTemplate.executePipelined(new RedisCallback<Object>() {
    public Object doInRedis(RedisConnection connection) throws DataAccessException {
        for(int i = 0; i < 5; i++) {
            connection.rPop("pipeline-list".getBytes());
        }
    return null;
     }
});
return results;

El método executePipelined() aceptó un nuevo RedisCallback(), en el que usamos el método doInRedis() para especificar lo que nos gustaría hacer.

Específicamente, hemos ejecutado el método rPop() 5 veces, eliminando los 5 elementos de la lista que hemos insertado de antemano.

Una vez que se han ejecutado estos cinco comandos, los elementos se eliminan de la lista y se devuelven; los resultados se empaquetan en la lista de resultados:

redis pipeline results

Conclusión

El caso de uso más popular de Redis es como almacén de caché. Sin embargo, también se puede utilizar como base de datos o como intermediario de mensajes.

Redis nos permite aumentar el rendimiento de las aplicaciones, al minimizar las llamadas a la capa de base de datos. Su compatibilidad con canalización permite enviar varios comandos al servidor en una sola operación de escritura, lo que reduce el tiempo de ida y vuelta desde y hacia el servidor.

En este artículo, canalizamos múltiples comandos usando la API RedisTemplate y verificamos los resultados.