Node.js مع Redis
Node.js و Redis متناسبان طبيعيًا. يغطي هذا الدرس استخدام Redis مع Node.js.
عملاء Node.js لـ Redis
عملاء Node.js الرئيسيون لـ Redis:
- ioredis: غني بالميزات، يدعم المجموعة والحارس وبرامج Lua النصية
- redis (node-redis): العميل الرسمي، سهل الاستخدام
- redis (v3): إصدار أقدم، واسع الاستخدام
💡 توصية: المشاريع الجديدة يجب أن تستخدم 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 لديه مجموعة اتصال مدمجة — لا حاجة لتكوين إضافي.
📖 ملخص
- ioredis: غني بالميزات، يدعم المجموعة والحارس وبرامج Lua النصية
- node-redis: العميل الرسمي، سهل الاستخدام
- جميع العمليات غير متزامنة، تُرجع Promises
- يدعم خط الأنابيب والمعاملات والنشر/الاشتراك
- مجموعة اتصال مدمجة وإعادة اتصال تلقائي
- أمثلة عملية: تخزين مؤقت، أقفال موزعة، تحديد معدل، قوائم انتظار رسائل
📝 تمارين
- العمليات الأساسية: استخدم ioredis لعمليات السلاسل والتجزئات والقوائم
- خط الأنابيب: استخدم خط الأنابيب لتعيين 1000 مفتاح دفعة واحدة، قارن الأداء
- النشر/الاشتراك: نفذ نظام رسائل نشر/اشتراك بسيط
- تطبيقي: نفذ قفلًا موزعًا بسيطًا أو محدد معدل
تهانينا!
لقد أكملت جميع الدروس الـ 26 من دورة Redis! الخطوات التالية:
- الممارسة: طبق Redis في مشاريع حقيقية
- التعمق: تعلم عن مجموعة Redis والحارس وبرامج Lua النصية
- التحسين: تعلم تحسين أداء Redis وأفضل الممارسات



