Workflow Automation

ساختن یک زیرساخت IoT مقاوم: پیاده‌سازی مدارشکن‌ها و صف‌های نامه‌های مرده در Node-RED

سیستم‌های اینترنت اشیا (IoT) ذاتاً شکننده هستند. یک سنسور ناپایدار، یک قطع کوتاه‌مدت شبکه یا یک زمان‌بندی API پایین‌دست می‌تواند منجر به خرابی‌های سلسله‌وار در کل سیستم شود. در حالی که زبان‌های برنامه‌نویسی سنتی این استثناها را با بلوک‌های `try-catch` مدیریت می‌کنند، ابزارهای بصری جریان کاری مانند Node-RED به رویکردی متفاوت برای تحمل خطا نیاز دارند. صرفاً اجازه دادن به پیام‌ها برای شکست خاموشانه گزینه‌ای برای زیرساخت‌های سطح تولید نیست. برای ساخت یک سیستم واقعاً خودترمیم، باید مدارشکن‌ها (Circuit Breakers) را برای جلوگیری از خرابی‌های سلسله‌وار پیاده‌سازی کنیم و صف‌های نامه‌های مرده (DLQs) را برای ثبت و بازرسی پیام‌های غیرقابل تحویل استفاده کنیم.

چرا جریان‌های کاری بصری به مدیریت خطای صریح نیاز دارند؟

در یک جریان Node-RED، اگر یک گره شکست بخورد (مثلاً یک درخواست HTTP زمان‌بندی شود)، پیام معمولاً حذف می‌شود یا به یک گره خطا ارسال می‌شود. بدون یک استراتژی، یک سرویس پایین‌دست شکست‌خورده (مانند یک پایگاه داده ابری) باعث می‌شود کل جریان متوقف شود، حافظه مصرف کند و هشدارهای حیاتی برای دستگاه‌های سالم را به تأخیر بیندازد. هدف یک سیستم خودترمیم این است که به طور خودکار این شکست‌ها را تشخیص دهد، ارسال ترافیک به اجزای خراب را متوقف کند (مدارشکن) و به طور ایمن داده‌های شکست‌خورده را برای بازیابی بعدی ذخیره کند (DLQ) بدون اینکه بقیه خط لوله را متوقف کند.

پیاده‌سازی الگوی مدارشکن (Circuit Breaker)

یک مدارشکن نرخ موفقیت فراخوانی‌ها به یک سرویس وابسته را پایش می‌کند. اگر نرخ شکست از یک آستانه فراتر رود، مدار را «باز» می‌کند و بلافاصله درخواست‌های بعدی را برای مدت مشخصی شکست می‌دهد تا به سرویس فرصت بازیابی بدهد.

اگرچه Node-RED دارای مدیریت خطای داخلی است، اما می‌توانیم این منطق را با ترکیبی از گره‌های switch، function و change پیاده‌سازی کنیم. در اینجا یک پیاده‌سازی مفهومی با استفاده از یک گره function که تعداد شکست‌ها را در حافظه زمینه (context storage) ردیابی می‌کند، آورده شده است.

// Function Node: Circuit Breaker Logic
// This node should be placed before the unstable HTTP Request node.

let failures = context.get('failures') || 0;
let circuitState = context.get('circuitState') || 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
const FAILURE_THRESHOLD = 3;
const RESET_TIMEOUT = 30000; // 30 seconds

// If the circuit is OPEN, check if it's time to try again (HALF_OPEN)
if (circuitState === 'OPEN') {
    const now = new Date().getTime();
    const lastOpenTime = context.get('lastOpenTime') || 0;
    
    if (now - lastOpenTime > RESET_TIMEOUT) {
        circuitState = 'HALF_OPEN';
        context.set('circuitState', circuitState);
        // Allow one request to pass through to test the service
        return msg;
    } else {
        // Circuit is still open, drop the message and trigger alert
        msg.payload = { error: "Service unavailable", circuit: "OPEN" };
        msg.topic = "circuit_breaker_trip";
        return [msg, null]; // Route to DLQ/Error Alert
    }
}

// Pass message to the service node
msg._circuitContext = { failures: failures };
return msg;

// **IMPORTANT:** You must handle the response.
// Attach a "Circuit Breaker Handler" node after the service call.
// If the service call fails (error node), increment failures.
// If it succeeds, reset failures.

مدیریت پاسخ: باید هر دو مسیر موفقیت و شکست را ثبت کنید. خروجی out گره سرویس خود را به یک گره function متصل کنید که در صورت موفقیت، تعداد شکست‌ها را بازنشانی می‌کند. خروجی error گره سرویس را به یک گره function جداگانه متصل کنید که تعداد شکست‌ها را افزایش می‌دهد و اگر آستانه برآورده شود، circuitState را به OPEN به‌روزرسانی می‌کند.

پیاده‌سازی صف نامه‌های مرده (DLQ)

وقتی یک مدارشکن فعال می‌شود یا یک پیام در اعتبارسنجی شکست می‌خورد، داده را دور نمی‌ریزیم. آن را به یک صف نامه‌های مرده ارسال می‌کنیم. در Node-RED، یک DLQ معمولاً به عنوان یک ذخیره‌گاه پایدار پیاده‌سازی می‌شود، مانند یک گره file، یک موضوع mqtt با نام dlq/alerts، یا یک مجموعه database.

در اینجا نحوه ساختاردهی گره DLQ برای اطمینان از یکپارچگی داده آورده شده است:

// Function Node: Prepare Message for DLQ
// This node is placed on the error output of the Circuit Breaker
// or the main error node of the flow.

const dlqMessage = {
    originalPayload: msg.payload,
    originalTopic: msg.topic,
    deviceId: msg.deviceId,
    timestamp: new Date().toISOString(),
    errorReason: msg.error.message || "Unknown Error",
    retryCount: context.get('retryCount') || 0
};

// Optionally, increment a retry counter for scheduled reprocessing
const currentRetries = dlqMessage.retryCount + 1;
dlqMessage.retryCount = currentRetries;

return {
    payload: dlqMessage,
    topic: "iot/dlq/alerts" // Use a dedicated MQTT topic
};

بازیابی خودکار و تلاش مجدد

یک سیستم خودترمیم واقعی فقط پیام‌های شکست‌خورده را ذخیره نمی‌کند؛ بلکه تلاش می‌کند آن‌ها را بازیابی کند. یک گره inject اضافه کنید که هر ۵ دقیقه فعال شود. این گره باید از ذخیره‌گاه DLQ شما بخواند (مثلاً یک اشتراک MQTT به iot/dlq/alerts/# با یک سیاست نگهداری، یا یک کوئری پایگاه داده).

برای هر پیامی که از DLQ بازیابی می‌شود، circuitState را بررسی کنید. اگر مدار CLOSED یا HALF_OPEN باشد، payload اصلی را به جریان پردازش اصلی دوباره منتشر کنید. اگر مدار OPEN باشد، آن را در DLQ برای چرخه بعدی رها کنید. این یک مکانیزم فشار بازگشتی (backpressure) طبیعی ایجاد می‌کند که در آن پیام‌های شکست‌خورده فقط زمانی تلاش مجدد می‌شوند که سرویس پایین‌دست سالم باشد.

نتیجه‌گیری

با یکپارچه‌سازی مدارشکن‌ها و صف‌های نامه‌های مرده در جریان‌های کاری Node-RED خود، یک سیستم IoT شکننده را به یک معماری مقاوم و خودترمیم تبدیل می‌کنید. مدارشکن از بار بیش از حد جلوگیری می‌کند و به سرویس‌ها اجازه بازیابی می‌دهد، در حالی که DLQ اطمینان می‌بخشد که هیچ داده‌ای از دست نمی‌رود. این الگو برای هر استقرار IoT در سطح تولید حیاتی است که در آن یکپارچگی داده و زمان فعالیت (uptime) از اهمیت بالایی برخوردارند. از کوچک شروع کنید: منطق مدارشکن را برای ناپایدارترین سرویس پایین‌دست خود پیاده‌سازی کنید و تأثیر آن بر پایداری سیستم را مشاهده کنید.

Share: