Caching in Rust: Redis, Memcached und integrierte Lösungen (Fortgeschrittenes Niveau)

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einleitung: Warum Caching in Rust eine Herausforderung und ein Vorteil ist

\n

In hochbelasteten Systemen mit Rust ist Caching nicht mehr nur das einfache Hinzufügen einer Bibliothek. Aufgrund des strengen Eigentümersystems, des Fehlens eines Garbage Collectors und der Anforderungen an die Threadsicherheit müssen traditionelle Ansätze überdacht werden. Rust bietet jedoch einzigartige Möglichkeiten: vorhersagbare Antwortzeiten, keine Gemeinkosten durch GC und vollständige Kontrolle über den Speicher. In diesem Artikel werden wir fortgeschrittene Caching-Techniken behandeln: von verteilten Lösungen (Redis, Memcached) bis hin zu integrierten Caches (moka, lru) und deren Kombination.

\n\n

1. Asynchrone Arbeit mit Redis über deadpool-redis

\n

1.1. Warum nicht direkt redis-rs?

\n

Der Client redis-rs ist synchron. Für asynchrone Anwendungen mit tokio wird ein Verbindungspool benötigt. deadpool-redis bietet einen asynchronen Pool, der Verbindungen verwaltet und bei Ausfällen automatisch neu verbindet.

\n\n

1.2. Beispiel: Caching mit TTL und Serialisierung

\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    // Versuch, aus dem Cache zu lesen\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 Sekunden\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. Fortgeschrittene Strategie: Cache-Aside mit Sperren

\n

Bei einem Cache Stampede (wenn viele Anfragen gleichzeitig versuchen, den Cache neu zu füllen) verwenden Sie einen Mutex auf Anwendungsebene:

\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: Leichtigkeit und Geschwindigkeit mit memcache-async

\n

2.1. Besonderheiten von Memcached für Rust

\n

Memcached ist einfacher als Redis: keine Persistenz und komplexen Datenstrukturen, aber es ist schneller für einfache Key-Value-Operationen. Die Crate memcache-async bietet einen asynchronen Client für tokio.

\n\n

2.2. Beispiel: Batch-Laden und Pipelining

\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

Buchempfehlungen