خط أنابيب 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 أضعاف!

خصائص خط الأنابيب

⚠️ ملاحظة: خط الأنابيب ليس معاملة ولا يضمن الذرية. استخدم 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 أمر لكل دفعة. كبير جدًا يستخدم الكثير من الذاكرة؛ صغير جدًا يعطي تحسينًا ضئيلًا.
س ماذا يحدث إذا فشل أمر في خط الأنابيب؟
ج فشل أمر واحد لا يؤثر على الأوامر الأخرى. تحقق من النتائج المرجعة بحثًا عن أخطاء.
س متى يجب استخدام خط الأنابيب؟
ج عمليات دفعات، استيراد/تصدير بيانات، وأي سيناريو يحتاج تحسين الأداء.

📖 ملخص

📝 تمارين

  1. خط أنابيب أساسي: استخدم خط الأنابيب لتعيين 100 مفتاح دفعة واحدة، قارن الأداء مع النهج العادي
  2. عمليات دفعات: نفذ الحصول على دفعات وحذف دفعات باستخدام خط الأنابيب
  3. مقارنة أداء: اختبر الأداء بأحجام دفعات مختلفة (10، 100، 1000)
  4. خط أنابيب + معاملة: نفذ عمليات دفعات ذرية باستخدام خط الأنابيب مع المعاملات

الدرس التالي

في الدرس التالي، سنتعلم Python مع Redis، والذي يغطي عمليات Python مع Redis.

100%