Redis والتخزين المؤقت المتقدم

أنواع بيانات Redis: الهاشات والمجموعات المرتبة

20 دقيقة الدرس 5 من 30

هاشات Redis

هاشات Redis هي خرائط بين حقول السلاسل وقيم السلاسل، مشابهة للكائنات أو القواميس في لغات البرمجة. الهاشات مثالية لتمثيل الكائنات مثل المستخدمين أو المنتجات أو أي بيانات منظمة ذات سمات متعددة.

كفاءة الذاكرة: يتم تحسين الهاشات لاستخدام ذاكرة قليلة جداً عند تخزين كائنات صغيرة. يستخدم الهاش مع عدد قليل من الحقول ذاكرة أقل من تخزين كل حقل كمفتاح سلسلة منفصل.

HSET و HGET - عمليات الهاش الأساسية

تعيين والحصول على حقول فردية في الهاش:

// HSET - تعيين حقل في الهاش\n127.0.0.1:6379> HSET user:1000 name "Edrees Salih"\n(integer) 1\n\n127.0.0.1:6379> HSET user:1000 email "edrees@example.com"\n(integer) 1\n\n127.0.0.1:6379> HSET user:1000 age "30"\n(integer) 1\n\n// HGET - الحصول على حقل من الهاش\n127.0.0.1:6379> HGET user:1000 name\n"Edrees Salih"\n\n127.0.0.1:6379> HGET user:1000 email\n"edrees@example.com"\n\n// الحصول على حقل غير موجود يرجع nil\n127.0.0.1:6379> HGET user:1000 phone\n(nil)
// أمثلة Laravel\n// تخزين ملف تعريف المستخدم\nRedis::hset('user:1000', 'name', 'Edrees Salih');\nRedis::hset('user:1000', 'email', 'edrees@example.com');\nRedis::hset('user:1000', 'role', 'admin');\n\n// الحصول على حقل محدد\n$userName = Redis::hget('user:1000', 'name');\necho "المستخدم: {$userName}";

HMSET و HMGET - حقول متعددة

تعيين أو الحصول على حقول متعددة في وقت واحد لأداء أفضل:

// HMSET - تعيين حقول متعددة (مهمل في Redis 4.0، استخدم HSET)\n127.0.0.1:6379> HMSET product:500 name "Laptop" price "999.99" stock "50" category "electronics"\nOK\n\n// HSET الحديث يقبل أزواج حقل-قيمة متعددة\n127.0.0.1:6379> HSET product:501 name "Mouse" price "29.99" stock "200"\n(integer) 3\n\n// HMGET - الحصول على حقول متعددة\n127.0.0.1:6379> HMGET product:500 name price stock\n1) "Laptop"\n2) "999.99"\n3) "50"
// Laravel - تخزين كائن كامل\n$productData = [\n 'name' => 'Gaming Keyboard',\n 'price' => '149.99',\n 'stock' => '75',\n 'category' => 'electronics',\n 'brand' => 'Razer'\n];\n\nRedis::hmset('product:502', $productData);\n\n// الحصول على حقول متعددة\n$fields = Redis::hmget('product:502', ['name', 'price', 'stock']);\nlist($name, $price, $stock) = $fields;

HGETALL - الحصول على جميع الحقول

استرجاع جميع الحقول والقيم من الهاش:

// HGETALL - الحصول على جميع الحقول والقيم\n127.0.0.1:6379> HGETALL user:1000\n1) "name"\n2) "Edrees Salih"\n3) "email"\n4) "edrees@example.com"\n5) "age"\n6) "30"\n\n// هاش فارغ أو غير موجود يرجع مصفوفة فارغة\n127.0.0.1:6379> HGETALL nonexistent\n(empty array)
// Laravel - الحصول على كائن كامل\n$userData = Redis::hgetall('user:1000');\n\n// يرجع مصفوفة ترابطية:\n// [\n// 'name' => 'Edrees Salih',\n// 'email' => 'edrees@example.com',\n// 'age' => '30'\n// ]\n\n// الاستخدام في العروض\nreturn view('profile', ['user' => $userData]);
تحذير الأداء: HGETALL هي O(N) حيث N هو حجم الهاش. تجنب استخدام HGETALL على هاشات كبيرة جداً (>1000 حقل). استخدم HSCAN للهاشات الكبيرة أو احصل فقط على الحقول المطلوبة باستخدام HMGET.

HDEL - حذف حقول

إزالة حقل واحد أو أكثر من الهاش:

// HDEL - حذف حقول\n127.0.0.1:6379> HSET user:1000 temp_token "abc123"\n(integer) 1\n\n127.0.0.1:6379> HDEL user:1000 temp_token\n(integer) 1 // 1 = تم حذف الحقل\n\n127.0.0.1:6379> HDEL user:1000 temp_token\n(integer) 0 // 0 = الحقل لم يكن موجوداً\n\n// حذف حقول متعددة\n127.0.0.1:6379> HDEL user:1000 age phone\n(integer) 2
// Laravel - مسح البيانات الحساسة\nRedis::hdel('user:1000', 'password_reset_token');\n\n// إزالة حقول مؤقتة متعددة\nRedis::hdel('session:abc123', 'csrf_token', 'temp_data', 'flash_message');

أوامر مساعدة للهاش

// HEXISTS - التحقق من وجود الحقل\n127.0.0.1:6379> HEXISTS user:1000 email\n(integer) 1 // 1 = موجود\n\n127.0.0.1:6379> HEXISTS user:1000 phone\n(integer) 0 // 0 = غير موجود\n\n// HKEYS - الحصول على جميع أسماء الحقول\n127.0.0.1:6379> HKEYS user:1000\n1) "name"\n2) "email"\n3) "age"\n\n// HVALS - الحصول على جميع القيم\n127.0.0.1:6379> HVALS user:1000\n1) "Edrees Salih"\n2) "edrees@example.com"\n3) "30"\n\n// HLEN - الحصول على عدد الحقول\n127.0.0.1:6379> HLEN user:1000\n(integer) 3
// أمثلة Laravel\n// التحقق من وجود الحقل\nif (Redis::hexists('user:1000', 'premium_until')) {\n // المستخدم لديه اشتراك مميز\n}\n\n// الحصول على جميع أسماء الحقول\n$fields = Redis::hkeys('product:500');\n// يرجع: ['name', 'price', 'stock', 'category']\n\n// عد الحقول\n$fieldCount = Redis::hlen('user:1000');

HINCRBY - زيادة حقول الهاش

زيادة الحقول الرقمية في الهاش بشكل ذري:

// HINCRBY - الزيادة بعدد صحيح\n127.0.0.1:6379> HSET product:500 views 0\n(integer) 1\n\n127.0.0.1:6379> HINCRBY product:500 views 1\n(integer) 1\n\n127.0.0.1:6379> HINCRBY product:500 views 5\n(integer) 6\n\n// النقصان بقيمة سالبة\n127.0.0.1:6379> HINCRBY product:500 stock -1\n(integer) 49\n\n// HINCRBYFLOAT - الزيادة بعدد عشري\n127.0.0.1:6379> HSET product:500 rating 4.5\n(integer) 1\n\n127.0.0.1:6379> HINCRBYFLOAT product:500 rating 0.3\n"4.8"
// Laravel - تتبع الإحصائيات\n// عداد مشاهدات المنتج\nRedis::hincrby('product:500', 'views', 1);\n\n// تقليل المخزون عند الشراء\nRedis::hincrby('product:500', 'stock', -$quantity);\n\n// تحديث متوسط التقييم\n$newRating = 4.8;\nRedis::hset('product:500', 'rating', $newRating);\nRedis::hincrby('product:500', 'rating_count', 1);

حالات استخدام عملية للهاش

// 1. تخزين جلسة المستخدم\nclass RedisSessionHandler {\n public function create($sessionId, $userId, $data) {\n Redis::hmset("session:{$sessionId}", [\n 'user_id' => $userId,\n 'ip_address' => request()->ip(),\n 'user_agent' => request()->userAgent(),\n 'created_at' => time(),\n 'data' => serialize($data)\n ]);\n Redis::expire("session:{$sessionId}", 7200); // ساعتان\n }\n \n public function getData($sessionId) {\n return Redis::hgetall("session:{$sessionId}");\n }\n}\n\n// 2. التخزين المؤقت لكتالوج المنتجات\nclass ProductCache {\n public function cacheProduct($product) {\n Redis::hmset("product:{$product->id}", [\n 'name' => $product->name,\n 'price' => $product->price,\n 'stock' => $product->stock,\n 'description' => $product->description,\n 'category' => $product->category,\n 'updated_at' => $product->updated_at\n ]);\n Redis::expire("product:{$product->id}", 3600);\n }\n \n public function getProduct($productId) {\n $cached = Redis::hgetall("product:{$productId}");\n return $cached ?: Product::find($productId);\n }\n}

المجموعات المرتبة في Redis

المجموعات المرتبة هي مجموعات من السلاسل الفريدة (الأعضاء) حيث يرتبط كل عضو بنقاط. يتم ترتيب الأعضاء حسب النقاط، مما يجعل المجموعات المرتبة مثالية للوحات المتصدرين والتصنيفات وقوائم انتظار الأولوية.

ميزة فريدة: تجمع المجموعات المرتبة بين تفرد المجموعات والوصول المرتب حسب النقاط. يتم تنفيذها باستخدام جدول تجزئة وقائمة تخطي، مما يوفر عمليات O(log N).

ZADD - إضافة أعضاء مع نقاط

// ZADD - إضافة أعضاء مع نقاط\n127.0.0.1:6379> ZADD leaderboard 100 "player1"\n(integer) 1\n\n127.0.0.1:6379> ZADD leaderboard 250 "player2" 180 "player3"\n(integer) 2\n\n// تحديث النقاط (العضو موجود بالفعل)\n127.0.0.1:6379> ZADD leaderboard 300 "player1"\n(integer) 0 // 0 = تم تحديث العضو، لم تتم إضافته\n\n// علم NX - إضافة فقط إذا لم يكن موجوداً\n127.0.0.1:6379> ZADD leaderboard NX 400 "player1"\n(integer) 0 // فشل - العضو موجود\n\n// علم XX - تحديث فقط إذا كان موجوداً\n127.0.0.1:6379> ZADD leaderboard XX 350 "player1"\n(integer) 0 // تم تحديث العضو الموجود
// Laravel - لوحة متصدري اللعبة\nRedis::zadd('game:leaderboard', 1500, 'user:1000');\nRedis::zadd('game:leaderboard', 2300, 'user:1001');\nRedis::zadd('game:leaderboard', 1800, 'user:1002');\n\n// تحديث نقاط اللاعب\n$userId = 1000;\n$newScore = 2500;\nRedis::zadd('game:leaderboard', $newScore, "user:{$userId}");

ZRANGE و ZREVRANGE - الحصول على نطاقات

استرجاع الأعضاء بالترتيب حسب النقاط (تصاعدي أو تنازلي):

// ZRANGE - الحصول على أعضاء حسب الرتبة (تصاعدي)\n127.0.0.1:6379> ZADD scores 10 "Alice" 20 "Bob" 15 "Charlie" 25 "David"\n(integer) 4\n\n127.0.0.1:6379> ZRANGE scores 0 -1\n1) "Alice" // النقاط: 10\n2) "Charlie" // النقاط: 15\n3) "Bob" // النقاط: 20\n4) "David" // النقاط: 25\n\n// ZRANGE مع النقاط\n127.0.0.1:6379> ZRANGE scores 0 -1 WITHSCORES\n1) "Alice"\n2) "10"\n3) "Charlie"\n4) "15"\n5) "Bob"\n6) "20"\n7) "David"\n8) "25"\n\n// ZREVRANGE - الحصول على أعضاء بترتيب عكسي (تنازلي)\n127.0.0.1:6379> ZREVRANGE scores 0 2\n1) "David" // أعلى النقاط أولاً\n2) "Bob"\n3) "Charlie"\n\n// الحصول على أفضل 3\n127.0.0.1:6379> ZREVRANGE scores 0 2 WITHSCORES\n1) "David"\n2) "25"\n3) "Bob"\n4) "20"\n5) "Charlie"\n6) "15"
// Laravel - الحصول على أفضل 10 لاعبين\n$topPlayers = Redis::zrevrange('game:leaderboard', 0, 9, 'WITHSCORES');\n\n// التنسيق للعرض\n$leaderboard = [];\nfor ($i = 0; $i < count($topPlayers); $i += 2) {\n $leaderboard[] = [\n 'player' => $topPlayers[$i],\n 'score' => $topPlayers[$i + 1]\n ];\n}\n\n// الحصول على اللاعبين ذوي الأداء الأدنى\n$bottomPlayers = Redis::zrange('game:leaderboard', 0, 9, 'WITHSCORES');

ZSCORE و ZRANK - الحصول على النقاط والرتبة

// ZSCORE - الحصول على نقاط العضو\n127.0.0.1:6379> ZSCORE scores "Bob"\n"20"\n\n127.0.0.1:6379> ZSCORE scores "NonExistent"\n(nil)\n\n// ZRANK - الحصول على رتبة العضو (تبدأ من 0، تصاعدي)\n127.0.0.1:6379> ZRANK scores "Bob"\n(integer) 2 // المركز الثالث (Alice، Charlie، Bob)\n\n// ZREVRANK - الحصول على الرتبة العكسية (تنازلي)\n127.0.0.1:6379> ZREVRANK scores "Bob"\n(integer) 1 // الثاني من الأعلى
// Laravel - إظهار إحصائيات اللاعب\n$userId = 1000;\n$score = Redis::zscore('game:leaderboard', "user:{$userId}");\n$rank = Redis::zrevrank('game:leaderboard', "user:{$userId}");\n\nif ($score !== null) {\n echo "نقاطك: {$score}<br>";\n echo "ترتيبك: " . ($rank + 1) . " من " . Redis::zcard('game:leaderboard');\n}

ZRANGEBYSCORE - نطاق حسب النقاط

الحصول على الأعضاء ضمن نطاق نقاط:

// ZRANGEBYSCORE - الحصول على أعضاء بنقاط بين الحد الأدنى والأقصى\n127.0.0.1:6379> ZRANGEBYSCORE scores 15 25\n1) "Charlie" // النقاط: 15\n2) "Bob" // النقاط: 20\n3) "David" // النقاط: 25\n\n// نطاق حصري مع بادئة (\n127.0.0.1:6379> ZRANGEBYSCORE scores (15 25\n1) "Bob" // 15 مستبعد\n2) "David"\n\n// نطاقات لانهائية\n127.0.0.1:6379> ZRANGEBYSCORE scores -inf 20\n1) "Alice"\n2) "Charlie"\n3) "Bob"\n\n// مع حد (الإزاحة والعدد)\n127.0.0.1:6379> ZRANGEBYSCORE scores 0 100 LIMIT 0 2\n1) "Alice"\n2) "Charlie"
// Laravel - الحصول على اللاعبين في نطاق نقاط\n// لاعبون بنقاط 1000-2000\n$midTierPlayers = Redis::zrangebyscore(\n 'game:leaderboard',\n 1000,\n 2000,\n ['withscores' => true]\n);\n\n// الحصول على منشورات اليوم حسب الطابع الزمني\n$todayStart = strtotime('today');\n$todayEnd = strtotime('tomorrow');\n$todayPosts = Redis::zrangebyscore(\n 'posts:timeline',\n $todayStart,\n $todayEnd\n);

ZINCRBY و ZREM - تعديل المجموعات المرتبة

// ZINCRBY - زيادة نقاط العضو\n127.0.0.1:6379> ZADD counters 10 "page_views"\n(integer) 1\n\n127.0.0.1:6379> ZINCRBY counters 5 "page_views"\n"15"\n\n127.0.0.1:6379> ZINCRBY counters -2 "page_views"\n"13"\n\n// ZREM - إزالة أعضاء\n127.0.0.1:6379> ZREM scores "Alice"\n(integer) 1 // 1 = تمت إزالة العضو\n\n127.0.0.1:6379> ZREM scores "Alice"\n(integer) 0 // 0 = العضو لم يكن موجوداً\n\n// ZCARD - الحصول على عدد الأعضاء\n127.0.0.1:6379> ZCARD scores\n(integer) 3
// Laravel - زيادة نقاط اللاعب\nRedis::zincrby('game:leaderboard', 100, "user:{$userId}"); // إضافة 100 نقطة\n\n// إزالة لاعب محظور\nRedis::zrem('game:leaderboard', "user:{$bannedUserId}");\n\n// الحصول على إجمالي اللاعبين\n$totalPlayers = Redis::zcard('game:leaderboard');

حالات استخدام متقدمة للمجموعات المرتبة

// 1. منشورات رائجة مع التدهور\nclass TrendingPosts {\n public function addView($postId) {\n $score = time(); // استخدام الطابع الزمني كنقاط\n Redis::zadd('posts:trending', $score, $postId);\n }\n \n public function getTrending($hours = 24) {\n $cutoff = time() - ($hours * 3600);\n // الحصول على منشورات من آخر 24 ساعة\n return Redis::zrevrangebyscore(\n 'posts:trending',\n '+inf',\n $cutoff,\n ['limit' => [0, 10]]\n );\n }\n \n public function cleanup() {\n $weekAgo = time() - (7 * 24 * 3600);\n // إزالة منشورات أقدم من أسبوع واحد\n Redis::zremrangebyscore('posts:trending', '-inf', $weekAgo);\n }\n}\n\n// 2. قائمة انتظار الأولوية\nclass PriorityQueue {\n public function enqueue($jobId, $priority) {\n // أولوية أعلى = نقاط أعلى\n Redis::zadd('jobs:queue', $priority, $jobId);\n }\n \n public function dequeue() {\n // الحصول على وإزالة وظيفة ذات أولوية أعلى\n $jobs = Redis::zrevrange('jobs:queue', 0, 0);\n if (!empty($jobs)) {\n $jobId = $jobs[0];\n Redis::zrem('jobs:queue', $jobId);\n return $jobId;\n }\n return null;\n }\n}
متى تستخدم كل نوع بيانات:
  • السلاسل: مفتاح-قيمة بسيط، عدادات، تخزين مؤقت
  • القوائم: قوائم انتظار، موجزات النشاط، عناصر حديثة
  • المجموعات: علامات، تتبع فريد، عمليات المجموعة
  • الهاشات: كائنات بحقول، بيانات منظمة
  • المجموعات المرتبة: لوحات متصدرين، تصنيفات، سلاسل زمنية
تمرين: أنشئ نظام تصنيف كامل لمنشورات المدونة:
  1. استخدم المجموعات المرتبة لتتبع المنشورات حسب: عدد المشاهدات وعدد الإعجابات والحداثة
  2. استخدم الهاشات لتخزين بيانات تعريف المنشور (العنوان والمؤلف والفئة)
  3. نفذ خوارزمية "الرائج" تجمع بين المشاهدات والحداثة
  4. أنشئ لوحة متصدرين تعرض أفضل 10 منشورات حسب كل مقياس
  5. أضف دالة للحصول على ترتيب ونقاط منشور المستخدم
  6. نفذ تنظيفاً تلقائياً للمنشورات القديمة من الرائجة

تهانينا! أنت الآن تفهم جميع أنواع بيانات Redis الرئيسية وحالات استخدامها. في الدروس المستقبلية، سنستكشف تجميع Redis والثبات وبرمجة Lua النصية والتكامل مع قوائم انتظار Laravel والتخزين المؤقت.