Caché en Rust: Redis, Memcached y soluciones integradas (nivel avanzado)

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: Por qué el caché en Rust es un desafío y una ventaja

\n

En sistemas de alta carga en Rust, el caché deja de ser una simple adición de una biblioteca. Debido al estricto sistema de propiedad, la ausencia de un recolector de basura y los requisitos de seguridad en la concurrencia, los enfoques tradicionales requieren un replanteamiento. Sin embargo, Rust ofrece capacidades únicas: tiempo de respuesta predecible, sobrecarga cero de GC y control total sobre la memoria. En este artículo, exploraremos técnicas avanzadas de caché: desde soluciones distribuidas (Redis, Memcached) hasta cachés integrados (moka, lru) y sus combinaciones.

\n\n

1. Trabajo asíncrono con Redis a través de deadpool-redis

\n

1.1. ¿Por qué no redis-rs directamente?

\n

El cliente redis-rs es síncrono. Para aplicaciones asíncronas con tokio, se requiere un pool de conexiones. deadpool-redis proporciona un pool asíncrono que gestiona las conexiones y se reconecta automáticamente en caso de fallos.

\n\n

1.2. Ejemplo: caché con TTL y serialización

\n
use deadpool_redis::{Config, Runtime, Pool};\nuse serde::{Serialize, Deserialize};\nuse tokio::sync::OnceCell;\n\n#[derive(Serialize, Deserialize, Clone)]\nstruct User {\n    id: u64,\n    name: String,\n    email: String,\n}\n\nasync fn get_pool() -> Pool {\n    static POOL: OnceCell<Pool> = OnceCell::const_new();\n    POOL.get_or_init(|| async {\n        let cfg = Config::from_url("redis://127.0.0.1/");\n        cfg.create_pool(Some(Runtime::Tokio1)).unwrap()\n    }).await.clone()\n}\n\nasync fn get_user_cached(user_id: u64) -> Option<User> {\n    let pool = get_pool().await;\n    let mut conn = pool.get().await.unwrap();\n    let key = format!("user:{}", user_id);\n\n    // Intento de leer desde la caché\n    if let Ok(Some(data)) = redis::cmd("GET").arg(&key).query_async::<Option<Vec<u8>>>(&mut conn).await {\n        if let Ok(user) = bincode::deserialize::<User>(&data) {\n            return Some(user);\n        }\n    }\n    None\n}\n\nasync fn set_user_cache(user: &User) {\n    let pool = get_pool().await;\n    let mut conn = pool.get().await.unwrap();\n    let key = format!("user:{}", user.id);\n    let data = bincode::serialize(user).unwrap();\n\n    // TTL 300 segundos\n    let _: () = redis::cmd("SETEX")\n        .arg(&key)\n        .arg(300)\n        .arg(&data)\n        .query_async(&mut conn)\n        .await\n        .unwrap();\n}
\n\n

1.3. Estrategia avanzada: Cache-Aside con bloqueos

\n

En caso de cache stampede (cuando múltiples solicitudes intentan recargar la caché simultáneamente), use un mutex a nivel de aplicación:

\n
use tokio::sync::Mutex;\nuse std::collections::HashMap;\n\nstruct CacheLock {\n    locks: Mutex<HashMap<String, tokio::sync::Semaphore>>,\n}\n\nimpl CacheLock {\n    async fn get_or_compute<F, Fut, T>(&self, key: &str, f: F) -> T\n    where\n        F: FnOnce() -> Fut,\n        Fut: Future<Output = T>,\n    {\n        let sem = {\n            let mut map = self.locks.lock().await;\n            map.entry(key.to_string())\n                .or_insert_with(|| tokio::sync::Semaphore::new(1))\n                .clone()\n        };\n        let _permit = sem.acquire().await.unwrap();\n        f().await\n    }\n}
\n\n

2. Memcached: ligereza y velocidad con memcache-async

\n

2.1. Características de Memcached para Rust

\n

Memcached es más simple que Redis: no tiene persistencia ni estructuras de datos complejas, pero es más rápido para operaciones simples de clave-valor. El crate memcache-async proporciona un cliente asíncrono sobre tokio.

\n\n

2.2. Ejemplo: carga por lotes y canalización

\n
use memcache_async::{Client, Protocol, Value};\nuse bytes::Bytes;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let client = Client::connect_async("memcache://127.0.0.1:11211").await?;\n\n    // C                

Blogs

Book Recommendations