Einleitung: Warum Caching in Rust?
\nGeschwindigkeit ist der Hauptvorteil von Rust. Aber selbst der schnellste Code kann an langsamen Ein-/Ausgaben (I/O) scheitern: Datenbankabfragen, externe APIs oder Berechnungen komplexer Daten. Caching löst dieses Problem, indem es die Ergebnisse teurer Operationen in einem schnellen Speicher ablegt. Rust bietet flexible Werkzeuge für das Caching: von einfachen integrierten Datenstrukturen bis hin zu leistungsstarken verteilten Systemen wie Redis und Memcached.
\nIn diesem Artikel werden wir alle Ebenen des Cachings in Rust behandeln: von einfachem HashMap bis zu professionellen Lösungen mit TTL (Time-to-Live) und Synchronisation. Sie erfahren, wie Sie das passende Werkzeug für Ihr Projekt auswählen und typische Fehler vermeiden.
1. Integriertes Caching: HashMap und seine Varianten
\nDer einfachste Weg zum Caching in Rust ist die Verwendung von HashMap aus der Standardbibliothek. In einer Multithread-Umgebung sind jedoch zusätzliche Synchronisationsmechanismen erforderlich.
1.1. Einfaches HashMap (single-threaded)
\nFür single-threaded Anwendungen eignet sich ein gewöhnliches HashMap. Beispiel:
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!("Gecachter Wert: {}", name);\n }\n}\nVorteile: minimaler Code, keine Abhängigkeiten.
\nNachteile: kein TTL, nicht threadsicher, Speicher nicht begrenzt.
1.2. Threadsicherer Cache mit Mutex und Arc
\nFür Multithread-Anwendungen verwenden Sie 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}\nNachteil: Die Mutex-Sperre kann bei hoher Konkurrenz die Leistung beeinträchtigen.
\n\n1.3. Fortgeschrittene Bibliotheken: lru und cached
\nFür Produktionscode ist es besser, fertige Crates zu verwenden:
\n- \n
lru— implementiert den LRU (Least Recently Used) Verdrängungsalgorithmus. Geeignet für begrenzte Caches. \n cached— bietet Makros für automatisches Caching von Funktionen mit Unterstützung für TTL und LRU. \n
Beispiel mit 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); // dauert 2 Sek\n let cached_result = expensive_computation(5); // sofort\n println!("{}", cached_result);\n}\n\n2. Redis: Verteilter Cache für skalierbare Systeme
\nRedis ist ein In-Memory-Datenspeicher, der sich ideal für verteiltes Caching eignet. In Rust wird für die Arbeit mit Redis das Crate redis verwendet.
2.1. Verbindung und grundlegende Operationen
\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 // Wert mit TTL (Lebensdauer) setzen\n con.set_ex("user:1", "Alice", 3600)?; //