Mise en cache en Rust : Redis, Memcached et solutions intégrées (niveau avancé)

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : Pourquoi la mise en cache en Rust est un défi et un avantage

\n

Dans les systèmes à forte charge en Rust, la mise en cache cesse d'être un simple ajout de bibliothèque. En raison du système de propriété strict, de l'absence de ramasse-miettes et des exigences de sécurité des threads, les approches traditionnelles nécessitent une refonte. Cependant, Rust offre des possibilités uniques : temps de réponse prévisible, frais généraux nuls pour le GC et contrôle total de la mémoire. Dans cet article, nous aborderons les techniques avancées de mise en cache : des solutions distribuées (Redis, Memcached) aux caches intégrés (moka, lru) et leurs combinaisons.

\n\n

1. Travail asynchrone avec Redis via deadpool-redis

\n

1.1. Pourquoi pas redis-rs directement ?

\n

Le client redis-rs est synchrone. Pour les applications asynchrones sur tokio, un pool de connexions est nécessaire. deadpool-redis fournit un pool asynchrone qui gère les connexions et se reconnecte automatiquement en cas de panne.

\n\n

1.2. Exemple : mise en cache avec TTL et sérialisation

\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    // Tentative de lecture depuis le cache\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 secondes\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. Stratégie avancée : Cache-Aside avec verrous

\n

En cas de cache stampede (lorsque plusieurs requêtes tentent simultanément de repeupler le cache), utilisez un mutex au niveau de l'application :

\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 : légèreté et rapidité avec memcache-async

\n

2.1. Particularités de Memcached pour Rust

\n

Memcached est plus simple que Redis : pas de persistance ni de structures de données complexes, mais il est plus rapide pour les opérations simples de type key-value. La crate memcache-async fournit un client asynchrone sur tokio.

\n\n

2.2. Exemple : chargement par lots et pipeline

\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    // P                

Blogs

Book Recommendations