Node.js مع Redis

Node.js و Redis متناسبان طبيعيًا. يغطي هذا الدرس استخدام Redis مع Node.js.

عملاء Node.js لـ Redis

عملاء Node.js الرئيسيون لـ Redis:

💡 توصية: المشاريع الجديدة يجب أن تستخدم ioredis أو node-redis v4+.

ioredis

التثبيت

BASH
npm install ioredis

الاتصال الأساسي

JAVASCRIPT
const Redis = require('ioredis');

// الاتصال بـ Redis
const redis = new Redis({
  host: 'localhost',
  port: 6379,
  // password: 'your_password',
  db: 0
});

// اختبار الاتصال
redis.ping().then(result => {
  console.log(result); // 'PONG'
});

عمليات السلاسل

JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();

// SET و GET
async function stringDemo() {
  // SET
  await redis.set('name', 'Alice');

  // GET
  const name = await redis.get('name');
  console.log(name); // 'Alice'

  // SET مع انتهاء صلاحية
  await redis.set('session', 'data', 'EX', 3600); // تنتهي صلاحيته بعد ساعة

  // MSET و MGET
  await redis.mset('key1', 'value1', 'key2', 'value2');
  const values = await redis.mget('key1', 'key2');
  console.log(values); // ['value1', 'value2']

  // INCR
  await redis.set('counter', 0);
  const count = await redis.incr('counter');
  console.log(count); // 1

  // DEL
  await redis.del('name');
}

stringDemo();

عمليات التجزئة

JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();

async function hashDemo() {
  // HSET و HGET
  await redis.hset('user:1', 'name', 'Alice');
  await redis.hset('user:1', 'age', 25);
  const name = await redis.hget('user:1', 'name');
  console.log(name); // 'Alice'

  // HMSET
  await redis.hmset('user:2', {
    name: 'Bob',
    age: 30,
    city: 'Beijing'
  });

  // HGETALL
  const user = await redis.hgetall('user:2');
  console.log(user); // { name: 'Bob', age: '30', city: 'Beijing' }

  // HKEYS و HVALS
  const keys = await redis.hkeys('user:2');
  const values = await redis.hvals('user:2');

  // HDEL
  await redis.hdel('user:2', 'city');

  // HINCRBY
  await redis.hincrby('user:2', 'age', 1);
}

hashDemo();

عمليات القوائم

JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();

async function listDemo() {
  // LPUSH و RPUSH
  await redis.lpush('mylist', 'value1', 'value2');
  await redis.rpush('mylist', 'value3');

  // LRANGE
  const list = await redis.lrange('mylist', 0, -1);
  console.log(list); // ['value2', 'value1', 'value3']

  // LPOP و RPOP
  const left = await redis.lpop('mylist');
  const right = await redis.rpop('mylist');

  // LLEN
  const length = await redis.llen('mylist');
}

listDemo();

عمليات المجموعات

JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();

async function setDemo() {
  // SADD
  await redis.sadd('myset', 'a', 'b', 'c');

  // SMEMBERS
  const members = await redis.smembers('myset');
  console.log(members); // Set { 'a', 'b', 'c' }

  // SISMEMBER
  const exists = await redis.sismember('myset', 'a');
  console.log(exists); // 1 (true)

  // SREM
  await redis.srem('myset', 'a');

  // SINTER, SUNION, SDIFF
  await redis.sadd('set1', 'a', 'b', 'c');
  await redis.sadd('set2', 'b', 'c', 'd');
  const inter = await redis.sinter('set1', 'set2');
  console.log(inter); // Set { 'b', 'c' }
}

setDemo();

عمليات المجموعات المرتبة

JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();

async function zsetDemo() {
  // ZADD
  await redis.zadd('leaderboard', 100, 'player1', 200, 'player2', 150, 'player3');

  // ZRANGE
  const top = await redis.zrange('leaderboard', 0, -1, 'WITHSCORES');
  console.log(top); // ['player1', '100', 'player3', '150', 'player2', '200']

  // ZREVRANGE (من الأعلى إلى الأقل)
  const topDesc = await redis.zrevrange('leaderboard', 0, 2, 'WITHSCORES');

  // ZSCORE
  const score = await redis.zscore('leaderboard', 'player1');
  console.log(score); // '100'

  // ZINCRBY
  await redis.zincrby('leaderboard', 50, 'player1');

  // ZREM
  await redis.zrem('leaderboard', 'player1');
}

zsetDemo();

النشر/الاشتراك

JAVASCRIPT
const Redis = require('ioredis');

// المشترك
const subscriber = new Redis();
subscriber.subscribe('news', 'sports');

subscriber.on('message', (channel, message) => {
  console.log(`Channel: ${channel}, Message: ${message}`);
});

// الناشر
const publisher = new Redis();
setInterval(() => {
  publisher.publish('news', `News message ${Date.now()}`);
}, 1000);

خط الأنابيب

JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();

async function pipelineDemo() {
  const pipeline = redis.pipeline();

  pipeline.set('key1', 'value1');
  pipeline.set('key2', 'value2');
  pipeline.set('key3', 'value3');
  pipeline.get('key1');

  const results = await pipeline.exec();
  console.log(results);
  // [ [ null, 'OK' ], [ null, 'OK' ], [ null, 'OK' ], [ null, 'value1' ] ]
}

pipelineDemo();

المعاملات

JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();

async function transactionDemo() {
  const multi = redis.multi();

  multi.set('key1', 'value1');
  multi.set('key2', 'value2');
  multi.incr('counter');

  const results = await multi.exec();
  console.log(results);
}

transactionDemo();

إدارة الاتصال

ioredis لديه مجموعة اتصال مدمجة — لا حاجة لتكوين إضافي:

JAVASCRIPT
const Redis = require('ioredis');

// اتصال واحد (مجموعة تلقائية)
const redis = new Redis({
  host: 'localhost',
  port: 6379,
  maxRetriesPerRequest: 3,
  enableReadyCheck: true
});

// وضع المجموعة
const cluster = new Redis.Cluster([
  { host: '127.0.0.1', port: 7000 },
  { host: '127.0.0.1', port: 7001 },
  { host: '127.0.0.1', port: 7002 }
]);

node-redis (العميل الرسمي)

التثبيت

BASH
npm install redis

الاستخدام الأساسي

JAVASCRIPT
const { createClient } = require('redis');

async function main() {
  // إنشاء عميل
  const client = createClient({
    url: 'redis://localhost:6379'
  });

  // الاتصال
  await client.connect();

  // SET و GET
  await client.set('key', 'value');
  const value = await client.get('key');
  console.log(value); // 'value'

  // عمليات التجزئة
  await client.hSet('user:1', 'name', 'Alice');
  const name = await client.hGet('user:1', 'name');

  // عمليات القوائم
  await client.lPush('mylist', 'value1');
  const list = await client.lRange('mylist', 0, -1);

  // عمليات المجموعات
  await client.sAdd('myset', 'a', 'b', 'c');
  const members = await client.sMembers('myset');

  // إغلاق الاتصال
  await client.quit();
}

main();

النشر/الاشتراك

JAVASCRIPT
const { createClient } = require('redis');

async function pubsub() {
  const subscriber = createClient();
  await subscriber.connect();

  // الاشتراك
  await subscriber.subscribe('news', (message) => {
    console.log(`Received: ${message}`);
  });

  const publisher = createClient();
  await publisher.connect();

  // نشر
  await publisher.publish('news', 'Hello World');
}

pubsub();

أمثلة عملية

مثال 1: مُزَيِّن التخزين المؤقت

JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();

function cache(expire = 3600) {
  return function(target, propertyKey, descriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = async function(...args) {
      const cacheKey = `${propertyKey}:${JSON.stringify(args)}`;

      // محاولة الحصول من الذاكرة المؤقتة
      const cached = await redis.get(cacheKey);
      if (cached) {
        return JSON.parse(cached);
      }

      // تنفيذ الطريقة الأصلية
      const result = await originalMethod.apply(this, args);

      // تخزين في الذاكرة المؤقتة
      await redis.set(cacheKey, JSON.stringify(result), 'EX', expire);

      return result;
    };

    return descriptor;
  };
}

class UserService {
  @cache(300)
  async getUser(userId) {
    console.log(`Querying database: userId=${userId}`);
    return { id: userId, name: `User${userId}` };
  }
}
▶ جرّب الكود

مثال 2: قفل موزع

JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();
const { v4: uuidv4 } = require('uuid');

class DistributedLock {
  constructor(key, expire = 10) {
    this.key = `lock:${key}`;
    this.expire = expire;
    this.identifier = uuidv4();
  }

  async acquire() {
    const result = await redis.set(
      this.key,
      this.identifier,
      'NX',
      'EX',
      this.expire
    );
    return result === 'OK';
  }

  async release() {
    const script = `
      if redis.call("get", KEYS[1]) == ARGV[1] then
        return redis.call("del", KEYS[1])
      else
        return 0
      end
    `;
    const result = await redis.eval(script, 1, this.key, this.identifier);
    return result === 1;
  }
}

// استخدام القفل
async function withLock(key, callback) {
  const lock = new DistributedLock(key);

  try {
    while (!(await lock.acquire())) {
      await new Promise(resolve => setTimeout(resolve, 100));
    }

    return await callback();
  } finally {
    await lock.release();
  }
}

// مثال
await withLock('resource', async () => {
  console.log('Executing business logic');
});
▶ جرّب الكود

مثال 3: محدد معدل

JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();

async function rateLimit(key, limit = 10, period = 60) {
  const current = Math.floor(Date.now() / 1000);
  const windowStart = current - period;

  const results = await redis
    .multi()
    .zremrangebyscore(key, 0, windowStart)
    .zcard(key)
    .zadd(key, current, `${current}`)
    .expire(key, period)
    .exec();

  const count = results[1][1];
  return count < limit;
}

// استخدام محدد المعدل
async function apiHandler(userId) {
  const allowed = await rateLimit(`api:${userId}`, 10, 60);

  if (allowed) {
    console.log('Request allowed');
    return { success: true };
  } else {
    console.log('Request denied');
    return { success: false, error: 'Rate limit exceeded' };
  }
}
▶ جرّب الكود

مثال 4: قائمة انتظار رسائل

JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();

class MessageQueue {
  constructor(name) {
    this.name = `queue:${name}`;
  }

  async push(message) {
    await redis.rpush(this.name, JSON.stringify(message));
  }

  async pop(timeout = 0) {
    const result = await redis.blpop(this.name, timeout);
    if (result) {
      return JSON.parse(result[1]);
    }
    return null;
  }

  async size() {
    return await redis.llen(this.name);
  }
}

// المنتج
const queue = new MessageQueue('tasks');
await queue.push({ task: 'send_email', to: 'user@example.com' });

// المستهلك
while (true) {
  const task = await queue.pop(5);
  if (task) {
    console.log('Processing task:', task);
  } else {
    break;
  }
}
▶ جرّب الكود

معالجة الأخطاء

JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();

redis.on('error', (error) => {
  console.error('Redis error:', error);
});

redis.on('connect', () => {
  console.log('Redis connected');
});

redis.on('ready', () => {
  console.log('Redis ready');
});

redis.on('close', () => {
  console.log('Redis connection closed');
});

❓ أسئلة شائعة

س كيف أختار بين ioredis و node-redis؟
ج ioredis أكثر غنى بالميزات (مجموعة، حارس). node-redis هو العميل الرسمي. ioredis موصى به.
س هل ioredis آمن للخيوط؟
ج نعم. ioredis لديه مجموعة اتصال مدمجة ويمكن مشاركته عبر الوحدات.
س كيف أتعامل مع قطع الاتصال؟
ج ioredis يعيد الاتصال تلقائيًا. يمكنك الاستماع لأحداث error و connect.
س كيف تعمل العمليات غير المتزامنة؟
ج جميع عمليات ioredis تُرجع Promises. استخدم async/await أو .then().
س كيف أنفذ مجموعة اتصال؟
ج ioredis لديه مجموعة اتصال مدمجة — لا حاجة لتكوين إضافي.

📖 ملخص

📝 تمارين

  1. العمليات الأساسية: استخدم ioredis لعمليات السلاسل والتجزئات والقوائم
  2. خط الأنابيب: استخدم خط الأنابيب لتعيين 1000 مفتاح دفعة واحدة، قارن الأداء
  3. النشر/الاشتراك: نفذ نظام رسائل نشر/اشتراك بسيط
  4. تطبيقي: نفذ قفلًا موزعًا بسيطًا أو محدد معدل

تهانينا!

لقد أكملت جميع الدروس الـ 26 من دورة Redis! الخطوات التالية:

100%