مقدمة: لماذا التخزين المؤقت في Rust؟
\nالسرعة هي الميزة الرئيسية لـ Rust. ولكن حتى أسرع كود يمكن أن يصطدم ببطء الإدخال/الإخراج (I/O): استعلامات قاعدة البيانات، واجهات برمجة التطبيقات الخارجية، أو حسابات البيانات المعقدة. يحل التخزين المؤقت هذه المشكلة عن طريق حفظ نتائج العمليات المكلفة في مخزن سريع. تقدم Rust أدوات مرنة للتخزين المؤقت: من هياكل البيانات المدمجة البسيطة إلى الأنظمة الموزعة القوية مثل Redis وMemcached.
\nفي هذه المقالة، سنقوم بتحليل جميع مستويات التخزين المؤقت في Rust: من HashMap البسيط إلى الحلول الاحترافية مع TTL (وقت الحياة) والمزامنة. ستتعلم كيفية اختيار الأداة المناسبة لمشروعك وتجنب الأخطاء الشائعة.
1. التخزين المؤقت المدمج: HashMap ومتغيراته
\nأبسط طريقة للتخزين المؤقت في Rust هي استخدام HashMap من المكتبة القياسية. ومع ذلك، في البيئة متعددة الخيوط، هناك حاجة إلى آليات مزامنة إضافية.
1.1. HashMap بسيط (أحادي الخيط)
\nللتطبيقات أحادية الخيط، فإن HashMap العادي مناسب. مثال:
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!("القيمة المخزنة مؤقتاً: {}", name);\n }\n}\nالإيجابيات: كود أدنى، لا تبعيات.
\nالسلبيات: لا يوجد TTL، غير آمن للخيوط، الذاكرة غير محدودة.
1.2. ذاكرة تخزين مؤقت آمنة للخيوط مع Mutex وArc
\nللتطبيقات متعددة الخيوط، استخدم 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}\nالعيب: قفل Mutex يمكن أن يقلل الأداء عند التنافس العالي.
\n\n1.3. مكتبات متقدمة: lru و cached
\nلكود الإنتاج، من الأفضل استخدام الكريتات الجاهزة:
\n- \n
lru— تطبق خوارزمية الإزاحة LRU (الأقل استخداماً مؤخراً). مناسبة للذاكرة التخزينية المحدودة. \n cached— توفر ماكرو للتخزين المؤقت التلقائي للدوال مع دعم TTL وLRU. \n
مثال مع 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); // يستغرق 2 ثانية\n let cached_result = expensive_computation(5); // فوري\n println!("{}", cached_result);\n}\n\n2. Redis: ذاكرة تخزين مؤقت موزعة للأنظمة القابلة للتوسع
\nRedis هو مخزن بيانات في الذاكرة، مثالي للتخزين المؤقت الموزع. في Rust، يتم استخدام الكريت redis للعمل مع Redis.
2.1. الاتصال والعمليات الأساسية
\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 // تعيين قيمة مع TTL (وقت الحياة)\n con.set_ex("user:1", "Alice", 3600)?; //