Skip to content

NOTE

عميل Redis الخاص بـ Bun يدعم إصدارات خادم Redis 7.2 وما فوق.

يوفر Bun روابط أصلية للعمل مع قواعد بيانات Redis مع واجهة برمجة تطبيقات حديثة قائمة على Promise. تم تصميم الواجهة لتكون بسيطة وعالية الأداء، مع إدارة مدمجة للاتصالات، واستجابات مكتوبة بالكامل، ودعم TLS.

ts
import { redis } from "bun";

// تعيين مفتاح
await redis.set("greeting", "Hello from Bun!");

// الحصول على مفتاح
const greeting = await redis.get("greeting");
console.log(greeting); // "Hello from Bun!"

// زيادة عداد
await redis.set("counter", 0);
await redis.incr("counter");

// التحقق من وجود مفتاح
const exists = await redis.exists("greeting");

// حذف مفتاح
await redis.del("greeting");

البداية

لاستخدام عميل Redis، تحتاج أولاً إلى إنشاء اتصال:

ts
import { redis, RedisClient } from "bun";

// استخدام العميل الافتراضي (يقرأ معلومات الاتصال من البيئة)
// process.env.REDIS_URL يُستخدم افتراضيًا
await redis.set("hello", "world");
const result = await redis.get("hello");

// إنشاء عميل مخصص
const client = new RedisClient("redis://username:password@localhost:6379");
await client.set("counter", "0");
await client.incr("counter");

افتراضيًا، يقرأ العميل معلومات الاتصال من متغيرات البيئة التالية (حسب ترتيب الأسبقية):

  • REDIS_URL
  • VALKEY_URL
  • إذا لم يتم تعيينه، الافتراضي هو "redis://localhost:6379"

دورة حياة الاتصال

يتعامل عميل Redis تلقائيًا مع الاتصالات في الخلفية:

ts
// لا يتم إنشاء أي اتصال حتى يتم تنفيذ أمر
const client = new RedisClient();

// الأمر الأول يبدأ الاتصال
await client.set("key", "value");

// يظل الاتصال مفتوحًا للأوامر اللاحقة
await client.get("key");

// أغلق الاتصال صراحةً عند الانتهاء
client.close();

يمكنك أيضًا التحكم يدويًا في دورة حياة الاتصال:

ts
const client = new RedisClient();

// اتصل صراحةً
await client.connect();

// شغّل الأوامر
await client.set("key", "value");

// افصل عند الانتهاء
client.close();

العمليات الأساسية

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

ts
// تعيين مفتاح
await redis.set("user:1:name", "Alice");

// الحصول على مفتاح
const name = await redis.get("user:1:name");

// الحصول على مفتاح كـ Uint8Array
const buffer = await redis.getBuffer("user:1:name");

// حذف مفتاح
await redis.del("user:1:name");

// التحقق من وجود مفتاح
const exists = await redis.exists("user:1:name");

// تعيين انتهاء الصلاحية (بالثواني)
await redis.set("session:123", "active");
await redis.expire("session:123", 3600); // ينتهي خلال ساعة واحدة

// الحصول على الوقت المتبقي (بالثواني)
const ttl = await redis.ttl("session:123");

العمليات الرقمية

ts
// تعيين القيمة الأولية
await redis.set("counter", "0");

// الزيادة بمقدار 1
await redis.incr("counter");

// النقصان بمقدار 1
await redis.decr("counter");

عمليات Hash

ts
// تعيين حقول متعددة في hash
await redis.hmset("user:123", ["name", "Alice", "email", "alice@example.com", "active", "true"]);

// الحصول على حقول متعددة من hash
const userFields = await redis.hmget("user:123", ["name", "email"]);
console.log(userFields); // ["Alice", "alice@example.com"]

// الحصول على حقل واحد من hash (يرجع القيمة مباشرة، null إذا كانت مفقودة)
const userName = await redis.hget("user:123", "name");
console.log(userName); // "Alice"

// زيادة حقل رقمي في hash
await redis.hincrby("user:123", "visits", 1);

// زيادة حقل عائم في hash
await redis.hincrbyfloat("user:123", "score", 1.5);

عمليات Set

ts
// إضافة عضو إلى set
await redis.sadd("tags", "javascript");

// إزالة عضو من set
await redis.srem("tags", "javascript");

// التحقق من وجود عضو في set
const isMember = await redis.sismember("tags", "javascript");

// الحصول على جميع أعضاء set
const allTags = await redis.smembers("tags");

// الحصول على عضو عشوائي
const randomTag = await redis.srandmember("tags");

// إزالة وإرجاع عضو عشوائي
const poppedTag = await redis.spop("tags");

Pub/Sub

يوفر Bun روابط أصلية لبروتوكول Redis Pub/Sub. جديد في Bun 1.2.23

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

للبدء في نشر الرسائل، يمكنك إعداد ناشر في publisher.ts:

typescript
import { RedisClient } from "bun";

const writer = new RedisClient("redis://localhost:6739");
await writer.connect();

writer.publish("general", "Hello everyone!");

writer.close();

في ملف آخر، أنشئ المشترك في subscriber.ts:

typescript
import { RedisClient } from "bun";

const listener = new RedisClient("redis://localhost:6739");
await listener.connect();

await listener.subscribe("general", (message, channel) => {
  console.log(`Received: ${message}`);
});

في طرفية واحدة، شغّل المشترك الخاص بك:

bash
bun run subscriber.ts

وفي أخرى، شغّل الناشر الخاص بك:

bash
bun run publisher.ts

NOTE

وضع الاشتراك يستولي على اتصال `RedisClient`. العميل الذي لديه اشتراكات يمكنه فقط استدعاء `RedisClient.prototype.subscribe()`. بعبارة أخرى، التطبيقات التي تحتاج إلى مراسلة Redis تحتاج إلى اتصال منفصل، يمكن الحصول عليه عبر `.duplicate()`:
ts
import { RedisClient } from "bun";

const redis = new RedisClient("redis://localhost:6379");
await redis.connect();
const subscriber = await redis.duplicate(); 

await subscriber.subscribe("foo", () => {});
await redis.set("bar", "baz");

النشر

يتم نشر الرسائل عبر طريقة publish():

typescript
await client.publish(channelName, message);

الاشتراكات

يسمح لك RedisClient الخاص بـ Bun بالاشتراك في القنوات عبر طريقة .subscribe():

typescript
await client.subscribe(channel, (message, channel) => {});

يمكنك إلغاء الاشتراك عبر طريقة .unsubscribe():

typescript
await client.unsubscribe(); // إلغاء الاشتراك من جميع القنوات.
await client.unsubscribe(channel); // إلغاء الاشتراك من قناة معينة.
await client.unsubscribe(channel, listener); // إلغاء اشتراك مستمع معين.

الاستخدام المتقدم

تنفيذ الأوامر والأنابيب

يُأنبب العميل الأوامر تلقائيًا، مما يحسن الأداء عن طريق إرسال أوامر متعددة في دفعة ومعالجة الاستجابات عند وصولها.

ts
// يتم أنببة الأوامر تلقائيًا افتراضيًا
const [infoResult, listResult] = await Promise.all([redis.get("user:1:name"), redis.get("user:2:email")]);

لتعطيل الأنابيب التلقائية، يمكنك تعيين خيار enableAutoPipelining إلى false:

ts
const client = new RedisClient("redis://localhost:6379", {
  enableAutoPipelining: false, 
});

الأوامر الخام

عندما تحتاج إلى استخدام أوامر ليس لها طرق راحة، يمكنك استخدام طريقة send:

ts
// شغّل أي أمر Redis
const info = await redis.send("INFO", []);

// LPUSH إلى قائمة
await redis.send("LPUSH", ["mylist", "value1", "value2"]);

// الحصول على نطاق القائمة
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);

تسمح لك طريقة send باستخدام أي أمر Redis، حتى تلك التي ليس لها طرق مخصصة في العميل. الوسيطة الأولى هي اسم الأمر، والوسيطة الثانية هي مصفوفة من وسيطات السلسلة.

أحداث الاتصال

يمكنك تسجيل معالجات لأحداث الاتصال:

ts
const client = new RedisClient();

// يُستدعى عند الاتصال بنجاح بخادم Redis
client.onconnect = () => {
  console.log("Connected to Redis server");
};

// يُستدعى عند الانقطاع عن خادم Redis
client.onclose = error => {
  console.error("Disconnected from Redis server:", error);
};

// اتصل/افصل يدويًا
await client.connect();
client.close();

حالة الاتصال والمراقبة

ts
// تحقق من الاتصال
console.log(client.connected); // boolean يشير إلى حالة الاتصال

// تحقق من كمية البيانات المخزنة مؤقتًا (بالبايتات)
console.log(client.bufferedAmount);

تحويل الأنواع

يتعامل عميل Redis تلقائيًا مع تحويل الأنواع لاستجابات Redis:

  • تُرجع استجابات الأعداد الصحيحة كأرقام JavaScript
  • تُرجع السلاسل الضخمة كسلاسل JavaScript
  • تُرجع السلاسل البسيطة كسلاسل JavaScript
  • تُرجع السلاسل الضخمة null كـ null
  • تُرجع استجابات المصفوفات كمصفوفات JavaScript
  • ترمي استجابات الخطأ أخطاء JavaScript مع رموز أخطاء مناسبة
  • تُرجع الاستجابات المنطقية (RESP3) كقيم boolean JavaScript
  • تُرجع استجابات الخرائط (RESP3) ككائنات JavaScript
  • تُرجع استجابات المجموعات (RESP3) كمصفوفات JavaScript

معالجة خاصة لأوامر محددة:

  • EXISTS ترجع boolean بدلاً من number (1 تصبح true، 0 تصبح false)
  • SISMEMBER ترجع boolean (1 تصبح true، 0 تصبح false)

الأوامر التالية تعطل الأنابيب التلقائية:

  • AUTH
  • INFO
  • QUIT
  • EXEC
  • MULTI
  • WATCH
  • SCRIPT
  • SELECT
  • CLUSTER
  • DISCARD
  • UNWATCH
  • PIPELINE
  • SUBSCRIBE
  • UNSUBSCRIBE
  • UNPSUBSCRIBE

خيارات الاتصال

عند إنشاء عميل، يمكنك تمرير خيارات مختلفة لتكوين الاتصال:

ts
const client = new RedisClient("redis://localhost:6379", {
  // مهلة الاتصال بالمللي ثانية (الافتراضي: 10000)
  connectionTimeout: 5000,

  // مهلة الخمول بالمللي ثانية (الافتراضي: 0 = لا مهلة)
  idleTimeout: 30000,

  // ما إذا كان سيتم إعادة الاتصال تلقائيًا عند الانقطاع (الافتراضي: true)
  autoReconnect: true,

  // الحد الأقصى لمحاولات إعادة الاتصال (الافتراضي: 10)
  maxRetries: 10,

  // ما إذا كان سيتم وضع الأوامر في قائمة عند الانقطاع (الافتراضي: true)
  enableOfflineQueue: true,

  // ما إذا كان سيتم أنببة الأوامر تلقائيًا (الافتراضي: true)
  enableAutoPipelining: true,

  // خيارات TLS (الافتراضي: false)
  tls: true,
  // بدلاً من ذلك، قدم تكوين TLS مخصص:
  // tls: {
  //   rejectUnauthorized: true,
  //   ca: "path/to/ca.pem",
  //   cert: "path/to/cert.pem",
  //   key: "path/to/key.pem",
  // }
});

سلوك إعادة الاتصال

عند فقدان الاتصال، يحاول العميل تلقائيًا إعادة الاتصال مع تراجع أسي:

  1. يبدأ العميل بتأخير صغير (50ms) ويضاعفه مع كل محاولة
  2. تأخير إعادة الاتصال محدود بـ 2000ms (ثانيتين)
  3. يحاول العميل إعادة الاتصال حتى maxRetries مرات (الافتراضي: 10)
  4. الأوامر المنفذة أثناء الانقطاع:
    • توضع في قائمة إذا كان enableOfflineQueue هو true (الافتراضي)
    • تُرفض فورًا إذا كان enableOfflineQueue هو false

تنسيقات URL المدعومة

يدعم عميل Redis تنسيقات URL مختلفة:

ts
// عنوان Redis قياسي
new RedisClient("redis://localhost:6379");
new RedisClient("redis://localhost:6379");

// مع المصادقة
new RedisClient("redis://username:password@localhost:6379");

// مع رقم قاعدة البيانات
new RedisClient("redis://localhost:6379/0");

// اتصالات TLS
new RedisClient("rediss://localhost:6379");
new RedisClient("rediss://localhost:6379");
new RedisClient("redis+tls://localhost:6379");
new RedisClient("redis+tls://localhost:6379");

// اتصالات مقبس Unix
new RedisClient("redis+unix:///path/to/socket");
new RedisClient("redis+unix:///path/to/socket");

// TLS عبر مقبس Unix
new RedisClient("redis+tls+unix:///path/to/socket");
new RedisClient("redis+tls+unix:///path/to/socket");

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

يرمي عميل Redis أخطاء مكتوبة لسيناريوهات مختلفة:

ts
try {
  await redis.get("non-existent-key");
} catch (error) {
  if (error.code === "ERR_REDIS_CONNECTION_CLOSED") {
    console.error("Connection to Redis server was closed");
  } else if (error.code === "ERR_REDIS_AUTHENTICATION_FAILED") {
    console.error("Authentication failed");
  } else {
    console.error("Unexpected error:", error);
  }
}

رموز الأخطاء الشائعة:

  • ERR_REDIS_CONNECTION_CLOSED - تم إغلاق الاتصال بخادم Redis
  • ERR_REDIS_AUTHENTICATION_FAILED - فشلت المصادقة مع الخادم
  • ERR_REDIS_INVALID_RESPONSE - تم استلام استجابة غير صالحة من الخادم

أمثلة على حالات الاستخدام

التخزين المؤقت

ts
async function getUserWithCache(userId) {
  const cacheKey = `user:${userId}`;

  // حاول الحصول من التخزين المؤقت أولاً
  const cachedUser = await redis.get(cacheKey);
  if (cachedUser) {
    return JSON.parse(cachedUser);
  }

  // ليس في التخزين المؤقت، احصل من قاعدة البيانات
  const user = await database.getUser(userId);

  // خزن في التخزين المؤقت لمدة ساعة واحدة
  await redis.set(cacheKey, JSON.stringify(user));
  await redis.expire(cacheKey, 3600);

  return user;
}

تحديد المعدل

ts
async function rateLimit(ip, limit = 100, windowSecs = 3600) {
  const key = `ratelimit:${ip}`;

  // زيادة العداد
  const count = await redis.incr(key);

  // تعيين انتهاء الصلاحية إذا كان هذا هو الطلب الأول في النافذة
  if (count === 1) {
    await redis.expire(key, windowSecs);
  }

  // تحقق من تجاوز الحد
  return {
    limited: count > limit,
    remaining: Math.max(0, limit - count),
  };
}

تخزين الجلسة

ts
async function createSession(userId, data) {
  const sessionId = crypto.randomUUID();
  const key = `session:${sessionId}`;

  // خزن الجلسة مع انتهاء الصلاحية
  await redis.hmset(key, ["userId", userId.toString(), "created", Date.now().toString(), "data", JSON.stringify(data)]);
  await redis.expire(key, 86400); // 24 ساعة

  return sessionId;
}

async function getSession(sessionId) {
  const key = `session:${sessionId}`;

  // احصل على بيانات الجلسة
  const exists = await redis.exists(key);
  if (!exists) return null;

  const [userId, created, data] = await redis.hmget(key, ["userId", "created", "data"]);

  return {
    userId: Number(userId),
    created: Number(created),
    data: JSON.parse(data),
  };
}

ملاحظات التنفيذ

تم تنفيذ عميل Redis الخاص بـ Bun بلغة Zig ويستخدم بروتوكول تسلسل Redis (RESP3). يدير الاتصالات بكفاءة ويوفر إعادة اتصال تلقائية مع تراجع أسي.

يدعم العميل أنببة الأوامر، مما يعني أنه يمكن إرسال أوامر متعددة دون انتظار الردود على الأوامر السابقة. هذا يحسن الأداء بشكل كبير عند إرسال أوامر متعددة على التوالي.

القيود والخطط المستقبلية

القيود الحالية لعميل Redis التي نخطط لمعالجتها في الإصدارات المستقبلية:

  • المعاملات (MULTI/EXEC) يجب أن تتم عبر أوامر خام في الوقت الحالي

الميزات غير المدعومة:

  • Redis Sentinel
  • Redis Cluster

Bun بواسطة www.bunjs.com.cn تحرير