Introduction : Pourquoi la mise en cache en Rust ?
\nLa vitesse est le principal avantage de Rust. Mais même le code le plus rapide peut se heurter à des entrées/sorties (E/S) lentes : requêtes vers une base de données, API externes ou calculs de données complexes. La mise en cache résout ce problème en stockant les résultats d'opérations coûteuses dans un stockage rapide. Rust propose des outils flexibles pour la mise en cache : des structures de données intégrées simples aux puissants systèmes distribués comme Redis et Memcached.
\nDans cet article, nous aborderons tous les niveaux de mise en cache en Rust : du simple HashMap aux solutions professionnelles avec TTL (durée de vie) et synchronisation. Vous apprendrez à choisir l'outil adapté à votre projet et à éviter les erreurs courantes.
1. Mise en cache intégrée : HashMap et ses variantes
\nLa façon la plus simple de mettre en cache en Rust est d'utiliser HashMap de la bibliothèque standard. Cependant, dans un environnement multithread, des mécanismes de synchronisation supplémentaires sont nécessaires.
1.1. HashMap simple (monothread)
\nPour les applications monothread, un HashMap standard convient. Exemple :
use std::collections::HashMap;\n\nfn main() {\n let mut cache: HashMap<String, String> = HashMap::new();\n cache.insert("user:1".to_string(), "Alice".to_string());\n \n if let Some(name) = cache.get("user:1") {\n println!("Valeur en cache : {}", name);\n }\n}\nAvantages : code minimal, aucune dépendance.
\nInconvénients : pas de TTL, non thread-safe, mémoire non limitée.
1.2. Cache thread-safe avec Mutex et Arc
\nPour les applications multithread, utilisez Arc<Mutex<HashMap>> :
use std::collections::HashMap;\nuse std::sync::{Arc, Mutex};\nuse std::thread;\n\nstruct Cache {\n store: Arc<Mutex<HashMap<String, String>>>,\n}\n\nimpl Cache {\n fn new() -> Self {\n Cache { store: Arc::new(Mutex::new(HashMap::new())) }\n }\n \n fn get(&self, key: &str) -> Option<String> {\n self.store.lock().ok()?.get(key).cloned()\n }\n \n fn set(&self, key: String, value: String) {\n self.store.lock().unwrap().insert(key, value);\n }\n}\n\nfn main() {\n let cache = Cache::new();\n let c1 = cache.clone();\n thread::spawn(move || {\n c1.set("key".to_string(), "value".to_string());\n });\n println!("{:?}", cache.get("key"));\n}\nInconvénient : le verrouillage Mutex peut réduire les performances en cas de forte concurrence.
\n\n1.3. Bibliothèques avancées : lru et cached
\nPour le code de production, il est préférable d'utiliser des crates prêts à l'emploi :
\n- \n
lru— implémente l'algorithme d'éviction LRU (Least Recently Used). Convient pour un cache limité. \n cached— fournit des macros pour la mise en cache automatique des fonctions avec prise en charge du TTL et du LRU. \n
Exemple avec cached :
use cached::proc_macro::cached;\n\n#[cached(size = 100, time = 300)]\nfn expensive_computation(x: u32) -> u32 {\n std::thread::sleep(std::time::Duration::from_secs(2));\n x * x\n}\n\nfn main() {\n let result = expensive_computation(5); // s'exécute en 2 sec\n let cached_result = expensive_computation(5); // instantané\n println!("{}", cached_result);\n}\n\n2. Redis : cache distribué pour les systèmes évolutifs
\nRedis est un magasin de données en mémoire, idéal pour la mise en cache distribuée. En Rust, la crate redis est utilisée pour travailler avec Redis.
2.1. Connexion et opérations de base
\nuse redis::Commands;\n\nfn main() -> redis::RedisResult<()> {\n let client = redis::Client::open("redis://127.0.0.1:6379/")?;\n let mut con = client.get_connection()?;\n \n // Définition d'une valeur avec TTL (durée de vie)\n con.set_ex("user:1", "Alice", 3600)?; //