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

أنواع بيانات Redis: السلاسل النصية

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

سلاسل Redis النصية

السلاسل هي نوع البيانات الأساسي والأكثر تنوعاً في Redis. على الرغم من الاسم، فإن سلاسل Redis آمنة ثنائياً ويمكن أن تحتوي على أي نوع من البيانات - نص، أرقام، كائنات متسلسلة، أو حتى صور (حتى 512 ميجابايت).

آمن ثنائياً: يمكن أن تحتوي سلاسل Redis على أي تسلسل من البايتات، بما في ذلك البايتات الفارغة. هذا يجعلها مناسبة لتخزين البيانات الثنائية مثل الصور والملفات المضغوطة أو الكائنات المتسلسلة.

أوامر SET و GET

العمليات الأساسية للعمل مع السلاسل هي SET (الكتابة) و GET (القراءة):

// SET و GET الأساسية\n127.0.0.1:6379> SET name "Edrees Salih"\nOK\n\n127.0.0.1:6379> GET name\n"Edrees Salih"\n\n// SET يستبدل القيم الموجودة\n127.0.0.1:6379> SET name "Ahmed Ali"\nOK\n\n127.0.0.1:6379> GET name\n"Ahmed Ali"\n\n// GET لمفتاح غير موجود يرجع nil\n127.0.0.1:6379> GET nonexistent\n(nil)

استخدام SET و GET في Laravel

use Illuminate\Support\Facades\Redis;\n\n// تعيين قيمة\nRedis::set('user:name', 'Edrees Salih');\n\n// الحصول على قيمة\n$name = Redis::get('user:name');\necho $name; // "Edrees Salih"\n\n// التحقق من وجود المفتاح\nif (Redis::exists('user:name')) {\n echo "المفتاح موجود!";\n}

MSET و MGET - مفاتيح متعددة

تعيين أو الحصول على مفاتيح متعددة في عملية ذرية واحدة - أسرع بكثير من استدعاءات SET/GET متعددة:

// MSET - تعيين مفاتيح متعددة في وقت واحد\n127.0.0.1:6379> MSET user:1:name "Edrees" user:1:email "edrees@example.com" user:1:role "admin"\nOK\n\n// MGET - الحصول على مفاتيح متعددة في وقت واحد\n127.0.0.1:6379> MGET user:1:name user:1:email user:1:role\n1) "Edrees"\n2) "edrees@example.com"\n3) "admin"
// مثال Laravel\nRedis::mset([\n 'product:100:name' => 'Laptop',\n 'product:100:price' => '999.99',\n 'product:100:stock' => '50'\n]);\n\n$values = Redis::mget(['product:100:name', 'product:100:price', 'product:100:stock']);\n// يرجع: ["Laptop", "999.99", "50"]
الأداء: MSET/MGET تقلل من رحلات الشبكة ذهاباً وإياباً. تعيين 100 مفتاح باستخدام MSET أسرع بحوالي 100 مرة من 100 أمر SET فردي!

INCR و DECR - العدادات الذرية

يوفر Redis عمليات زيادة ونقصان ذرية - مثالية للعدادات والمشاهدات والإعجابات وتحديد المعدل:

// INCR - الزيادة بمقدار 1\n127.0.0.1:6379> SET page_views 100\nOK\n\n127.0.0.1:6379> INCR page_views\n(integer) 101\n\n127.0.0.1:6379> INCR page_views\n(integer) 102\n\n// DECR - النقصان بمقدار 1\n127.0.0.1:6379> DECR page_views\n(integer) 101\n\n// INCRBY - الزيادة بمقدار محدد\n127.0.0.1:6379> INCRBY page_views 10\n(integer) 111\n\n// DECRBY - النقصان بمقدار محدد\n127.0.0.1:6379> DECRBY page_views 5\n(integer) 106\n\n// INCR على مفتاح غير موجود يبدأ من 0\n127.0.0.1:6379> INCR new_counter\n(integer) 1

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

// تتبع مشاهدات الصفحة\nRedis::incr('page:home:views');\n\n// تتبع طلبات API (تحديد المعدل)\n$key = 'rate_limit:user:' . $userId . ':' . date('Y-m-d-H-i');\nRedis::incr($key);\nRedis::expire($key, 60); // تنتهي الصلاحية بعد 60 ثانية\n\nif (Redis::get($key) > 100) {\n throw new Exception('تجاوز حد المعدل');\n}\n\n// تتبع مخزون المنتج\nRedis::decrby('product:' . $productId . 'stock', $quantity);\n\n// تتبع الإعجابات/الأصوات\nRedis::incr('post:' . $postId . ':likes');
الذرية: عمليات INCR/DECR ذرية - لا توجد شروط سباق حتى مع الطلبات المتزامنة. هذا يجعل Redis مثالياً للعدادات في الأنظمة الموزعة.

APPEND و STRLEN

بناء السلاسل تدريجياً والتحقق من طولها:

// APPEND - إضافة إلى نهاية السلسلة\n127.0.0.1:6379> SET message "Hello"\nOK\n\n127.0.0.1:6379> APPEND message " World"\n(integer) 11\n\n127.0.0.1:6379> GET message\n"Hello World"\n\n// STRLEN - الحصول على طول السلسلة\n127.0.0.1:6379> STRLEN message\n(integer) 11\n\n// APPEND لمفتاح غير موجود ينشئه\n127.0.0.1:6379> APPEND newkey "value"\n(integer) 5
// مثال Laravel - بناء السجلات\n$logKey = 'app:log:' . date('Y-m-d');\nRedis::append($logKey, date('H:i:s') . ' - User logged in\n');\nRedis::append($logKey, date('H:i:s') . ' - Order created\n');\n\n$logSize = Redis::strlen($logKey);\necho "حجم السجل: " . $logSize . " بايت";

TTL و EXPIRE - انتهاء صلاحية المفتاح

تعيين انتهاء صلاحية تلقائي على المفاتيح - ضروري للتخزين المؤقت والبيانات المؤقتة:

// EXPIRE - تعيين انتهاء الصلاحية بالثواني\n127.0.0.1:6379> SET session:abc123 "user_data"\nOK\n\n127.0.0.1:6379> EXPIRE session:abc123 3600\n(integer) 1 // 1 = نجاح\n\n// TTL - التحقق من الوقت المتبقي (الثواني المتبقية)\n127.0.0.1:6379> TTL session:abc123\n(integer) 3595\n\n// EXPIREAT - انتهاء الصلاحية في طابع زمني Unix محدد\n127.0.0.1:6379> EXPIREAT session:abc123 1735689600\n(integer) 1\n\n// PERSIST - إزالة انتهاء الصلاحية\n127.0.0.1:6379> PERSIST session:abc123\n(integer) 1\n\n// TTL يرجع -1 للمفاتيح بدون انتهاء صلاحية\n127.0.0.1:6379> TTL session:abc123\n(integer) -1\n\n// TTL يرجع -2 للمفاتيح غير الموجودة\n127.0.0.1:6379> TTL nonexistent\n(integer) -2
// مثال Laravel\n// التعيين مع انتهاء الصلاحية (بالثواني)\nRedis::setex('cache:product:100', 3600, $productData);\n\n// أو استخدام set مع expire\nRedis::set('cache:product:100', $productData);\nRedis::expire('cache:product:100', 3600);\n\n// التحقق من TTL\n$ttl = Redis::ttl('cache:product:100');\necho "تنتهي الصلاحية في {$ttl} ثانية";\n\n// إزالة انتهاء الصلاحية\nRedis::persist('cache:product:100');

SETEX - التعيين مع انتهاء الصلاحية

دمج SET و EXPIRE في عملية ذرية واحدة:

// SETEX - التعيين مع انتهاء الصلاحية (بالثواني)\n127.0.0.1:6379> SETEX cache:homepage 300 "<html>...</html>"\nOK\n\n// يعادل:\n127.0.0.1:6379> SET cache:homepage "<html>...</html>"\n127.0.0.1:6379> EXPIRE cache:homepage 300\n\n// PSETEX - التعيين مع انتهاء الصلاحية بالميلي ثانية\n127.0.0.1:6379> PSETEX cache:api_response 5000 '{"status":"ok"}'\nOK
أفضل ممارسة: استخدم دائماً SETEX بدلاً من SET + EXPIRE منفصلة. إنها ذرية (لا شرط سباق) وأسرع (أمر واحد بدلاً من اثنين).

أعلام NX و XX - SET الشرطي

التحكم في وقت نجاح SET بناءً على وجود المفتاح:

// SETNX - التعيين إذا لم يكن موجوداً (علم NX)\n127.0.0.1:6379> SETNX lock:process "locked"\n(integer) 1 // نجاح - المفتاح لم يكن موجوداً\n\n127.0.0.1:6379> SETNX lock:process "locked"\n(integer) 0 // فشل - المفتاح موجود بالفعل\n\n// SET مع علم NX (صيغة حديثة)\n127.0.0.1:6379> SET lock:process "locked" NX\nOK\n\n127.0.0.1:6379> SET lock:process "locked" NX\n(nil) // فشل\n\n// SET مع علم XX - التعيين فقط إذا كان المفتاح موجوداً\n127.0.0.1:6379> SET existing_key "new_value" XX\nOK // نجاح إذا كان المفتاح موجوداً\n\n127.0.0.1:6379> SET nonexistent_key "value" XX\n(nil) // فشل - المفتاح غير موجود\n\n// دمج NX مع EX لقفل ذري مع انتهاء الصلاحية\n127.0.0.1:6379> SET lock:process "locked" NX EX 30\nOK // تم الحصول على القفل لمدة 30 ثانية

نمط القفل الموزع

// الحصول على قفل مع انتهاء صلاحية تلقائي\n$lockKey = 'lock:import_products';\n$lockValue = uniqid(); // قيمة فريدة لتحديد حامل القفل هذا\n\n$acquired = Redis::set($lockKey, $lockValue, 'NX', 'EX', 300);\n\nif ($acquired) {\n try {\n // قسم حرج - يمكن لعملية واحدة فقط أن تكون هنا\n $this->importProducts();\n } finally {\n // تحرير القفل (فقط إذا كنا لا نزال نمتلكه)\n if (Redis::get($lockKey) === $lockValue) {\n Redis::del($lockKey);\n }\n }\n} else {\n throw new Exception('تعذر الحصول على القفل - عملية أخرى قيد التشغيل');\n}

ترميز السلسلة وتحسين الذاكرة

يقوم Redis تلقائياً بتحسين تخزين السلاسل بناءً على المحتوى:

// الأعداد الصحيحة مخزنة كأعداد صحيحة (وليس سلاسل) - يوفر الذاكرة\n127.0.0.1:6379> SET count 12345\nOK\n\n127.0.0.1:6379> OBJECT ENCODING count\n"int"\n\n// السلاسل الصغيرة مخزنة بكفاءة\n127.0.0.1:6379> SET short "hello"\nOK\n\n127.0.0.1:6379> OBJECT ENCODING short\n"embstr" // سلسلة مضمنة - محسنة\n\n// السلاسل الكبيرة تستخدم ترميزاً خاماً\n127.0.0.1:6379> SET large "very long string..."\nOK\n\n127.0.0.1:6379> OBJECT ENCODING large\n"raw"
تحسين الذاكرة: يستخدم Redis ما يصل إلى 3 ترميزات مختلفة للسلاسل:
  • int: عدد صحيح موقع 64 بت (8 بايت)
  • embstr: سلاسل ≤ 44 بايت (محسنة، ثابتة)
  • raw: سلاسل > 44 بايت (ترميز قياسي)

أنماط السلاسل الشائعة

// 1. التخزين المؤقت لاستعلامات قاعدة البيانات\n$cacheKey = 'users:active:list';\n$users = Redis::get($cacheKey);\n\nif (!$users) {\n $users = User::where('active', true)->get()->toJson();\n Redis::setex($cacheKey, 600, $users); // التخزين المؤقت لمدة 10 دقائق\n}\n\n// 2. تخزين الجلسات\n$sessionId = session()->getId();\nRedis::setex('session:' . $sessionId, 7200, serialize($sessionData));\n\n// 3. التخزين المؤقت لاستجابة API\n$apiKey = 'api:weather:' . $city;\n$response = Redis::get($apiKey);\n\nif (!$response) {\n $response = $this->callWeatherAPI($city);\n Redis::setex($apiKey, 1800, $response); // التخزين المؤقت لمدة 30 دقيقة\n}\n\n// 4. أعلام الميزات\nRedis::set('feature:new_dashboard', 'enabled');\n\nif (Redis::get('feature:new_dashboard') === 'enabled') {\n // إظهار لوحة تحكم جديدة\n}
حدود حجم السلسلة:
  • الحد الأقصى لحجم السلسلة: 512 ميجابايت
  • الحد العملي: احتفظ بالسلاسل أقل من 1 ميجابايت لأفضل أداء
  • السلاسل الكبيرة تبطئ التسلسل ونقل الشبكة
  • فكر في استخدام الهاشات للبيانات المنظمة بدلاً من ذلك
تمرين: نفذ نظام عداد مشاهدات الصفحة:
  1. أنشئ دالة تزيد عدد المشاهدات لصفحة معينة
  2. تتبع المشاهدات اليومية والإجمالية (استخدم مفتاحين)
  3. اضبط العداد اليومي لتنتهي صلاحيته في نهاية اليوم
  4. أضف دالة للحصول على عدد المشاهدات الحالي
  5. اختبر بمعرفات صفحة متعددة وتحقق من عمل العدادات بشكل صحيح
  6. إضافي: نفذ محدد معدل يحجب المستخدمين الذين يشاهدون أكثر من 100 صفحة في الدقيقة

في الدرس التالي، سنستكشف قوائم ومجموعات Redis - بنيات بيانات قوية لإدارة مجموعات من العناصر.