خط أنابيب Redis
خط الأنابيب يسمح بإرسال الأوامر دفعات، مما يقلل من جولات الشبكة ويحسن الأداء بشكل كبير.
ما هو خط الأنابيب؟
النهج التقليدي
كل أمر يتطلب جولة شبكة واحدة:
العميل خادم Redis
| |
|----- SET key1 -------->|
|<----- OK --------------|
| |
|----- SET key2 -------->|
|<----- OK --------------|
| |
|----- SET key3 -------->|
|<----- OK --------------|
3 أوامر = 6 عمليات نقل شبكة (3 طلبات + 3 استجابات)
نهج خط الأنابيب
أوامر متعددة ترسل مرة واحدة:
العميل خادم Redis
| |
|----- SET key1 -------->|
|----- SET key2 -------->|
|----- SET key3 -------->|
| |
|<----- OK --------------|
|<----- OK --------------|
|<----- OK --------------|
3 أوامر = 2 عمليتي نقل شبكة (1 طلب دفعة + 1 استجابة دفعة)
💡 تحسين الأداء: خط الأنابيب يقلل من جولات الشبكة، مما يحسن الأداء بشكل كبير — خاصة عندما يكون زمن انتقال الشبكة مرتفعًا.
كيف يعمل خط الأنابيب؟
تأثير زمن انتقال الشبكة
بافتراض جولة شبكة 1ms:
النهج التقليدي:
- 100 أمر = 100 جولة = 100ms زمن انتقال شبكة
- بالإضافة إلى وقت معالجة Redis (افترض 0.1ms/أمر) = 10ms
- الوقت الإجمالي = 100ms + 10ms = 110ms
خط الأنابيب:
- 100 أمر = 1 جولة = 1ms زمن انتقال شبكة
- بالإضافة إلى وقت معالجة Redis = 10ms
- الوقت الإجمالي = 1ms + 10ms = 11ms
تحسين الأداء: 110ms → 11ms، زيادة 10 أضعاف!
خصائص خط الأنابيب
- إرسال دفعات: أوامر متعددة ترسل مرة واحدة
- تنفيذ متسلسل: Redis ينفذ الأوامر بالترتيب
- استجابة دفعة: جميع النتائج تُرجع مرة واحدة
- غير ذري: ليس معاملة — يمكن مقاطعة الأوامر من قبل عملاء آخرين
⚠️ ملاحظة: خط الأنابيب ليس معاملة ولا يضمن الذرية. استخدم MULTI/EXEC للذرية.
استخدام خط الأنابيب
وضع خط أنابيب redis-cli
BASH
# استخدام وضع خط أنابيب redis-cli
echo -e "SET key1 value1\nSET key2 value2\nSET key3 value3" | redis-cli
# أو القراءة من ملف
cat commands.txt | redis-cli
خط أنابيب Python
PYTHON
import redis
r = redis.Redis(host='localhost', port=6379)
# إنشاء خط أنابيب
pipe = r.pipeline()
# إضافة أوامر إلى خط الأنابيب
pipe.set('key1', 'value1')
pipe.set('key2', 'value2')
pipe.set('key3', 'value3')
# تنفيذ خط الأنابيب
results = pipe.execute()
print(results) # [True, True, True]
خط أنابيب Java (Jedis)
JAVA
Jedis jedis = new Jedis("localhost", 6379);
Pipeline pipeline = jedis.pipelined();
pipeline.set("key1", "value1");
pipeline.set("key2", "value2");
pipeline.set("key3", "value3");
List<Object> results = pipeline.syncAndReturnAll();
خط أنابيب Node.js (ioredis)
JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();
const pipeline = redis.pipeline();
pipeline.set('key1', 'value1');
pipeline.set('key2', 'value2');
pipeline.set('key3', 'value3');
const results = await pipeline.exec();
خط الأنابيب مقابل المعاملات
خط الأنابيب + المعاملات
يمكن دمج خط الأنابيب مع المعاملات:
PYTHON
import redis
r = redis.Redis()
# إنشاء خط أنابيب مع تمكين المعاملة
pipe = r.pipeline(transaction=True)
pipe.set('key1', 'value1')
pipe.set('key2', 'value2')
pipe.incr('counter')
# تنفيذ المعاملة
results = pipe.execute()
خط الأنابيب مقابل المعاملة
| الجانب | خط الأنابيب | المعاملة |
|---|---|---|
| الذرية | ❌ لا | ✅ نعم |
| العزل | ❌ لا | ✅ نعم |
| تحسين الأداء | ✅ كبير | ✅ كبير |
| إرسال دفعات | ✅ نعم | ✅ نعم |
| حالة الاستخدام | عمليات دفعات، أداء | عمليات دفعات تحتاج ذرية |
💡 الاختيار: استخدم خط الأنابيب عندما لا تكون الذرية مطلوبة. استخدم المعاملات أو خط الأنابيب+المعاملة عندما تكون الذرية مطلوبة.
حالات استخدام خط الأنابيب
حالة الاستخدام 1: تعيين دفعات
PYTHON
import redis
r = redis.Redis()
pipe = r.pipeline()
# تعيين 1000 مفتاح دفعة واحدة
for i in range(1000):
pipe.set(f'key:{i}', f'value:{i}')
results = pipe.execute()
print(f'Set {len(results)} keys')
حالة الاستخدام 2: الحصول على دفعات
PYTHON
import redis
r = redis.Redis()
pipe = r.pipeline()
# الحصول على 1000 مفتاح دفعة واحدة
keys = [f'key:{i}' for i in range(1000)]
for key in keys:
pipe.get(key)
values = pipe.execute()
print(f'Got {len(values)} values')
حالة الاستخدام 3: حذف دفعات
PYTHON
import redis
r = redis.Redis()
pipe = r.pipeline()
# حذف دفعات
keys = ['key1', 'key2', 'key3', 'key4', 'key5']
for key in keys:
pipe.delete(key)
results = pipe.execute()
print(f'Deleted {sum(results)} keys')
حالة الاستخدام 4: استيراد بيانات
PYTHON
import redis
import json
r = redis.Redis()
pipe = r.pipeline()
# استيراد بيانات من ملف JSON
with open('data.json', 'r') as f:
data = json.load(f)
for item in data:
key = f"user:{item['id']}"
value = json.dumps(item)
pipe.set(key, value)
pipe.execute()
print('Import complete')
حالة الاستخدام 5: تحديث عدادات دفعات
PYTHON
import redis
r = redis.Redis()
pipe = r.pipeline()
# تحديث عدادات دفعات
counters = {
'article:1:views': 10,
'article:2:views': 20,
'article:3:views': 30,
}
for key, increment in counters.items():
pipe.incrby(key, increment)
pipe.execute()
print('Counters updated')
مقارنة أداء خط الأنابيب
كود الاختبار
PYTHON
import redis
import time
r = redis.Redis()
# بيانات الاختبار
n = 10000
# الطريقة 1: النهج العادي
start = time.time()
for i in range(n):
r.set(f'key:{i}', f'value:{i}')
end = time.time()
print(f'Regular: {end - start:.2f} seconds')
# الطريقة 2: خط الأنابيب
start = time.time()
pipe = r.pipeline()
for i in range(n):
pipe.set(f'key:{i}', f'value:{i}')
pipe.execute()
end = time.time()
print(f'Pipeline: {end - start:.2f} seconds')
نتائج نموذجية
Regular: 5.23 seconds
Pipeline: 0.51 seconds
تحسين الأداء: حوالي 10 أضعاف
أفضل ممارسات خط الأنابيب
1. تعيين حجم دفعة معقول
PYTHON
# ❌ إرسال عدد كبير جدًا من الأوامر مرة واحدة
pipe = r.pipeline()
for i in range(100000):
pipe.set(f'key:{i}', f'value:{i}')
pipe.execute() # قد يستخدم الكثير من الذاكرة
# ✅ التنفيذ على دفعات
batch_size = 1000
for batch in range(0, 100000, batch_size):
pipe = r.pipeline()
for i in range(batch, batch + batch_size):
pipe.set(f'key:{i}', f'value:{i}')
pipe.execute()
2. معالجة الأخطاء
PYTHON
import redis
r = redis.Redis()
pipe = r.pipeline()
pipe.set('key1', 'value1')
pipe.incr('key1') # سيفشل (key1 ليس رقمًا)
pipe.set('key2', 'value2')
try:
results = pipe.execute()
for i, result in enumerate(results):
if isinstance(result, Exception):
print(f'Command {i} failed: {result}')
except Exception as e:
print(f'Pipeline execution failed: {e}')
3. استخدام المعاملات للذرية
PYTHON
import redis
r = redis.Redis()
# استخدم المعاملة عند الحاجة للذرية
pipe = r.pipeline(transaction=True)
pipe.set('account:a', '100')
pipe.set('account:b', '50')
try:
results = pipe.execute()
print('Transaction executed successfully')
except Exception as e:
print(f'Transaction failed: {e}')
4. مراقبة أداء خط الأنابيب
PYTHON
import redis
import time
r = redis.Redis()
start = time.time()
pipe = r.pipeline()
for i in range(10000):
pipe.set(f'key:{i}', f'value:{i}')
results = pipe.execute()
end = time.time()
print(f'Execution time: {end - start:.2f} seconds')
print(f'QPS: {10000 / (end - start):.0f}')
قيود خط الأنابيب
1. استخدام الذاكرة
PYTHON
# خط الأنابيب يخزّن جميع الأوامر والنتائج مؤقتًا
# دفعة كبيرة جدًا تستخدم الكثير من الذاكرة
# الحل: تنفيذ على دفعات
2. غير ذري
PYTHON
# الأوامر في خط الأنابيب يمكن مقاطعتها من قبل عملاء آخرين
# استخدم المعاملات عندما تكون الذرية مطلوبة
3. لا يمكن استخدام النتائج الوسيطة
PYTHON
# لا يمكن استخدام نتيجة أمر سابق في خط الأنابيب
# ❌ مثال غير صحيح
pipe = r.pipeline()
pipe.incr('counter')
# لا يمكن الحصول على نتيجة incr للأوامر اللاحقة
pipe.set('result', ???) # لا يمكن استخدام نتيجة incr
pipe.execute()
# ✅ الحل: استخدم برامج Lua النصية
❓ أسئلة شائعة
س ما الفرق بين خط الأنابيب والمعاملة؟
ج خط الأنابيب يقلل جولات الشبكة لكنه لا يضمن الذرية. المعاملات تضمن الذرية وتقلل أيضًا جولات الشبكة.
س كم تحسين في الأداء يمكن أن يوفره خط الأنابيب؟
ج يعتمد على زمن انتقال الشبكة. زمن انتقال أعلى يعني تحسينًا أكبر. عادة 5-10 أضعاف.
س ما حجم الدفعة الذي يجب استخدامه؟
ج يُوصى بـ 100-1000 أمر لكل دفعة. كبير جدًا يستخدم الكثير من الذاكرة؛ صغير جدًا يعطي تحسينًا ضئيلًا.
س ماذا يحدث إذا فشل أمر في خط الأنابيب؟
ج فشل أمر واحد لا يؤثر على الأوامر الأخرى. تحقق من النتائج المرجعة بحثًا عن أخطاء.
س متى يجب استخدام خط الأنابيب؟
ج عمليات دفعات، استيراد/تصدير بيانات، وأي سيناريو يحتاج تحسين الأداء.
📖 ملخص
- خط الأنابيب يرسل الأوامر دفعات، مما يقلل من جولات الشبكة
- تحسين كبير في الأداء، خاصة مع زمن انتقال شبكة عالٍ
- خط الأنابيب لا يضمن الذرية — استخدم المعاملات عند الحاجة
- حالات الاستخدام: تعيين دفعات، الحصول على دفعات، استيراد بيانات
- أفضل الممارسات: حجم دفعة معقول، معالجة أخطاء، تنفيذ دفعات
- قيود خط الأنابيب: استخدام الذاكرة، غير ذري، لا يمكن استخدام النتائج الوسيطة
📝 تمارين
- خط أنابيب أساسي: استخدم خط الأنابيب لتعيين 100 مفتاح دفعة واحدة، قارن الأداء مع النهج العادي
- عمليات دفعات: نفذ الحصول على دفعات وحذف دفعات باستخدام خط الأنابيب
- مقارنة أداء: اختبر الأداء بأحجام دفعات مختلفة (10، 100، 1000)
- خط أنابيب + معاملة: نفذ عمليات دفعات ذرية باستخدام خط الأنابيب مع المعاملات
الدرس التالي
في الدرس التالي، سنتعلم Python مع Redis، والذي يغطي عمليات Python مع Redis.



