Redis والتخزين المؤقت المتقدم
المعاملات والتسلسل في Redis
المعاملات والتسلسل في Redis
يوفر Redis ميزتين قويتين لتنفيذ أوامر متعددة بكفاءة: المعاملات للعمليات الذرية والتسلسل للتنفيذ الدفعي. فهم هذه الميزات أمر حاسم لبناء تطبيقات عالية الأداء.
معاملات Redis (MULTI/EXEC)
تسمح معاملات Redis بتنفيذ مجموعة من الأوامر بشكل ذري - جميع الأوامر تنجح أو تفشل جميعها:
معاملة أساسية:
MULTI # بدء المعاملة SET user:1 "John" SET user:2 "Jane" INCR user:count EXEC # تنفيذ جميع الأوامر # يعيد مصفوفة من النتائج: 1) OK 2) OK 3) (integer) 3
الخصائص الرئيسية:
- جميع الأوامر في قائمة انتظار ويتم تنفيذها معاً
- تنفيذ ذري - لا يمكن لأي عميل آخر المقاطعة
- الكل أو لا شيء: إذا كان أمر واحد يحتوي على خطأ في بناء الجملة، لا يتم تنفيذ أي منها
- إذا فشل الأمر أثناء التشغيل (مثل نوع خاطئ)، لا تزال الأوامر الأخرى تنفذ
DISCARD - إلغاء المعاملة
إلغاء معاملة في قائمة الانتظار قبل التنفيذ:
مثال:
MULTI SET user:1 "John" SET user:2 "Jane" DISCARD # إلغاء المعاملة - لا يتم تنفيذ أي شيء # يعيد: OK
معاملات Laravel Redis
يوفر Laravel واجهة برمجة تطبيقات نظيفة لمعاملات Redis:
استخدام طريقة transaction():
use Illuminate\Support\Facades\Redis;
// معاملة بسيطة
Redis::transaction(function ($redis) {
$redis->set('user:1', 'John');
$redis->set('user:2', 'Jane');
$redis->incr('user:count');
});
// معاملة مع قيمة إرجاع
$results = Redis::transaction(function ($redis) {
$redis->set('balance:1', 1000);
$redis->decrby('balance:1', 100);
return $redis->get('balance:1');
});
// $results تحتوي على مصفوفة من نتائج الأوامرالتحكم اليدوي في المعاملات:
Redis::multi();
Redis::set('key1', 'value1');
Redis::set('key2', 'value2');
Redis::incr('counter');
$results = Redis::exec(); // التنفيذ وإرجاع النتائج
// أو الإلغاء
Redis::multi();
Redis::set('key1', 'value1');
Redis::discard(); // إلغاء المعاملةWATCH - القفل المتفائل
WATCH يراقب المفاتيح للتغييرات ويلغي المعاملة إذا تم تعديل أي مفتاح مراقب:
نمط القفل المتفائل:
// مثال Redis CLI WATCH balance:1 # مراقبة هذا المفتاح GET balance:1 # قراءة القيمة الحالية: 1000 # إذا عدّل عميل آخر balance:1 هنا، ستفشل المعاملة MULTI DECRBY balance:1 100 # خصم 100 EXEC # التنفيذ فقط إذا لم يتغير balance:1 # يعيد: (nil) إذا تم تعديل المفتاح المراقب # يعيد: [OK, 900] إذا نجح
تنفيذ Laravel - تحويل بنكي:
public function transfer(int $fromAccountId, int $toAccountId, float $amount)
{
$maxRetries = 5;
$attempt = 0;
while ($attempt < $maxRetries) {
try {
// مراقبة أرصدة الحسابين
Redis::watch("balance:{$fromAccountId}", "balance:{$toAccountId}");
// قراءة الأرصدة الحالية
$fromBalance = (float) Redis::get("balance:{$fromAccountId}");
$toBalance = (float) Redis::get("balance:{$toAccountId}");
// التحقق
if ($fromBalance < $amount) {
Redis::unwatch();
throw new \Exception('رصيد غير كافٍ');
}
// تنفيذ المعاملة
$results = Redis::transaction(function ($redis) use (
$fromAccountId, $toAccountId, $amount, $fromBalance, $toBalance
) {
$redis->set("balance:{$fromAccountId}", $fromBalance - $amount);
$redis->set("balance:{$toAccountId}", $toBalance + $amount);
$redis->lpush('transactions', json_encode([
'from' => $fromAccountId,
'to' => $toAccountId,
'amount' => $amount,
'timestamp' => time()
]));
});
if ($results !== null) {
return true; // نجح
}
// فشلت المعاملة بسبب تغيير المفتاح المراقب - إعادة المحاولة
$attempt++;
usleep(50000); // انتظر 50 ميلي ثانية قبل إعادة المحاولة
} catch (\Exception $e) {
Redis::unwatch();
throw $e;
}
}
throw new \Exception('فشلت المعاملة بعد الحد الأقصى من المحاولات');
}مهم: اتصل دائماً بـ UNWATCH أو نفذ/ألغِ المعاملة لتحرير المفاتيح المراقبة. الفشل في القيام بذلك يمكن أن يسبب تسرب في الذاكرة.
التسلسل - تنفيذ الأوامر الدفعية
التسلسل يرسل أوامر متعددة إلى Redis دون انتظار الردود الفردية، مما يقلل من رحلات الشبكة:
بدون تسلسل (بطيء):
// 1000 أمر = 1000 رحلة شبكة
for ($i = 0; $i < 1000; $i++) {
Redis::set("key:{$i}", "value:{$i}"); // انتظر الاستجابة في كل مرة
}
// الوقت: ~2 ثانية (مع 2 ميلي ثانية كمون لكل طلب)مع التسلسل (سريع):
// تسلسل Laravel
Redis::pipeline(function ($pipe) {
for ($i = 0; $i < 1000; $i++) {
$pipe->set("key:{$i}", "value:{$i}");
}
});
// الوقت: ~10 ميلي ثانية (رحلة شبكة واحدة)
// يعيد مصفوفة من جميع النتائجتعزيز الأداء: يمكن أن يحسن التسلسل الأداء بمقدار 10-100 مرة عند تنفيذ العديد من الأوامر. إنه فعال بشكل خاص للاتصالات عالية الكمون.
التسلسل مقابل المعاملات
الاختلافات الرئيسية:
المعاملات (MULTI/EXEC): ✓ تنفيذ ذري - الكل أو لا شيء ✓ تنفذ الأوامر في عزلة ✓ يضمن الاتساق ✗ أبطأ من التسلسل ✗ إنتاجية محدودة التسلسل: ✓ أقصى إنتاجية ✓ حد أدنى من النفقات العامة للشبكة ✓ يمكن أن يتضمن أي أوامر ✗ ليس ذرياً - يمكن تشابك الأوامر ✗ لا توجد ضمانات اتساق ✗ ممكنة الفشل الجزئي
الجمع بين التسلسل والمعاملات
استخدم كليهما للحصول على أقصى أداء مع الذرية:
معاملات متسلسلة:
// تنفيذ معاملات مستقلة متعددة في دفعة واحدة
Redis::pipeline(function ($pipe) {
// المعاملة 1: تحديث المستخدم 1
$pipe->multi();
$pipe->set('user:1:name', 'John');
$pipe->set('user:1:email', 'john@example.com');
$pipe->exec();
// المعاملة 2: تحديث المستخدم 2
$pipe->multi();
$pipe->set('user:2:name', 'Jane');
$pipe->set('user:2:email', 'jane@example.com');
$pipe->exec();
// المعاملة 3: تحديث العدادات
$pipe->multi();
$pipe->incr('users:count');
$pipe->incr('updates:count');
$pipe->exec();
});أساسيات البرمجة النصية Lua
توفر نصوص Lua عمليات ذرية حقيقية مع منطق معقد:
نص Lua بسيط:
// Redis CLI
EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 mykey myvalue
// Laravel - نص Lua لزيادة ذرية مع حد
$script = <<<LUA
local current = redis.call('GET', KEYS[1])
if not current then
current = 0
end
if tonumber(current) < tonumber(ARGV[1]) then
return redis.call('INCR', KEYS[1])
else
return -1
end
LUA;
$result = Redis::eval($script, 1, 'counter', 100);
// يعيد قيمة العداد الجديدة أو -1 إذا تم الوصول إلى الحدLaravel - محدد المعدل مع Lua:
public function checkRateLimit(string $key, int $limit, int $window): bool
{
$script = <<<LUA
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local current = redis.call('GET', key)
if not current then
redis.call('SETEX', key, window, 1)
return 1
elseif tonumber(current) < limit then
redis.call('INCR', key)
return tonumber(current) + 1
else
return -1
end
LUA;
$result = Redis::eval($script, 1, $key, $limit, $window);
return $result !== -1;
}مزايا Lua:
- تنفيذ ذري مضمون
- منطق معقد على خادم Redis (تقليل حركة مرور الشبكة)
- أداء أفضل من القفل المتفائل المبني على WATCH
- يتم تخزين النصوص مؤقتاً بواسطة Redis (استخدم SCRIPT LOAD للتنفيذ المتكرر)
أفضل الممارسات
متى تستخدم كل نهج:
1. استخدم المعاملات (MULTI/EXEC): - عمليات ذرية بسيطة - 2-10 أوامر ذات صلة - دلالات نجاح/فشل واضحة 2. استخدم WATCH + المعاملات: - التحديثات الشرطية بناءً على القيم الحالية - سيناريوهات القفل المتفائل - مفاتيح تنافسية منخفضة 3. استخدم التسلسل: - عمليات جماعية (100+ أمر) - عمليات للقراءة فقط - اتصالات عالية الكمون - عندما لا يكون الذرية مطلوباً 4. استخدم نصوص Lua: - منطق شرطي معقد - سيناريوهات تنافسية عالية - تحتاج إلى ذرية مضمونة مع منطق - عمليات متكررة (تخزين النص مؤقتاً)
تمرين عملي:
- تنفيذ سداد عربة التسوق باستخدام WATCH والمعاملات (فحص المخزون، خصم المخزون، إنشاء طلب)
- إنشاء وظيفة استيراد جماعي باستخدام التسلسل لإدراج 10,000 سجل
- بناء نص Lua لـ "pop and push" الذري بين قائمتين مع منطق شرطي
- تنفيذ عداد موزع مع Lua يعيد الضبط تلقائياً بعد الوصول إلى عتبة
- مقارنة الأداء: تشغيل 1000 أمر SET مع وبدون التسلسل، قياس فرق الوقت