تعتبر سرعه وكفاءه أي تطبيق من العوامل الأساسية لنجاح أي مشروع، وإذا كنا نتعامل مع بيانات بحجم كبير جدا، فإن قواعد البيانات التقليدية بمفردها لن تكون قادره على تلبية متطلبات الأداء العالية، من هنا، تبرز أهمية Redis: نظام تخزين بيانات داخل الذاكرة (In-Memory) يُستخدم كقاعدة بيانات، ووسيط رسائل، وذاكرة تخزين مؤقت، وقائمة انتظار، والمزيد.
Redisهو نظام مفتوح المصدر لتخزين البيانات على شكل مفتاح-قيمة (Key-Value)، لكنه لا يقتصر على أنواع البيانات البسيطة. بل يدعم هياكل متعددة مثل السلاسل، القوائم، المجموعات .... وبما أن Redisيُخزن البيانات في الذاكرة (RAM)، فإنه يقدم أداءً استثنائيًا يصل إلى ملايين العمليات في الثانية، مما يجعله خيارًا مثاليًا لتخزين البيانات المؤقتة أو المتكررة.
قبل أن نبدأ في استخدام Redis مع Laravel، نحتاج إلى تثبيته وتشغيله على نظامك.
تثبيت Redis:
طرق التثبيت تختلف حسب نظام التشغيل الخاص بك.
على Linux (Ubuntu/Debian):
sudo apt update sudo apt install redis-server
بعد التثبيت، يجب أن يبدأ خادم Redis تلقائياً. يمكنك التحقق من حالته باستخدام:
sudo systemctl status redis-server
على macOS باستخدام Homebrew:
brew install redis brew services start redis
قبل الانتقال إلىLaravel، دعنا نلقي نظرة سريعة على كيفية التفاعل معRedisمباشرة باستخدامredis-cli.
كتابة البيانات(SET):
استخدم الأمر SET لتخزين قيمة بسلسلة (string) معينة كمفتاح (key).
redis-cli SET myRedisKey "Hello Redis"
الإستجابه ستكون OK
قراءة البيانات GET
استخدم الأمرGETلاسترجاع القيمة المرتبطة بمفتاح معين.
GET myRedisKey
الاستجابة ستكونHello Redis.
تعيين مفتاح مع انتهاء صلاحية(SETEX):
يمكنك تعيين مفتاح بمدة انتهاء صلاحية بالثواني.
SETEX anotherkey 10
بعد 10 ثوانٍ، إذا حاولتGET anotherkey، ستحصل على(nil)، مما يعني أن المفتاح لم يعد موجوداً.
حذف مفتاح(DEL):
لحذف مفتاح واحد أو أكثر.
DEL mykey DEL key1 key2 key3
RedisInsight هو واجهة رسومية GUI مجانية وقوية طورتها شركة Redis, تُستخدم لإدارة وتحليل قواعد بيانات Redis بشكل مرئي. توفر تجربة مستخدم سهلة تتيح لك استكشاف البيانات، تنفيذ أوامر Redis، وتحليل الأداء دون الحاجة لكتابة أوامر سطرية
بداية نحتاج لتثبيت حزمة predis
composer require predis/predis
ومن ثم تكوين Redis في ملف config/database.php.
'redis' => [ 'client' => env('REDIS_CLIENT', 'predis'), // أو 'phpredis' 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), ], 'default' => [ 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), 'database' => env('REDIS_DB', 0), ], 'cache' => [ 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), 'database' => env('REDIS_CACHE_DB', 1), // قاعدة بيانات مختلفة للكاش ], // ... يمكن إضافة اتصالات أخرى هنا للطوابير مثلاً ],
لاحظ أن Laravel يسمح لك بتحديد قواعد بيانات Redis مختلفة لأغراض مختلفة (مثل الكاش والطوابير) باستخدام REDIS_DB و REDIS_CACHE_DB إلخ. هذا يساعد على تنظيم البيانات داخل Redis.
في Redis لا توجد قواعد بيانات بأسماء كما في MySQL أو PostgreSQL، بل يتم ترقيم قواعد البيانات بأرقام صحيحة تبدأ من 0 حتى 16.
في الإعدادات أعلاه
'database' => env('REDIS_DB', 0),
هذا يعني ان laravel سوف يتعامل مع قاعده البيانات 0 وللتأكد
127.0.0.1:6379> SELECT 0
فأنت تختار قاعدة البيانات رقم 0.
Laravelيوفر واجهة بسيطة ومرنة للتخزين المؤقت عبر الواجهة Cache Facade.
يمكنك استخدام Cache::put() لتخزين البيانات لفترة محددة بالدقائق.
use Illuminate\Support\Facades\Cache;
تخزين سلسلة نصية لمدة 60 دقيقة
Cache::put('greeting', 'Hello from Redis Cache!', 60);
تخزين مصفوفة لمدة 30 دقيقة
Cache::put('user_settings', ['theme' => 'dark', 'notifications' => true], 30);
استخدم Cache::get() لاسترجاع البيانات.
$greeting = Cache::get('greeting');
$userSettings = Cache::get('user_settings');
إذا لم يكن المفتاح موجوداً في الكاش، سيعيد null. يمكنك توفير قيمة افتراضية:
$nonExistentKey = Cache::get('some_key', 'Default Value'); // $nonExistentKey سيكون "Default Value"
استخدم Cache::forget() لإزالة مفتاح معين.
Cache::forget('greeting');
Cache::forever('important_data', ['value' => 123]);
تعد الدالة Cache::remember() قوية جداً. إذا كان المفتاح موجوداً في الكاش، فسيتم إرجاع قيمته. وإلا، فسيتم تنفيذ الـ closure وتخزين القيمة الناتجة في الكاش قبل إرجاعها.
$users = Cache::remember('all_users', 60, function () { // هذه الدالة ستنفذ فقط إذا لم يكن 'all_users' موجوداً في الكاش return \App\Models\User::all(); });
هذا النمط مفيد جداً لتقليل عدد الاستعلامات على قاعدة البيانات.
مثال في Controller
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; class ProductController extends Controller { public function show($id) { // محاولة جلب المنتج من الكاش أولاً $product = Cache::remember('product_' . $id, 60, function () use ($id) { // إذا لم يكن موجوداً في الكاش، قم بالبحث عنه في قاعدة البيانات return \App\Models\Product::find($id); }); if (!$product) { abort(404); } return view('product.show', compact('product')); } public function update(Request $request, $id) { $product = \App\Models\Product::find($id); if (!$product) { abort(404); } // تحديث المنتج في قاعدة البيانات $product->update($request->all()); // حذف المنتج من الكاش لضمان جلب أحدث البيانات في المرة القادمة Cache::forget('product_' . $id); return redirect()->back()->with('success', 'Product updated successfully!'); } }
تخزين نماذج Eloquent في الكاش يمكن أن يحسن أداء التطبيق بشكل كبير، خاصة للصفحات التي تعرض نفس البيانات بشكل متكرر.
تخزين نموذج فردي:
use App\Models\Post; use Illuminate\Support\Facades\Cache;
جلب منشور معين من قاعدة البيانات
$post = Post::find(1);
// تخزينه في الكاش لمدة 120 دقيقة
Cache::put('post_1', $post, 120);
استرجاعه من الكاش
$cachedPost = Cache::get('post_1');
// جلب آخر 10 منشورات
$latestPosts = Post::orderBy('created_at', 'desc')->take(10)->get();
تخزينها في الكاش
Cache::put('latest_posts', $latestPosts, 60);
استرجاعها
$cachedLatestPosts = Cache::get('latest_posts');
هذا هو النمط الأكثر شيوعاً وفعالية:
use App\Models\User; use Illuminate\Support\Facades\Cache;
في Controller أو Service:
public function getUserProfile($userId) { $user = Cache::remember('user_' . $userId, 60 * 24, function () use ($userId) { // تخزين لمدة 24 ساعة return User::with('posts', 'comments')->find($userId); }); if (!$user) { abort(404, 'User not found.'); } return view('profile.show', compact('user')); }
تحديث الكاش بعد تحديث أو حذف نموذج Eloquent:
عندما يتم تحديث أو حذف نموذج Eloquent، يجب عليك إزالة المفتاح المقابل من الكاش لضمان أن المستخدمين يرون أحدث البيانات.
use App\Models\Product; use Illuminate\Support\Facades\Cache; public function updateProduct(Request $request, $id) { $product = Product::find($id); if (!$product) { return redirect()->back()->with('error', 'Product not found.'); } $product->update($request->all()); // مسح المفتاح المتعلق بهذا المنتج من الكاش Cache::forget('product_' . $id); // إذا كان هناك كاش لمجموعة من المنتجات (مثل "all_products")، // قد تحتاج إلى مسحها أيضاً أو إعادة بنائها Cache::forget('all_products'); // مثال return redirect()->back()->with('success', 'Product updated successfully.'); } public function deleteProduct($id) { $product = Product::find($id); if ($product) { $product->delete(); Cache::forget('product_' . $id); Cache::forget('all_products'); // مثال } return redirect()->back()->with('success', 'Product deleted.'); }
المثال الثالث على إستخدام Redis
هنا سنقوم بعمل Seeder لكمية كبيره من البيانات ومقارنة وقت تحميل الصفحة مع / بدون التخزين المؤقت.
سننشئ Product Model
public function up(): void { Schema::create('products', function (Blueprint $table) { $table->id(); $table->string('name'); // لغرض الاختبار $table->double('price'); $table->integer('quantity'); $table->integer('sold_units'); $table->timestamps(); }); }
Product Factory public function definition(): array { return [ 'name' => $this->faker->name(), 'price' => $this->faker->randomFloat(2, 1, 1000), 'quantity' => random_int(1, 100_000), 'sold_units' => random_int(1, 1_000_000), ]; }
DataBase Seeder class DatabaseSeeder extends Seeder { public function run(): void { $parts=30; for ($i = 1; $i <= $parts; $i++) { $products = Product::factory()->count(10_000)->make()->toArray(); DB::table('products')->insert($products); echo "Inserted batch $i\n"; } } }
Product.php class Product extends Model { use HasFactory; protected $fillable = [ 'name', 'price', 'quantity', 'sold_units', ]; }
الأن لفرض إننا نريد الحصول على إحصائيات في لوحة التحكم
Route::get('/dashboard', [DashBoardController::class,'calculateTotalValues']);
الدالة calculateTotalValues بدون كاش
public function calculateTotalValues() { $array = [ 'totalValue' => 0, 'totalSoldValue' => 0, 'totalCombinedValue' => 0, ]; \App\Models\Product::chunk(1000, function ($products) use (&$array) { foreach ($products as $product) { $array['totalValue'] += $product->price * $product->quantity; $array['totalSoldValue'] += $product->price * $product->sold_units; $array['totalCombinedValue'] += $product->price * ($product->quantity + $product->sold_units); } }); return view('dashboard', ['array' => $array]); }
الأن مع كاش
public function calculateTotalValues() { if (Cache::driver('redis')->has('totalValuesCached')) { return [ 'totalValue' => Cache::driver('redis')->get('totalValue'), 'totalSoldValue' => Cache::driver('redis')->get('totalSoldValue'), 'totalCombinedValue' => Cache::driver('redis')->get('totalCombinedValue'), ]; } $array = [ 'totalValue' => 0, 'totalSoldValue' => 0, 'totalCombinedValue' => 0, ]; \App\Models\Product::chunk(1000, function ($products) use (&$array) { foreach ($products as $product) { $array['totalValue'] += $product->price * $product->quantity; $array['totalSoldValue'] += $product->price * $product->sold_units; $array['totalCombinedValue'] += $product->price * ($product->quantity + $product->sold_units); } }); Cache::driver('redis')->put('totalValue', $array['totalValue']); Cache::driver('redis')->put('totalSoldValue', $array['totalSoldValue']); Cache::driver('redis')->put('totalCombinedValue', $array['totalCombinedValue']); Cache::driver('redis')->put('totalValuesCached', true); return view('dashboard', ['array' => $array]); }
وذلك لأننا قمنا بإستخدام الكاش بإستخدام Redis
ونحن لا نقوم بأي حسابات عند إعادة تحميل الصفحة. نحصل على البيانات من Redis ونعرضها. هذا أسرع بكثير من إجراء الحسابات في كل مرة.
حتى الآن، قمنا بتخزين البيانات مؤقتًا إلى الأبد. وعلى الرغم من أن هذا نهج صالح، إلا أنه لن تكون هناك تحديثات إلا إذا قمنا بمسح ذاكرة التخزين المؤقت يدويًا. لإصلاح ذلك، يمكننا إضافة انتهاء صلاحية إلى ذاكرة التخزين المؤقت الخاصة بنا:
Cache::driver('redis')->put('totalValue', $array['totalValue'], 600); Cache::driver('redis')->put('totalSoldValue', $array['totalSoldValue'], 600); Cache::driver('redis')->put('totalCombinedValue', $array['totalCombinedValue'], 600); Cache::driver('redis')->put('totalValuesCached', true, 599);
لقد أضفنا معلمة ثالثة إلى طريقة ->put(): عدد الثواني التي ستكون ذاكرة التخزين المؤقت صالحة لها. في حالتنا، قمنا بتعيينها على 10 دقائق (600 ثانية). هذا يعني أنه بعد 10 دقائق، ستنتهي صلاحية ذاكرة التخزين المؤقت، وسنحتاج إلى إعادة حساب القيم. يمكننا أيضًا رؤية ذلك في RedisInsight:
هذه طريقة رائعة لتخزين البيانات مؤقتًا التي يمكن تحديثها بشكل دوري.
على سبيل المثال، إذا كانت لوحة التحكم الخاصة بنا تعرض القيمة الإجمالية للمنتجات المباعة بتأخير 24 ساعة - يمكننا تخزين النتيجة مؤقتًا لمدة 24 ساعة ثم استخدام وظيفة مجدولة لإعادة حساب القيم كل 24 ساعة. هذا سيسرع لوحة التحكم الخاصة بنا للمستخدم النهائي.
المثال الرابع :
تخزين نماذج Eloquent مؤقتًا
في أمثلتنا السابقة، قمنا بتخزين النتائج مؤقتًا. ولكن هناك طريقة أخرى لاستخدام Cache هنا. يمكننا تخزين النماذج نفسها مؤقتًا:
use Illuminate\Support\Facades\Cache; use App\Models\Product; class ProductController extends Controller { public function index() { $products = Cache::driver('redis')->remember('products_query', 600, function () { return Product::query()->limit(10_000)->get(); }); return view('products.index', compact('products')); }
ملاحظة: لقد أخذنا 10,000 منتج فقط حيث أن محاولة تخزين 300,000 منتج مؤقتًا ستستهلك الكثير من الذاكرة وعادة ما تتسبب في خطأ Allowed memory size of XXXXX bytes exhausted (tried to allocate XXXX bytes). كن حذرًا مع هذا.
الآن، عند تحميل صفحتنا، يمكننا أن نرى أننا قمنا بالاستعلام عن قاعدة البيانات وتحميل 10,000 نموذج:
ولكن بعد إعادة تحميل الصفحة، يمكننا أن نرى أننا لم نستعلم عن قاعدة البيانات وبدلاً من ذلك قمنا بتحميل النماذج من Redis:
وإذا نظرنا إلى RedisInsight، يمكننا أن نرى أننا قمنا بتخزين النماذج مؤقتًا:
بالطبع، هذا تبسيط جذري، ولكن يمكننا استخدام Redis لتخزين أنواع مختلفة من البيانات مؤقتًا. وبينما استخدمنا للتو نوع string بسيط لذاكرة التخزين المؤقت الخاصة بنا، يدعم Redis العديد من الأنواع الأخرى، لذلك دعنا نلقي نظرة عليها في القسم التالي.
إستخدام Redis لمراقب زيارات منتج معين
use Illuminate\Support\Facades\Redis; // في Controller لعرض صفحة المنتج: public function showProduct($id) { $product = \App\Models\Product::find($id); // جلب المنتج من قاعدة البيانات if (!$product) { abort(404); } // زيادة عداد الزيارات للمنتج في Redis // مفتاح Redis سيكون "product:views:1" للمنتج رقم 1 Redis::incr('product:views:' . $id); // الحصول على عدد الزيارات الحالي (للعرض) $viewsCount = Redis::get('product:views:' . $id); return view('product.show', compact('product', 'viewsCount'));}
بينما نظرنا للتو إلى التخزين المؤقت البسيط، فإن Redis يستخدم لأكثر من ذلك. في Laravel، غالبًا ما تجد Redis مسؤولًا عن قائمة الانتظار الخاصة بك. إليك كيف يبدو ذلك:
.env QUEUE_CONNECTION=redis
الآن، عندما نقوم بدفع مهمة إلى قائمة الانتظار، سيتم تخزينها في Redis:
dispatch(new ProcessProductJob($product));
ويمكننا رؤيتها في RedisInsight:
هذا استخدم نوع list لتخزين البيانات. ويمكننا أن نرى أن البيانات مخزنة بتنسيق JSON:
الآن، يمكن لعاملين قوائم الانتظار في Laravel تحميل ومعالجة المهمة من Redis بأسرع طريقة ممكنة.
بدلاً من إرسال البريد الإلكتروني مباشرةً في طلب المستخدم، يمكننا إرساله إلى طابور ليتم معالجته لاحقاً.
أولاً، قم بإنشاء مهمة (Job)
php artisan make:job SendWelcomeEmail
ثم قم بتعديل ملف app/Jobs/SendWelcomeEmail.php:
<?php namespace App\Jobs; use App\Models\User; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use App\Mail\WelcomeMail; // نفترض وجود هذا الـ Mailable use Illuminate\Support\Facades\Mail; class SendWelcomeEmail implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $user; public function __construct(User $user) { $this->user = $user; } public function handle() { Mail::to($this->user->email)->send(new WelcomeMail($this->user)); }}
الآن، يمكنك إرسال هذه المهمة إلى الطابور في أي مكان في تطبيقك (على سبيل المثال، بعد تسجيل المستخدم)
use App\Jobs\SendWelcomeEmail; use App\Models\User; use Illuminate\Http\Request;
// في Controller أو خدمة تسجيل المستخدم:
public function registerUser(Request $request) { $user = User::create($request->all());
إرسال مهمة إرسال البريد الإلكتروني إلى الطابور
SendWelcomeEmail::dispatch($user); return redirect()->route('dashboard')->with('success', 'تم التسجيل بنجاح! سيصلك بريد ترحيبي قريباً.');
لكي تتم معالجة المهام في الطابور، تحتاج إلى تشغيل عامل الطابور
php artisan queue:work
Invalidationإبطال الكاش (Cache Invalidation) هو الجزء الأكثر تحدياً. تأكد من أنك تقوم بمسح الكاش بشكل صحيح عندما تتغير البيانات الأساسية.
علاقات Eloquent: عند تخزين نموذج يحتوي على علاقات (مثل User::with('posts'))، يتم تخزين العلاقة كجزء من النموذج. إذا تغيرت المنشورات المرتبطة، لن ينعكس ذلك في الكاش حتى يتم مسحه أو انتهاء صلاحيته.
الذاكرة: كن حذراً عند تخزين كميات كبيرة جداً من البيانات في الكاش، فقد يؤدي ذلك إلى استهلاك ذاكرة Redis بشكل كبير.
التعقيد: لبعض السيناريوهات المعقدة، قد تحتاج إلى استخدام أحداث Eloquent (Eloquent Events) لمسح الكاش تلقائياً عند حفظ أو تحديث أو حذف النماذج.
تعتبر اصطلاحات التسمية مهمة. فهي تسمح لنا بالحفاظ على رمزنا نظيفًا وسهل الفهم. ولا يختلف Redis عن ذلك. لديه بعض قواعد التسمية التي يجب أن نتبعها:
استخدم النقطتين للفصل بين الكلمات: على سبيل المثال، يمكننا استخدام products:total بدلاً من productstotal.
استخدم الأحرف الصغيرة: على سبيل المثال، يمكننا استخدام products:total بدلاً من PRODUCTS:TOTAL.
استخدم الشرطة السفلية للكلمات المتعددة: على سبيل المثال، يمكننا استخدام products:total_value بدلاً من products:total-value.
على الرغم من أن هذا ليس مطلبًا، إلا أننا أردنا ذكره لأنه ممارسة جيدة يجب اتباعها. سيجعل أيضًا استعلام البيانات أسهل وتحديد معايير لفريقك.
لقد نظرنا إلى استخدام Redis البسيط مع السلاسل وقوائم الانتظار، ولكن Redis يدعم مجموعة متنوعة من أنواع البيانات. دعنا نلقي نظرة عليها:
السلاسل (Strings)
السلاسل هي أبسط أنواع البيانات فهمًا. إنها مجرد زوج من المفتاح والقيمة. على سبيل المثال، يمكننا تخزين سلسلة بهذا الشكل:
Cache::driver('redis')->put('KEY', 'VALUE');
سيخزن هذا VALUE في KEY على Redis، ويمكننا استعادته بهذا الشكل:
Cache::driver('redis')->get('KEY');
هذا كل شيء. يمكنك وضع أي سلسلة (نعم، حتى JSON، ولكن لذلك، يجب أن تبحث في القوائم/المجموعات) في Redis بهذا الشكل. فقط تذكر أن لديها حدًا يبلغ 512 ميجابايت (وهو كثير!).
القوائم (Lists)
بعد ذلك، لدينا القوائم التي تشبه المصفوفات. يمكننا تخزين قيم متعددة في قائمة ثم استعادتها. على سبيل المثال، يمكننا تخزين قائمة بهذا الشكل:
مهم: بينما يحتوي Laravel على مساعد Cache، فإننا نستخدم واجهة Redis هنا لأنها أكثر قوة وتسمح لنا باستخدامها مباشرة. هذا سيمكننا من استخدام جميع أوامر Redis (المزيد عن ذلك لاحقًا).
$products = Product::query()->limit(100)->get(); foreach ($products as $product) { Redis::command('LPUSH', ['products', $product]); }
سيخزن هذا منتجاتنا في قائمة تسمى products:
ويمكننا استعادتها بهذا الشكل:
PHP // Redis::command('LRANGE', ['products', FROM INDEX, TO INDEX]); $products = Redis::command('LRANGE', ['products', 0, 10]);
سيعيد هذا أول 10 منتجات من قائمتنا:
هنا دفعنا سجلات جديدة إلى أعلى القائمة وليس إلى الأسفل. هذا لأننا استخدمنا الأمر LPUSH، الذي يدفع السجلات الجديدة إلى أعلى القائمة. إذا أردنا دفع سجلات جديدة إلى أسفل القائمة، يمكننا استخدام الأمر RPUSH:
$products = Product::query()->limit(100)->get(); foreach ($products as $product) { Redis::command('RPUSH', ['products', $product]); }
هذا سيدفع سجلات جديدة إلى أسفل القائمة
ويمكننا استعادتها بهذا الشكل:
$products = Redis::command('LRANGE', ['products', 0, 10]);
سيعيد هذا أول 10 منتجات من قائمتنا:
استرداد أول 10 منتجات وإزالتها من القائمة:
$products = Redis::command('LPOP', ['products', 10]);
سيعيد هذا أول 10 منتجات من قائمتنا ويزيلها من القائمة (لذلك لا يمكننا استردادها مرة أخرى):
يمكن أن يكون هذا مفيدًا إذا كنت ترغب في معالجة البيانات على دفعات. على سبيل المثال، إذا كان لديك قائمة انتظار تضم 1000 منتج تحتاج إلى معالجتها، يمكنك استرداد ومعالجة 100 منتج في كل مرة. هذا سيسمح لك بمعالجة البيانات على دفعات وليس كلها مرة واحدة مع الحفاظ على ترتيب البيانات.
المجموعات (Sets)
تشبه القوائم ولكن لها فرق كبير. لا تسمح بالتكرارات وليس لها فهرس. لتخزين مجموعة، يمكننا استخدام الأمر SADD:
$products = Product::query()->limit(100)->get(); foreach ($products as $product) { Redis::command('SADD', ['products', $product->id]); }
سيخزن هذا منتجاتنا في مجموعة تسمى products:
ويمكننا استعادتها بهذا الشكل:
$products = Redis::command('SMEMBERS', ['products']);
سيعيد هذا جميع المنتجات من مجموعتنا:
للتحقق مما إذا كانت القيمة موجودة في مجموعة، يمكننا استخدام الأمر SISMEMBER:
Redis::command('SISMEMBER', ['products', 1]);
سيعيد هذا 1 إذا كانت القيمة موجودة في المجموعة و 0 إذا لم تكن كذلك.
تُعد المجموعات طريقة ممتازة لتخزين البيانات الفريدة التي لا تحتاج إلى أن تكون مرتبة أو لها فهارس. على سبيل المثال، إذا كنت ترغب في الاحتفاظ بقائمة بالمستخدمين الذين أعجبوا بمنشور، يمكنك استخدام مجموعة لتخزين معرفاتهم. سيسمح لك هذا بالتحقق مما إذا كان المستخدم قد أعجب بمنشور ما. وإذا كنت ترغب في مزامنة البيانات مع قاعدة البيانات الخاصة بك، يمكنك استخدام الأمر SMEMBERS لاسترداد جميع المعرفات ثم إرفاقها بمنشورك. مثال آخر لاستخدام المجموعة هو تتبع المستخدمين الفريدين. إذا كنت ترغب في تتبع المستخدمين الفريدين الذين زاروا موقع الويب الخاص بك، يمكنك استخدام مجموعة لتخزين معرفاتهم. سيسمح لك هذا بتتبع المستخدمين الفريدين دون تخزين بياناتهم في قاعدة البيانات الخاصة بك.
المجموعات المرتبة (Sorted Sets)
بعد ذلك، لدينا المجموعات المرتبة. إنها تشبه المجموعات، ولكن لها درجة (score). هذا يسمح لنا بفرز البيانات حسب الدرجة. لتخزين مجموعة مرتبة، يمكننا استخدام الأمر ZADD:
$products = Product::query()->limit(100)->get();
foreach ($products as $product) { // التنسيق هو ZADD KEY SCORE VALUE Redis::command('ZADD', ['products', $product->price, $product->id]); }
سيخزن هذا منتجاتنا في مجموعة مرتبة تسمى products:
ويمكننا استعادتها بهذا الشكل:
$products = Redis::command('ZRANGE', ['products', 0, 10]);
سيعيد هذا أول 10 منتجات من مجموعتنا المرتبة:
$products = Redis::command('ZREVRANGE', ['products', 0, 10]);
سيعيد هذا آخر 10 منتجات من مجموعتنا المرتبة.
بينما كان هذا مثالًا بسيطًا، تُعد المجموعات المرتبة رائعة لتخزين البيانات التي تحتاج إلى فرز. بعض الأمثلة على ذلك هي:
وغيرها الكثير...
الآن بعد أن عرفنا كيفية استخدام Redis وأنواع البيانات التي يدعمها، دعنا نلقي نظرة على بعض الأوامر الشائعة الاستخدام:
COPY
ينسخ الأمر COPY قيمة مفتاح إلى مفتاح آخر. يعمل هذا الأمر مثل copy/paste. في حالتنا، لدينا visitors مخزنة في مجموعة، والتي نريد نسخها كـ "لقطة" للمعالجة:
للقيام بذلك، يمكننا استخدام الأمر COPY:
يجب أن نراقب اسم المفتاح الذي يعينه Laravel لذاكرة التخزين المؤقت لدينا. في هذه الحالة، يلزم `laravel_database_`
Redis::command('COPY', ['laravel_database_visitors', 'visitors_snapshot']);
بمجرد تشغيل هذا الأمر، يمكننا أن نرى أن لدينا مفتاحًا جديدًا يسمى visitors_snapshot:
PERSIST
يزيل الأمر PERSIST انتهاء الصلاحية من مفتاح. يعمل هذا الأمر مثل remove expiration. في حالتنا، لدينا visitors مخزنة في مجموعة، والتي نريد إزالة انتهاء الصلاحية منها:
(صورة تعرض مفتاح Redis مع انتهاء الصلاحية قبل استخدام PERSIST)
للقيام بذلك، يمكننا استخدام الأمر PERSIST:
Redis::command('PERSIST', ['laravel_database_visitors']);
بمجرد تشغيل هذا الأمر، يمكننا أن نرى أنه تم إزالة انتهاء الصلاحية:
هذا مفيد إذا قمت عن طريق الخطأ بتعيين انتهاء صلاحية لمفتاح كان من الصعب حسابه وترغب في الاحتفاظ به إلى الأبد. تحدث الأخطاء، ويمكن أن يساعدك هذا الأمر في إصلاحها.
SORT
يفرز الأمر SORT العناصر في قائمة أو مجموعة أو مجموعة مرتبة. يعمل هذا الأمر مثل sort. في حالتنا، لدينا visitors مخزنة في مجموعة، والتي نريد فرزها:
للقيام بذلك، يمكننا استخدام الأمر SORT:
Redis::command('SORT', ['visitors', ['sort' => 'desc', 'by' => 'ALPHA']]);
بمجرد تشغيل هذا الأمر، يمكننا أن نرى أنه تم فرز المجموعة:
يعيد هذا الأمر العناصر التي تم فرزها ولكنه لا يغير المجموعة الأصلية. إنه مفيد لفرز قائمة وإعادة العناصر التي تم فرزها.
TTL
يعيد الأمر TTL الوقت المتبقي لانتهاء صلاحية مفتاح. يعمل هذا الأمر مثل get expiration. في حالتنا، لدينا visitors مخزنة في مجموعة، والتي نريد التحقق من انتهاء صلاحيتها:
للقيام بذلك، يمكننا استخدام الأمر TTL:
Redis::command('TTL', ['visitors']);
بمجرد تشغيل هذا الأمر، يمكننا أن نرى أن انتهاء الصلاحية مضبوط على 600 ثانية:
هذا مفيد للتحقق من المدة التي سيظل فيها المفتاح صالحًا. على سبيل المثال، إذا كنت تريد تخزين قيمة مؤقتًا لمدة 10 دقائق، يمكنك استخدام الأمر TTL للتحقق من المدة التي ستظل فيها القيمة مخزنة مؤقتًا.
EXISTS
في حال كنت ترغب في التحقق مما إذا كان مفتاح موجودًا، يمكنك استخدام الأمر EXISTS:
Redis::command('EXISTS', ['visitors']);
هذا يعيد قيمة بسيطة true/false (أو 1/0). هذا مفيد للتأكد من وجود مفتاح قبل محاولة استعادته أو القيام بأي شيء به (خاصة إذا كنت تستخدم Redis::command()).
INCR
الأمر التالي هو INCR، الذي يزيد قيمة مفتاح بمقدار 1. يعمل هذا الأمر مثل ++. في حالتنا، يمكننا تتبع عدد المشاهدات لمشاركاتنا:
Redis::command('INCR', ['views:post:1']);
سيؤدي هذا إلى زيادة قيمة views:post:1 بمقدار 1، ويمكننا رؤية ذلك في RedisInsight:
(صورة تعرض قيمة مفتاح في RedisInsight بعد استخدام INCR)
يمكنك استخدام هذا الأمر لتتبع عدد المشاهدات لمشاركاتك أو منتجاتك أو أي بيانات أخرى ترغب في تتبعها.
ملاحظة: من الآمن الافتراض أنه إذا كان لدينا INCR، فلدينا أيضًا DECR، الذي يقلل قيمة مفتاح بمقدار 1. يعمل هذا الأمر مثل --.
FLUSHALL
يزيل الأمر FLUSHALL جميع المفاتيح من جميع قواعد البيانات. يعمل هذا الأمر مثل delete all. في حالتنا، يمكننا مسح جميع المفاتيح من Redis:
Redis::command('FLUSHALL');
بمجرد تشغيل هذا، ستكون مساحة تخزين Redis لدينا فارغة. هذا مفيد إذا كنت ترغب في مسح جميع مساحة تخزين Redis (على سبيل المثال، لأغراض الاختبار محليًا). لا نوصي باستخدام هذا في الإنتاج لأنه سيزيل جميع مفاتيح قواعد البيانات، مما قد يسبب مشكلات!
DEL
يزيل الأمر DEL مفتاحًا واحدًا أو أكثر من Redis. يعمل هذا الأمر مثل delete. في حالتنا، يمكننا مسح مفتاح معين من Redis:
Redis::command('DEL', ['visitors']);
سيؤدي هذا إلى حذف مفتاح visitors الخاص بنا من Redis. هذا مفيد إذا كنت ترغب في مسح مفتاح معين من Redis لتحرير بعض المساحة أو للتأكد من أن المفتاح لم يعد موجودًا.
PING
يتحقق الأمر PING مما إذا كان Redis قيد التشغيل. يعمل هذا الأمر مثل ping. في حالتنا، يمكننا التحقق مما إذا كان Redis قيد التشغيل:
Redis::command('PING');
بمجرد تشغيل هذا الأمر، يجب أن نحصل على استجابة ممتعة:
PONG
هذا مفيد للتحقق مما إذا كان Redis قيد التشغيل قبل محاولة القيام بأي شيء به.
بينما يسمح لك Redis بتشغيل أوامر متعددة بشكل فردي، إلا أنه سيخلق اختناقات. قد لا يكون ذلك ملحوظًا في البداية، ولكن مع نمو تطبيقك، ستلاحظ أن Redis يبطئ تطبيقك. هنا يأتي دور الأنابيب:
الأنابيب هي طريقة لتجميع أوامر متعددة في طلب واحد. هذا يعني أنه بدلاً من إرسال عشرة أو عشرين أو حتى مائة طلب إلى Redis - يمكنك إرسال طلب واحد يتضمن جميع الأوامر. هذا سيسرع العملية لأنه لن يضطر إلى انتظار انتهاء كل أمر ولن يضطر إلى الانتظار حتى تعيد الاتصال بـ Redis مرة أخرى. إليك كيف يبدو ذلك:
ملاحظة: سنستخدم Laravel Benchmark لنوضح لك الفرق بين الأنابيب والأوامر العادية.
تشغيل المعيار باستخدام الأوامر العادية:
use Illuminate\Support\Benchmark; Benchmark::dd(function () { for ($i = 0; $i < 100; $i++) { Redis::command('set', ['key:' . $i, $i]); } }, 10);
هذا يخبرنا أن الأمر استغرق 5.169 مللي ثانية لتشغيل 100 أمر. بزيادة عدد الأوامر إلى 1000، استغرق الأمر 23.363 مللي ثانية. الآن، دعنا نحاول تشغيل نفس المعيار باستخدام الأنابيب:
use Illuminate\Support\Benchmark; Benchmark::dd(function () { Redis::pipeline(function ($pipe) { for ($i = 0; $i < 100; $i++) { $pipe->set('key:' . $i, $i); } }); }, 10);
هذه المرة، استغرق الأمر 0.735 مللي ثانية لتشغيل 100 أمر و 4.021 مللي ثانية لتشغيل 1000 أمر. هذا فرق هائل! وعلى الرغم من أنه قد لا يكون ملحوظًا في البداية - إلا أنه سيكون واضحًا عندما تبدأ في توسيع نطاق تطبيقك. نوصي باستخدام الأنابيب بمجرد قيامك بإجراءات متعددة في Redis.
عند التعامل مع Redis، يجب أن نتذكر أنه يمكن أن يفشل أيضًا. تمامًا مثلما يمكن أن تفشل قاعدة بيانات MySQL الخاصة بنا. وعندما يحدث ذلك - نحتاج إلى أن نكون مستعدين. لا نريد أن يتوقف Redis عن العمل بينما نعالج ملف بيانات، أليس كذلك؟ هنا يأتي دور المعاملات. إنها تشبه جدًا DB::transaction() في Laravel:
Redis::transaction(function ($pipe) { $pipe->set('key', 'value'); $pipe->set('key2', 'value2'); });
عندما يتلقى Redis هذا الأمر، فإنه يقوم بتشغيل تنفيذ ذري (atomic execution). هذا يعني أنه سينفذ جميع الأوامر أو لا شيء منها، تمامًا كما يفعل DB::transaction(). هذا رائع إذا كنت تكتب الكثير من البيانات أو ترسل أوامر يمكن أن تفشل، حيث ستمنع تلف البيانات.
بما أننا نعلم أن Redis هي قاعدة بيانات داخل الذاكرة، فيجب أن نعرف أن أي إعادة تشغيل للخادم أو تعطل للخدمة سيؤدي إلى فقدان جميع البيانات. لهذا السبب نحتاج إلى حمايتها! الآن، هناك بضع طرق للقيام بذلك:
تحذير: كن حذرًا بشأن الذاكرة في Redis
بما أن Redis يخزن البيانات في الذاكرة، يجب أن تكون حذرًا بشأن مقدار البيانات التي تخزنها. إذا احتفظت بالكثير من البيانات، فإن Redis سيقبل البيانات فقط (ويرد بأخطاء إذا حاولت تخزين المزيد من البيانات). لا يزال بإمكانك الوصول إلى البيانات المخزنة عبر أوامر القراءة، ولكن إضافة أمر جديد - لن يكون ممكنًا.