التخزين المؤقت في Rust: Redis وMemcached والحلول المدمجة (مستوى متقدم)

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا التخزين المؤقت في Rust هو تحدٍ وميزة

\n

في الأنظمة عالية التحميل بلغة Rust، لم يعد التخزين المؤقت مجرد إضافة مكتبة. بسبب نظام الملكية الصارم، وغياب جامع القمامة، ومتطلبات أمان الخيوط، تتطلب الأساليب التقليدية إعادة تفكير. ومع ذلك، تمنح Rust إمكانيات فريدة: وقت استجابة يمكن التنبؤ به، وتكاليف إضافية صفرية لـ GC، وتحكم كامل في الذاكرة. في هذه المقالة، سنستعرض تقنيات التخزين المؤقت المتقدمة: من الحلول الموزعة (Redis، Memcached) إلى التخزين المؤقت المدمج (moka، lru) ومجموعاتها.

\n\n

1. العمل غير المتزامن مع Redis عبر deadpool-redis

\n

1.1. لماذا ليس redis-rs مباشرة؟

\n

عميل redis-rs متزامن. للتطبيقات غير المتزامنة على tokio، يلزم وجود مجموعة اتصالات. يوفر deadpool-redis مجموعة اتصالات غير متزامنة تدير الاتصالات وتعيد الاتصال تلقائيًا عند الأعطال.

\n\n

1.2. مثال: التخزين المؤقت مع TTL والتسلسل

\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    // محاولة القراءة من التخزين المؤقت\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 ثانية\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. استراتيجية متقدمة: Cache-Aside مع الأقفال

\n

عند حدوث cache stampede (عندما تحاول طلبات متعددة إعادة ملء التخزين المؤقت في وقت واحد)، استخدم كائن المزامنة (mutex) على مستوى التطبيق:

\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: الخفة والسرعة مع memcache-async

\n

2.1. ميزات Memcached لـ Rust

\n

Memcached أبسط من Redis: لا يوجد استمرارية أو هياكل بيانات معقدة، ولكنه أسرع لعمليات key-value البسيطة. يوفر كريت memcache-async عميلاً غير متزامن على tokio.

\n\n

2.2. مثال: التحميل المجمع وخط الأنابيب

\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                

المدونات

توصيات الكتب