در معماریهای توزیعشده مدرن، یک سیستم اعلاندهی بسیار فراتر از یک تابع «ارسال ایمیل» است. این سیستم یک جزء حیاتی در تعامل کاربر بوده و به عنوان نبض برنامه شما عمل میکند. چه یک هشدار تراکنشی باشد، چه یک کمپین بازاریابی، یا یک پیام چت بلادرنگ، قابلیت اطمینان و تأخیر خط لوله اعلاندهی شما مستقیماً بر اعتماد کاربر تأثیر میگذارد. طراحی سیستمی که میلیونها پیام در ثانیه را با حفظ تأخیر کم مدیریت میکند، نیازمند رویکردی منطقی به جداسازی، اولویتبندی و مدیریت کانالها است.
معماری اصلی: جداسازی از طریق صفهای پیام
اصل بنیادین یک سیستم اعلاندهی مقیاسپذیر، جداسازی است. منطق برنامه که یک رویداد را تولید میکند (مثلاً «سفارش ارسال شد») هرگز نباید روی مکانیسم تحویل مسدود شود. به جای آن، باید یک رویداد را به یک واسطه پیام، مانند Apache Kafka، RabbitMQ یا Amazon SNS منتشر کند. این کار تضمین میکند که اگر سرویس ایمیل از کار بیفتد، برنامه اصلی همچنان کارآمد باقی بماند.
قطعه کد شبهکد زیر را در نظر بگیرید که نشان میدهد چگونه یک سرویس برنامه بدون مسدود شدن یک رویداد را منتشر میکند:
function onOrderShipped(orderId, trackingId) {
const event = {
type: "ORDER_SHIPPED",
payload: { orderId, trackingId },
timestamp: Date.now()
};
// Fire and forget to the message queue
messageBroker.publish("notification-events", event);
// Log for observability but do not await delivery completion
logger.info("Notification event queued", { orderId });
}
تحویل چندکاناله و اولویتبندی
کاربران انتظار دارند اعلانها از طریق کانالهای متعدد دریافت کنند: ایمیل، پیامک، اعلانهای پوش و توستهای درونبرنامهای. هر کانال نیازمندیهای تأخیر و ساختارهای هزینه متفاوتی دارد. اعلانهای پوش و پیامهای درونبرنامهای معمولاً بلادرنگ و کمهزینه هستند، در حالی که پیامک پرهزینه و ایمیل کمهزینه اما با تأخیر بالاتر است.
برای مدیریت این موضوع، شما به یک مسیریاب اعلان نیاز دارید. این سرویس رویدادها را از صف مصرف کرده و بر اساس ترجیحات کاربر و شدت رویداد، منطق مسیریابی را اعمال میکند. رویدادهای حیاتی (مانند «کد احراز هویت دومرحلهای») باید صفهای استاندارد را دور بزنند و از یک کانال مستقیم با اولویت بالا استفاده کنند. رویدادهای غیرفوری (مانند «دنبالکننده جدید») میتوانند دستهبندی شوند تا فراخوانیهای API به ارائهدهندگان ثالث کاهش یابد.
پیادهسازی یک ساختار صف اولویتدار ضروری است:
class NotificationQueue {
constructor() {
this.priorityMap = new Map(); // 1: Critical, 2: High, 3: Normal
}
enqueue(notification) {
const priority = this.calculatePriority(notification);
if (!this.priorityMap.has(priority)) {
this.priorityMap.set(priority, []);
}
this.priorityMap.get(priority).push(notification);
}
dequeue() {
// Always serve the highest priority (lowest number) first
for (let i = 1; i <= 3; i++) {
const queue = this.priorityMap.get(i);
if (queue && queue.length > 0) {
return queue.shift();
}
}
return null; // Queue empty
}
}
مدیریت خطاها و توانمندی (Idempotency)
در سیستمهای توزیعشده، شکست اجتنابناپذیر است. یک سرویس اعلان پوش ممکن است زمانبندیاش تمام شود (timeout) یا یک دروازه پیامک ممکن است درخواستی را رد کند. سیستم شما باید بازتلاشها را بدون تکرار پیامها بهطور ظریف مدیریت کند. اینجاست که توانمندی (Idempotency) وارد میشود. هر اعلان باید یک شناسه منحصربهفرد داشته باشد. قبل از ارسال، سیستم یک کش (مانند Redis) را بررسی میکند تا اطمینان حاصل کند که اعلان قبلاً پردازش نشده است.
علاوه بر این، برای بازتلاشها از پسنشینی نمایی (exponential backoff) استفاده کنید. اگر یک API ثالث شکست بخورد، ۱ ثانیه، سپس ۲ ثانیه، و سپس ۴ ثانیه صبر کنید قبل از تلاش مجدد. پس از تعداد مشخصی از شکستها (مثلاً ۵ بار)، اعلان را به یک «صف نامه مرده» (DLQ) منتقل کنید تا برای بازرسی دستی یا تحویل جایگزین (مثلاً اگر پیامک شکست خورد، ایمیل را امتحان کنید) استفاده شود.
نتیجهگیری
ساختن یک سیستم اعلاندهی یک تعادل میان قابلیت اطمینان، تأخیر و هزینه است. با جداسازی تولید رویداد از تحویل، استفاده از صفهای اولویتدار برای اقدامات حیاتی و اعمال توانمندی، میتوانید سیستمی بسازید که بهآسانی مقیاسپذیر باشد. با رشد پایگاه کاربری شما، به یاد داشته باشید که شاخصهای نرخ تحویل و تأخیر را پایش کنید تا اطمینان حاصل شود که خط لوله اعلاندهی شما همچنان ستون فقرات قابل اتکای تجربه کاربری شما باقی میماند.