Conectar a Redis
Cómo conectar a Redis en Python, JavaScript y Java — con AUTH, TLS, pooling y los tipos de datos que realmente usarás.
Visión General
Redis es un almacén de datos en memoria — las lecturas y escrituras se resuelven en microsegundos porque nada toca disco en el camino caliente. Conectar es fácil; conectar bien es donde la gente se quema: sin AUTH en un puerto expuesto, sin TLS en una red pública, sin pool, sin política de maxmemory. Aquí tienes la configuración que funciona en Python, JavaScript y Java, más los detalles de conexión que la mayoría de tutoriales se saltan.
Cuándo Usar
Usa este recurso cuando:
- Añades una capa de caché delante de una base de datos para bajar latencia y carga — consulta cache-aside, el patrón más común
- Guardas sesiones compartidas entre instancias de la app (los servicios sin estado no pueden mantenerlas en proceso)
- Construyes limitadores de tasa, clasificaciones, feature flags o contadores en tiempo real
- Pasas mensajes ligeros entre servicios con pub/sub o Streams
Redis no es la opción por defecto cuando los datos deben sobrevivir un reinicio por sí mismos, cuando el dataset no cabe en RAM o cuando necesitas consultas ad-hoc sobre relaciones — para eso usa PostgreSQL o MySQL, y mantén Redis al lado, no en su lugar.
Solución
Python
import os
import redis
# redis://default:password@host:6379/0 — usa rediss:// para TLS
r = redis.Redis.from_url(
os.getenv("REDIS_URL", "redis://localhost:6379/0"),
decode_responses=True,
socket_timeout=5,
socket_connect_timeout=5,
max_connections=20
)
# Strings
r.set('user:1:name', 'Alice', ex=3600)
name = r.get('user:1:name')
# Lists
r.lpush('queue:tasks', 'task_1')
task = r.brpop('queue:tasks', timeout=5)
# Hashes
r.hset('user:1', mapping={'email': 'alice@example.com', 'role': 'admin'})
user = r.hgetall('user:1')
JavaScript
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379', {
// las URLs rediss:// activan TLS automáticamente; añade tls: {} para certs propios
maxRetriesPerRequest: 3,
retryStrategy: (times) => Math.min(times * 200, 2000), // backoff, ms
enableReadyCheck: true,
lazyConnect: false
});
// Strings
await redis.set('user:1:name', 'Alice', 'EX', 3600);
const name = await redis.get('user:1:name');
// Lists
await redis.lpush('queue:tasks', 'task_1');
const task = await redis.brpop('queue:tasks', 5);
// Hashes
await redis.hset('user:1', 'email', 'alice@example.com', 'role', 'admin');
const user = await redis.hgetall('user:1');
Java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.Map;
public class RedisExample {
// Para TLS usa una URI: new JedisPool("rediss://default:pass@host:6379")
private final JedisPool pool = new JedisPool("localhost", 6379);
public void stringOps() {
try (Jedis jedis = pool.getResource()) {
jedis.setex("user:1:name", 3600, "Alice");
String name = jedis.get("user:1:name");
}
}
public void listOps() {
try (Jedis jedis = pool.getResource()) {
jedis.lpush("queue:tasks", "task_1");
var task = jedis.brpop(5, "queue:tasks");
}
}
public void hashOps() {
try (Jedis jedis = pool.getResource()) {
jedis.hset("user:1", "email", "alice@example.com");
jedis.hset("user:1", "role", "admin");
Map<String, String> user = jedis.hgetAll("user:1");
}
}
}
Explicación
Los tres clientes hablan RESP, el protocolo de Redis, y cada uno mantiene las conexiones de forma distinta: redis.Redis gestiona un pool interno (max_connections), ioredis envía los comandos por un único socket con lógica de reconexión integrada, y JedisPool entrega una conexión bloqueante por getResource() — siempre dentro de try-with-resources o se fuga.
Dos cosas que los ejemplos de camino feliz esconden. Primero, las instancias en producción necesitan AUTH: desde Redis 6, las ACLs te dan usuarios con nombre (ACL SETUSER app on >secret ~app:* +@all), y la contraseña viaja en la URL — redis://default:secret@host:6379. Segundo, redis:// plano va sin cifrar — sobre cualquier cosa que no sea loopback quieres rediss:// (TLS). Los servicios gestionados como ElastiCache y Redis Cloud te entregan un endpoint rediss:// justo por eso.
Una vez conectado y cacheando, el siguiente paso son los patrones de caché con Redis — nomenclatura de claves, protección anti-estampida y estrategias de invalidación.
Lo que funciona
- Pon la cadena de conexión en una variable de entorno (
REDIS_URL) — las credenciales no van en el código, y el formato URL funciona en los tres drivers - Establece expiración explícita (
EX,PX,EXAToKEEPTTLal reescribir) en las claves de caché para evitar crecimiento descontrolado de memoria - Usa
decode_responses=Trueen Python — por defecto devuelvebytes, que rompe las comparaciones de strings en silencio - Prefiere
BRPOP/BLPOPpara consumir colas — las lecturas bloqueantes ganan a los bucles de polling - Agrupa comandos relacionados en un pipeline (
.pipeline()en redis-py y Jedis,.pipeline()/.multi()en ioredis) para reducir round-trips
Errores Comunes
- Exponer Redis en una interfaz pública sin AUTH ni TLS — es el puerto por defecto más escaneado de internet
- Guardar objetos grandes (>1 MB) en claves individuales, con picos de latencia en cada lectura
- No manejar los fallos de conexión, causando errores en cascada cuando Redis cae — degrada a la base de datos, no te estrelles
- Olvidar los TTLs, llegando a
OOM command not allowedcuandomaxmemoryse llena - Usar
KEYSen producción — escanea todas las claves en el hilo único y bloquea la instancia; usaSCANen su lugar
Ejemplos Avanzados
Python con pipelines y transacciones
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
# Pipeline: agrupa varios comandos para reducir RTT
pipe = r.pipeline()
pipe.set('user:1:name', 'Alice', ex=3600)
pipe.set('user:1:email', 'alice@example.com', ex=3600)
pipe.hset('user:1', mapping={'name': 'Alice', 'email': 'alice@example.com'})
results = pipe.execute()
print(results) # [True, True, 1]
# Transacción con MULTI/EXEC — atómica, nada se intercala
pipe = r.pipeline(transaction=True)
pipe.set('counter', 0)
pipe.incr('counter')
pipe.incr('counter')
results = pipe.execute()
print(results) # [True, 1, 2]
JavaScript con sorted sets y pub/sub
const Redis = require('ioredis');
const redis = new Redis({ host: 'localhost', port: 6379 });
// Sorted sets: clasificaciones
await redis.zadd('leaderboard', 100, 'alice', 85, 'bob', 120, 'charlie');
const topPlayers = await redis.zrevrange('leaderboard', 0, 9, 'WITHSCORES');
// ['charlie', '120', 'alice', '100', 'bob', '85']
// Pub/Sub — el suscriptor necesita su propia conexión
const subscriber = new Redis({ host: 'localhost', port: 6379 });
const publisher = new Redis({ host: 'localhost', port: 6379 });
await subscriber.subscribe('notifications');
subscriber.on('message', (channel, message) => {
console.log(`Recibido en ${channel}: ${message}`);
});
await publisher.publish('notifications', JSON.stringify({ event: 'user_joined', userId: 42 }));
Java con JedisPool y scripting Lua
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.List;
public class RedisAdvanced {
private final JedisPool pool;
public RedisAdvanced(String host, int port) {
this.pool = new JedisPool(host, port);
}
public void rateLimit(String userId, int maxRequests, int windowSeconds) {
try (Jedis jedis = pool.getResource()) {
String key = "rate:" + userId;
String luaScript =
"local current = redis.call('INCR', KEYS[1]) " +
"if current == 1 then " +
" redis.call('EXPIRE', KEYS[1], ARGV[2]) " +
"end " +
"return tonumber(current) <= tonumber(ARGV[1])";
Object allowed = jedis.eval(
luaScript, 1, key,
String.valueOf(maxRequests), String.valueOf(windowSeconds)
);
System.out.println("Petición permitida: " + allowed);
}
}
public void pipelineExample() {
try (Jedis jedis = pool.getResource()) {
var pipe = jedis.pipelined();
pipe.set("key1", "value1");
pipe.set("key2", "value2");
pipe.sync();
}
}
}
Python async con redis-py
import asyncio
import redis.asyncio as aioredis
async def main():
r = aioredis.Redis(host='localhost', port=6379, decode_responses=True)
# Operaciones async con strings
await r.set('async:key', 'value', ex=60)
value = await r.get('async:key')
# Pipeline async
pipe = r.pipeline()
pipe.set('a', 1)
pipe.set('b', 2)
pipe.get('a')
results = await pipe.execute()
print(results) # [True, True, '1']
await r.aclose()
asyncio.run(main())
Variantes
| Tecnología | Driver | Async | Clúster | Notas |
|---|---|---|---|---|
| Python | redis-py | Sí (redis.asyncio) | redis.cluster.RedisCluster | Cliente oficial — el soporte de clúster viene integrado desde la 4.x |
| Python | valkey-py | Sí | Sí | Para Valkey, el fork de Redis 7.2 de la Linux Foundation |
| JavaScript | ioredis | Sí (Promise) | new Redis.Cluster() | Recomendado para Node.js |
| JavaScript | node-redis (paquete redis) | Sí (Promise) | createCluster() | Cliente oficial de Redis |
| Java | Jedis | No | JedisCluster | API bloqueante simple |
| Java | Lettuce | Sí (reactivo) | RedisClusterClient | El default de Spring Data Redis |
| Go | go-redis | Sí | ClusterClient | Cliente estándar de Go |
Consejos de Producción
- Configura
maxmemoryy una política de evicción. Sin ellos, Redis crece hasta que el OOM killer lo elige.allkeys-lrues el default correcto para cachés;noevictionpara lo que sea durable:
# redis.conf
maxmemory 2gb
maxmemory-policy allkeys-lru
- Monitorea con
INFOySLOWLOG.INFOmuestra memoria, clientes y tasa de aciertos — mirakeyspace_hitscontrakeyspace_misses;SLOWLOG GETlista los comandos que superaronslowlog-log-slower-thanmicrosegundos:
info = r.info()
print(f"Memoria usada: {info['used_memory_human']}")
print(f"Tasa de aciertos: {info['keyspace_hits'] / (info['keyspace_hits'] + info['keyspace_misses']):.1%}")
-
Usa Sentinel para alta disponibilidad. Sentinel vigila el master y promueve una réplica cuando falla; los tres clientes tienen modos compatibles con Sentinel (
redis.sentinel.Sentinel, la opciónsentinelsde ioredis,JedisSentinelPool). -
Agrupa con
PIPELINE,MGETyMSET. Todo lo que pase de unos pocos comandos seguidos debería ir en un solo round-trip:
values = r.mget('key1', 'key2', 'key3')
r.mset({'key1': 'val1', 'key2': 'val2'})
- Usa
HSCANen lugar deHGETALLen hashes grandes.HGETALLcopia el hash entero a memoria de golpe;HSCANitera en lotes:
for field, value in r.hscan_iter('large_hash', count=100):
print(field, value)
- Activa los flags
lazyfree(Redis 4.0+). El borrado asíncrono evita picos de latencia cuando claves grandes expiran o se evictan:
# redis.conf
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes
- Inspecciona el almacenamiento con
OBJECT ENCODING. Redis alterna entre codificaciones compactas y completas por clave — revisarlas te dice si un tipo de dato ocupa más de lo que debería:
encoding = r.object('encoding', 'mykey')
print(f"Encoding: {encoding}") # ej. listpack, hashtable, int
Solución de Problemas
| Error | Qué significa | Cómo arreglarlo |
|---|---|---|
ECONNREFUSED | No hay nada escuchando en 6379, o un firewall/grupo de seguridad lo bloquea | Comprueba que redis-server está arriba, el puerto abierto y el host cliente permitido |
NOAUTH Authentication required | La instancia tiene contraseña y no la enviaste | Pásala en la URL: redis://:password@host:6379 |
WRONGPASS invalid username-password pair | Credenciales ACL incorrectas (Redis 6+) | Revisa el usuario (default salvo personalización) y la contraseña |
READONLY You can't write against a read only replica | Estás escribiendo en una réplica | Apunta las escrituras al master, o mira si Sentinel acaba de hacer failover |
OOM command not allowed | maxmemory alcanzado y la política es noeviction | Sube maxmemory o cambia a una política de evicción como allkeys-lru |
MISCONF Redis is configured to save RDB snapshots | La persistencia a disco falla (normalmente disco lleno o permisos) | Libera espacio o arregla los permisos del directorio — las escrituras siguen bloqueadas hasta arreglarlo |
WRONGTYPE Operation against a key holding the wrong kind of value | La clave existe con otro tipo | Comprueba TYPE key — no puedes hacer GET a un hash ni HGETALL a un string |
Connection timed out | Caída de red o el servidor ocupado en un comando lento | Mira SLOWLOG, sube socket_timeout y mantén los comandos en O(log n) |
Ver también
- Documentación de Redis — comandos, tipos de datos, ACLs y client-side caching
- ioredis — clúster, Sentinel, TLS y opciones de reintento para Node.js
- redis-py — pools de conexión,
redis.asyncioyredis.cluster - Jedis —
JedisPooled, Sentinel y modos clúster para Java
Preguntas frecuentes
¿Debería usar Redis como mi base de datos principal?
No por sí sola. Redis vive en memoria — incluso con snapshots RDB o persistencia AOF activada, está diseñado como capa rápida junto a una base de datos real, no como sistema de registro. Combínalo con PostgreSQL o MySQL para los datos duraderos.
¿Cómo manejo el modo clúster de Redis?
Usa clientes compatibles con clúster: redis.cluster.RedisCluster en redis-py 4.x (el viejo paquete redis-py-cluster está deprecado e integrado en el cliente oficial), new Redis.Cluster() en ioredis, y JedisCluster o el RedisClusterClient de Lettuce en Java. El clúster divide los datos entre nodos, así que los comandos multi-clave solo funcionan dentro del mismo hash tag ({user:1}:name).
¿Cuál es la diferencia entre EX y PX en Redis?
EX establece la expiración en segundos, PX en milisegundos — SET k v EX 60 equivale a SET k v PX 60000. EXAT/PXAT toman timestamps absolutos en lugar de relativos, y KEEPTTL preserva el TTL existente al sobrescribir una clave.
¿Cómo implemento un limitador de tasa con Redis?
Usa INCR con un EXPIRE de ventana — o el script Lua de Ejemplos Avanzados si lo necesitas atómico entre drivers:
def rate_limit(r, user_id, max_requests=100, window=60):
key = f"rate:{user_id}"
current = r.incr(key)
if current == 1:
r.expire(key, window)
return current <= max_requests
¿Cuál es la diferencia entre MULTI/EXEC y PIPELINE?
MULTI/EXEC es una transacción — los comandos corren atómicos y nada se intercala. PIPELINE solo agrupa comandos para ahorrar round-trips; otros clientes pueden intercalarse entre ellos. Usa MULTI cuando importa la corrección (contadores, transferencias), PIPELINE cuando solo cargas datos rápido.
¿Cómo uso Redis Streams para colas de mensajes?
XADD añade entradas, XREAD/XREADGROUP las lee de vuelta — Streams te da semántica tipo Kafka-lite con grupos de consumidores, entradas pendientes y replay:
# Productor
r.xadd('events', {'type': 'order_created', 'order_id': 123})
# Consumidor
response = r.xread({'events': '0'}, block=5000, count=10)
for stream, messages in response:
for msg_id, fields in messages:
print(f"Evento {msg_id}: {fields}")
¿Cómo conecto con AUTH y TLS?
Pon ambos en la URL — rediss://user:password@host:6379 te da TLS y AUTH en una sola cadena. redis-py la lee con Redis.from_url(), ioredis la parsea en el constructor (añade tls: {} para certificados CA propios) y Jedis acepta la URI rediss:// en JedisPool/JedisPooled.
Recursos Relacionados
Conectar a MySQL
Cómo conectar a MySQL en Python, JavaScript y Java — con pool de conexiones, SSL y consultas preparadas que aguantan en producción.
RecipeConectar a PostgreSQL
Cómo conectar a bases de datos PostgreSQL en Python, JavaScript y Java.
PatternPatrón Cache-Aside
Carga datos en el caché bajo demanda desde el almacenamiento principal. Un patrón de caché que da a la aplicación control total sobre qué y cuándo cachear.
RecipePatrones de Cache de Redis para Aplicaciones de Alto
Como implementar patrones cache-aside, write-through y write-behind con Redis para reducir carga de base de datos y mejorar tiempos de respuesta
RecipeEstrategias de Caché: Navegador, CDN, Redis y Base de Datos
Implementa caché efectiva en cada capa del stack: caché de navegador, edge de CDN, caché de aplicación con Redis y caché de consultas de base de datos — con estrategias de invalidación que mantienen los datos frescos.
GuidePooling de Conexiones a Base de Datos: Guía Práctica
Guía práctica sobre pooling de conexiones de base de datos: dimensionar pools, manejar timeouts de inactividad, detectar fugas, y configurar HikariCP, PgBouncer y pools nativos en la nube para máximo throughput.