Trabajando con Redis en Python con Django

Redis es un almacén de estructura de datos en memoria, que a menudo se utiliza como base de datos, caché o intermediario de mensajes. En este artículo, exploraremos Redis como un almacén de clave-valor y lo usaremos en un proyecto de Python con Django.

Introducción

Los datos se están convirtiendo cada vez más en un bien valioso en la era actual de la tecnología y esto requiere la optimización del almacenamiento y el acceso a estos datos.

Existen bastantes soluciones notables para el almacenamiento de datos, incluyendo Relational Database Management Systems (RDBMS) como mysql y [postgresql](/trabajando-con -postgresql-en-python/), que almacenan datos en un formato estructurado usando filas y columnas y las relaciones dentro de los datos.

Además de RDBMS, existen almacenes de clave-valor que almacenan datos basados ​​en claves y valores únicos como un diccionario. Las bases de datos de clave-valor pertenecen a la familia de bases de datos NoSQL que no se ajustan a la naturaleza relacional de RDBMS.

En esta publicación, exploraremos redis como un almacén de clave-valor y lo usaremos en un proyecto para explorar su funcionalidad.

¿Qué es Redis y por qué usarlo?

Redis (servidor de diccionario remoto) es un almacén de estructura de datos en memoria que se puede utilizar como base de datos, caché o intermediario de mensajes.

Los datos se almacenan en Redis en forma de valores-clave donde las claves se utilizan para ubicar y extraer los datos almacenados en la instancia de Redis.

Las bases de datos normales almacenan datos en el disco, lo que genera un costo adicional en términos de tiempo y recursos de hardware. Redis evita esto al almacenar todos los datos en la memoria, lo que hace que los datos estén fácilmente disponibles y aumenta la velocidad de acceso y manipulación de datos, en comparación con las bases de datos normales.

Esta es la razón por la que Redis es conocido por su excepcional capacidad de alto rendimiento.

Redis nos permite almacenar datos en múltiples estructuras de datos de alto nivel, incluidas cadenas, hashes, listas, conjuntos y conjuntos ordenados. Esto nos brinda más flexibilidad sobre el tipo y la cantidad de información que podemos almacenar en un almacén de datos de Redis.

Habiendo sido escrito en ANSI C, Redis es liviano y sin dependencias externas. También es bastante amigable para los desarrolladores, ya que admite la mayoría de los lenguajes de alto nivel, como Python, JavaScript, Java, C/C++ y PHP.

¿Cuándo debería usar Redis?

Los casos de uso comunes de Redis incluyen:

  • Almacenamiento en caché: Dada su velocidad con respecto a las bases de datos tradicionales, en términos de operaciones de lectura y escritura, Redis se ha convertido en una solución ideal para almacenar datos temporalmente en un caché para acelerar el acceso a los datos en el futuro.
  • Message Queueing: con la capacidad de implementar el paradigma de mensajería de publicación/suscripción, Redis se ha convertido en un intermediario de mensajes para los sistemas de colas de mensajes.
  • Almacenamiento de datos: Redis se puede usar para almacenar datos de clave-valor como una base de datos NoSQL.

Empresas como Twitter, Pinterest, Github, Snapchat y StackOverflow utilizan Redis para almacenar y hacer que los datos estén altamente disponibles para sus usuarios.

Por ejemplo, Twitter almacena los tweets entrantes más recientes para un usuario en Redis para acelerar la entrega de los tweets a las aplicaciones de los clientes.

Pinterest usa Redis para almacenar una lista de usuarios y tableros que sigue un usuario, una lista de seguidores de un usuario y una lista de personas que siguen tus tableros, entre otras listas para mejorar la experiencia en la plataforma.

Instalación de Redis {#instalación de Redis}

Para seguir explorando Redis, necesitamos descargar e instalar el servidor Redis siguiendo las instrucciones de la página web oficial. Redis también está disponible como Imagen de Docker en Docker Hub.

También viene con una herramienta Redis-CLI que podemos usar para interactuar y manipular datos en nuestro servidor Redis.

Redis también está disponible para su instalación a través de Homebrew (para MacOS) y a través del repositorio apt predeterminado para Debian Linux y sus variantes, como Ubuntu.

Para instalar Redis en MacOS, simplemente ejecute:

1
$ brew install redis

En Debian Linux:

1
$ sudo apt-get install redis-server

Para verificar nuestra instalación de Redis, escriba el comando redis-cli, luego escriba ping en el mensaje que aparece:

1
2
3
4
5
6
$ redis-cli -v
redis-cli 5.0.6
$ redis-cli
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>

Podemos ver que nuestro servidor Redis está listo con la respuesta: PONG.

Comandos Redis

Redis, a través de Redis-CLI, proporciona algunos comandos útiles que podemos usar para interactuar con el servidor de Redis y manipular los datos almacenados allí. De manera predeterminada, los servidores de Redis se ejecutan en el puerto 6379 y esto será visible en nuestro aviso.

Los comandos disponibles en el indicador de Redis-CLI incluyen:

  1. SET: este comando se utiliza para establecer una clave y su valor, con parámetros opcionales adicionales para especificar la caducidad de la entrada de clave-valor. Establezcamos una clave hola con el valor de mundo con una caducidad de 10 segundos:
1
2
127.0.0.1:6379> SET hello "world" EX 10
OK
  1. GET: este comando se utiliza para obtener el valor asociado con una clave. En caso de que la entrada de clave-valor haya superado su período de caducidad, se devolverá nil:
1
2
3
4
5
6
127.0.0.1:6379> GET hello
world

# After expiry
127.0.0.1:6379> GET hello
(nil)
  1. DELETE: Este comando elimina una clave y el valor asociado:
1
2
127.0.0.1:6379> DEL hello
(integer) 1
  1. TTL: cuando se configura una clave con vencimiento, este comando se puede usar para ver cuánto tiempo queda:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
127.0.0.1:6379> SET foo "bar" EX 100     # 100 is the number of seconds
OK

127.0.0.1:6379> TTL foo
(integer) 97      # Number of seconds remaining till expiry

127.0.0.1:6379> TTL foo
(integer) 95

127.0.0.1:6379> TTL foo
(integer) 93
  1. PERSIST: si cambiamos de opinión sobre el vencimiento de una clave, podemos usar este comando para eliminar el período de vencimiento:
1
2
3
4
5
6
7
8
127.0.0.1:6379> PERSIST foo
(integer) 1

127.0.0.1:6379> TTL foo
(integer) -1

127.0.0.1:6379> GET foo
"bar"
  1. RENAME: este comando se usa para cambiar el nombre de las claves en nuestro servidor Redis:
1
2
3
4
5
6
7
8
127.0.0.1:6379> RENAME foo foo2
OK

127.0.0.1:6379> GET foo
(nil)

127.0.0.1:6379> GET foo2
"bar"
  1. FLUSHALL: este comando se usa para purgar todas las entradas de clave-valor que hemos establecido en nuestra sesión actual:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
127.0.0.1:6379> RENAME foo foo2
OK

127.0.0.1:6379> GET foo
(nil)

127.0.0.1:6379> GET foo2
(nil)

127.0.0.1:6379> GET hello
(nil)

Puede encontrar más información sobre estos y otros comandos de Redis en la página web oficial.

Redis con Django

Para demostrar cómo integrar Redis en una aplicación web, crearemos una API usando Django y Django REST que puede recibir un par clave-valor y almacenarlo en nuestro servidor Redis.

Nuestra API también podrá recuperar valores para claves dadas, recuperar todos los pares clave-valor almacenados y también eliminar una entrada clave-valor.

Comencemos creando una carpeta para albergar nuestro proyecto:

1
$ mkdir redis_demo && cd $_

Luego, vamos a crear un entorno virtual y activarlo:

1
2
$ virtualenv --python=python3 env --no-site-packages
$ source env/bin/activate

Y finalmente, instalemos las bibliotecas necesarias:

1
$ pip install django djangorestframework redis

La API de nuestra aplicación recibirá solicitudes e interactuará con nuestro servidor Redis mediante la biblioteca Redis-py.

Ahora vamos a crear la aplicación:

1
2
3
4
5
6
7
8
9
# Create the project
$ django-admin startproject django_redis_demo
$ cd django_redis_demo

# Create the app
$ django-admin startapp api

# Migrate
$ python manage.py migrate

Para verificar que nuestra configuración de Django fue exitosa, iniciamos el servidor:

1
$ python manage.py runserver

Cuando navegamos a http:127.0.0.1:8000, nos da la bienvenida:

django setup

El siguiente paso es agregar nuestra aplicación api y Django REST a nuestro proyecto actualizando la lista INSTALLED_APPS que se encuentra en django_redis_demo/settings.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Add these two
    'rest_framework',
    'api',
]

Redis-py necesita una instancia en ejecución de Redis para interactuar. Tendremos que configurar esto en nuestro django_redis_demo/settings.py agregando:

1
2
REDIS_HOST = 'localhost'
REDIS_PORT = 6379

Con esta configuración, también podemos usar una instancia de Redis que se ejecuta dentro de un contenedor Docker o una instancia remota de Redis, aunque es posible que debamos proporcionar detalles de autenticación para ese caso. Por ahora, usaremos nuestra instancia local de Redis que configuramos.

A continuación, vamos a crear la ruta que se usará para acceder a nuestra API y vincularla a nuestra aplicación Django principal. Primero, crearemos un archivo api/urls.py vacío, luego crearemos nuestra ruta en django_redis_demo/urls.py:

1
2
3
4
5
6
7
8
# Modify this import
from django.urls import path, include

urlpatterns = [
    ...
    # Add this entry
    path('api/', include('api.urls')),
]

Todas las solicitudes que ingresen a través del punto final api/ ahora serán manejadas por nuestra aplicación api. Lo que falta ahora son las vistas que manejarán las solicitudes.

Nuestras vistas serán vistas simples basadas en funciones que nos permitirán interactuar con el servidor Redis. Primero, creemos las URL con las que interactuaremos en nuestro api/urls.py:

1
2
3
4
5
6
7
8
9
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from .views import manage_items, manage_item

urlpatterns = {
    path('', manage_items, name="items"),
    path('<slug:key>', manage_item, name="single_item")
}
urlpatterns = format_suffix_patterns(urlpatterns)

La primera ruta nos permitirá crear entradas y ver todas las entradas, mientras que la segunda ruta nos dará una gestión granular de entradas individuales.

Tendremos dos vistas basadas en funciones: manage_items() y manage_item() que manejarán las solicitudes e interactuarán con nuestra instancia de Redis. Ambos residirán en nuestro archivo api/views.py.

Para explicar mejor el código, lo dividiremos en partes más concisas. Si desea ver el código completo, hay un enlace al repositorio de GitHub con el código fuente al final de este artículo.

Comenzaremos importando las bibliotecas necesarias y conectándonos a nuestra instancia de Redis:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import json
from django.conf import settings
import redis
from rest_framework.decorators import api_view
from rest_framework import status
from rest_framework.response import Response

# Connect to our Redis instance
redis_instance = redis.StrictRedis(host=settings.REDIS_HOST,
                                  port=settings.REDIS_PORT, db=0)

Aquí, creamos nuestro objeto de conexión pasando el host y el puerto de Redis como se configuró anteriormente en nuestro django_redis_demo/settings.py.

Luego, creamos nuestra primera vista manage_items() que se usará para recuperar todos los elementos configurados actualmente en nuestra instancia de Redis en ejecución. Esta vista también nos permitirá crear nuevas entradas en nuestra instancia de Redis pasando un objeto JSON:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@api_view(['GET', 'POST'])
def manage_items(request, *args, **kwargs):
    if request.method == 'GET':
        items = {}
        count = 0
        for key in redis_instance.keys("*"):
            items[key.decode("utf-8")] = redis_instance.get(key)
            count += 1
        response = {
            'count': count,
            'msg': f"Found {count} items.",
            'items': items
        }
        return Response(response, status=200)
    elif request.method == 'POST':
        item = json.loads(request.body)
        key = list(item.keys())[0]
        value = item[key]
        redis_instance.set(key, value)
        response = {
            'msg': f"{key} successfully set to {value}"
        }
        return Response(response, 201)

Entonces, definamos manage_item():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@api_view(['GET', 'PUT', 'DELETE'])
def manage_item(request, *args, **kwargs):
    if request.method == 'GET':
        if kwargs['key']:
            value = redis_instance.get(kwargs['key'])
            if value:
                response = {
                    'key': kwargs['key'],
                    'value': value,
                    'msg': 'success'
                }
                return Response(response, status=200)
            else:
                response = {
                    'key': kwargs['key'],
                    'value': None,
                    'msg': 'Not found'
                }
                return Response(response, status=404)
    elif request.method == 'PUT':
        if kwargs['key']:
            request_data = json.loads(request.body)
            new_value = request_data['new_value']
            value = redis_instance.get(kwargs['key'])
            if value:
                redis_instance.set(kwargs['key'], new_value)
                response = {
                    'key': kwargs['key'],
                    'value': value,
                    'msg': f"Successfully updated {kwargs['key']}"
                }
                return Response(response, status=200)
            else:
                response = {
                    'key': kwargs['key'],
                    'value': None,
                    'msg': 'Not found'
                }
                return Response(response, status=404)

    elif request.method == 'DELETE':
        if kwargs['key']:
            result = redis_instance.delete(kwargs['key'])
            if result == 1:
                response = {
                    'msg': f"{kwargs['key']} successfully deleted"
                }
                return Response(response, status=404)
            else:
                response = {
                    'key': kwargs['key'],
                    'value': None,
                    'msg': 'Not found'
                }
                return Response(response, status=404)

manage_item() nos da acceso a entradas individuales en nuestra instancia de Redis. Esta vista requiere que la persona que llama pase la clave del elemento que necesitamos en la URL.

Esta clave luego se usa para ubicar el valor almacenado en nuestra instancia. Al usar el método HTTP PUT y pasar el nuevo valor de una clave, podemos actualizar el valor de la clave.

A través del método DELETE, podemos eliminar un par clave-valor de nuestra instancia de Redis.

Para ver nuestra API en acción, usaremos Cartero. Pero primero, creemos una entrada o dos usando la herramienta redis-cli:

1
2
3
4
5
$ redis-cli
127.0.0.1:6379> SET HELLO "WORLD"
OK
127.0.0.1:6379> SET REDIS "DEMO"
OK

Después de configurar los datos, enviemos una solicitud GET a localhost:8000/api/items:

get all items

Nuestra API puede obtener todos los pares clave-valor en nuestra instancia actual de Redis. Ahora enviemos una solicitud POST con la siguiente carga útil a la misma URL:

1
2
3
{
"mighty": "mug"
}

Enviemos otra solicitud GET al mismo punto final:

obtener todos los elementos después de la actualización

Podemos ver que la clave que creamos usando nuestra API está guardada en nuestra instancia de Redis. Podemos verificar su existencia usando la herramienta CLI.

Probemos ahora el segundo punto final que devuelve el valor de una sola clave enviando una solicitud GET a http://localhost:8000/api/items/HELLO:

obtener elemento individual

Eso salió bien. Actualicemos ahora el valor asociado a la clave HOLA enviando el siguiente JSON a través de una solicitud PUT al mismo punto final:

1
2
3
{
"new_value": "wikihtp.com"
}

Cuando volvemos a buscar la clave HOLA:

update single item

Nuestro valor ha sido actualizado con éxito. Lo último que queda es la eliminación de claves, así que sigamos adelante y enviemos una solicitud DELETE a http://localhost:8000/api/items/HELLO para eliminar la clave que acabamos de actualizar.

Cuando intentamos acceder al mismo elemento después de eliminarlo:

elemento único de eliminación posterior

Se nos informa que nuestra clave ha sido eliminada. Nuestra API de Django se ha conectado con éxito con nuestra instancia de Redis usando la biblioteca Redis-py.

Conclusión

Redis es una opción de almacenamiento de datos potente y rápida que, si se usa en la situación adecuada, puede traer muchos beneficios. No tiene una curva de aprendizaje pronunciada, por lo que es fácil de aprender y también viene con una práctica herramienta CLI para ayudarnos a interactuar con ella a través de comandos simples e intuitivos.

Hemos podido integrar nuestra API de Django con una instancia de Redis que se ejecuta localmente sin problemas, lo que es un testimonio de su facilidad de uso con lenguajes de programación comunes de alto nivel.

El código fuente del script de este proyecto se puede encontrar aquí en GitHub.