Node.js बैकएंड विकास बूटकैंप · पाठ

Circuit Breaker पैटर्न और Bulkhead पृथक्करण

Circuit को ट्रिप करके और प्रत्येक निर्भरता के लिए संसाधन पूल अलग करके श्रृंखलाबद्ध विफलताओं को रोकें।

पाठ 2, कुल 4 में से13 चरण

Circuit Breaker पैटर्न और Bulkhead पृथक्करण, CoddyKit पर Node.js बैकएंड विकास बूटकैंप का एक निःशुल्क पाठ है। यह 4 में से 2वाँ पाठ है। आप नीचे पूरा पाठ निःशुल्क पढ़ सकते हैं—फिर अंतर्निहित कोड संपादक और 24/7 एआई ट्यूटर के साथ ब्राउज़र में इसका व्यावहारिक अभ्यास कर सकते हैं। यह Node.js बैकएंड विकास बूटकैंप सीखने के मार्ग का हिस्सा है और आपकी प्रगति वेब तथा CoddyKit ऐप पर सिंक होती रहती है। Node.js बैकएंड विकास बूटकैंप पाठ्यक्रम में कुल 4 पाठ शामिल हैं।

श्रृंखलाबद्ध विफलताएँ क्यों होती हैं

माइक्रोसर्विस बैकएंड में आपका Node.js API अक्सर नीचे की ओर स्थित सेवाओं पर निर्भर करता है: भुगतान प्रदाता, इन्वेंटरी सेवा या डेटाबेस। जब कोई निर्भरता धीमी हो जाती है (बंद भी नहीं—सिर्फ धीमी), तो उससे जुड़े हर अनुरोध का ढेर लगने लगता है।

  • हर लंबित अनुरोध इवेंट-लूप का एक स्थान, एक सॉकेट और मेमोरी रोककर रखता है।
  • कॉल करने वाले पुनः प्रयास करते हैं, जिससे पहले से संघर्ष कर रही निर्भरता पर लोड बढ़ जाता है।
  • बीमार निर्भरता के इंतज़ार में प्रक्रिया भर जाने के कारण आपकी स्वस्थ एंडपॉइंट सेवाओं को संसाधन नहीं मिलते।

इसे श्रृंखलाबद्ध विफलता कहते हैं: एक धीमी निर्भरता पूरी सेवा को नीचे खींच लेती है। इससे निपटने वाले दो प्रतिरूप हैं—सर्किट ब्रेकर (विफल निर्भरता को कॉल करना रोकना) और बल्कहेड (किसी एक निर्भरता द्वारा उपयोग किए जा सकने वाले संसाधनों की संख्या सीमित करना)।

भोला-भाला पुनः प्रयास स्थिति को और खराब करता है

आम तौर पर पहली प्रतिक्रिया किसी अस्थिर कॉल को पुनः प्रयास चक्र में लपेटने की होती है। लेकिन बिना सोचे किए गए पुनः प्रयास आंशिक खराबी को पूर्ण खराबी में बदल देते हैं—ठीक उसी समय ट्रैफ़िक तीन गुना हो जाता है जब निर्भरता उसे सबसे कम सँभाल सकती है।

नीचे एक कमज़ोर डाउनस्ट्रीम सेवा पर लगातार अनुरोध भेजे जा रहे हैं। ध्यान दें कि पुनः प्रयास 1 तार्किक अनुरोध को कई वास्तविक कॉलों में बदल देते हैं।

async function callDependency(attempt) {
  // Simulate a dependency that fails 70% of the time
  if (Math.random() < 0.7) {
    throw new Error('dependency timeout');
  }
  return 'ok';
}

async function withBlindRetry(maxRetries) {
  let physicalCalls = 0;
  for (let i = 0; i <= maxRetries; i++) {
    physicalCalls++;
    try {
      const res = await callDependency(i);
      console.log(`Success after ${physicalCalls} physical call(s)`);
      return res;
    } catch (e) {
      console.log(`Attempt ${i + 1} failed: ${e.message}`);
    }
  }
  console.log(`Gave up after ${physicalCalls} physical calls`);
}

withBlindRetry(3);

सर्किट ब्रेकर की अवस्थाएँ

सर्किट ब्रेकर किसी कॉल को लपेटता है और उसके स्वास्थ्य पर नज़र रखता है। यह तीन अवस्थाओं वाली एक छोटी स्थिति मशीन है:

  • CLOSED — कॉल सामान्य रूप से आगे जाती हैं। विफलताओं की गिनती की जाती है।
  • OPEN — हाल की बहुत अधिक विफलताएँ। निर्भरता को छुए बिना कॉल को तुरंत अस्वीकार कर दिया जाता है (तुरंत विफल)।
  • HALF_OPEN — ठंडा होने की अवधि के बाद कुछ परीक्षण कॉलों की अनुमति दी जाती है। सफल होने पर CLOSED में लौटें; विफल होने पर तुरंत OPEN में लौट जाएँ।

मुख्य बात यह है कि OPEN अवस्था में पहले से विफल हो रही निर्भरता पर संसाधन बर्बाद करना बंद कर देते हैं। इससे उसे सँभलने की गुंजाइश मिलती है और आपका इवेंट लूप खाली रहता है।

न्यूनतम सर्किट ब्रेकर

यह एक स्वतंत्र रूप से काम करने वाला ब्रेकर है। यह विफलताओं की सीमा पार होने पर खुलता है, OPEN रहते हुए तुरंत अनुरोध अस्वीकार करता है और रीसेट समय-सीमा पूरी होने पर जाँच करता है। हर उत्पादन-स्तरीय लाइब्रेरी में यही मूल तर्क लागू किया जाता है।

class CircuitBreaker {
  constructor(fn, { threshold = 3, resetMs = 5000 } = {}) {
    this.fn = fn;
    this.threshold = threshold;
    this.resetMs = resetMs;
    this.failures = 0;
    this.state = 'CLOSED';
    this.nextTry = 0;
  }

  async exec(...args) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextTry) {
        throw new Error('Circuit OPEN - failing fast');
      }
      this.state = 'HALF_OPEN';
    }
    try {
      const result = await this.fn(...args);
      this.failures = 0;
      this.state = 'CLOSED';
      return result;
    } catch (err) {
      this.failures++;
      if (this.failures >= this.threshold) {
        this.state = 'OPEN';
        this.nextTry = Date.now() + this.resetMs;
      }
      throw err;
    }
  }
}

let n = 0;
const flaky = async () => { n++; if (n <= 5) throw new Error('boom'); return 'ok'; };
const breaker = new CircuitBreaker(flaky, { threshold: 3, resetMs: 1000 });

(async () => {
  for (let i = 0; i < 4; i++) {
    try { console.log(await breaker.exec()); }
    catch (e) { console.log(`call ${i}: ${e.message} [state=${breaker.state}]`); }
  }
})();

धीमी विफलता की तुलना में तुरंत विफल होना बेहतर है

OPEN अवस्था का वास्तविक लाभ विलंबता है। समय-सीमा 10 सेकंड की हो सकती है, जबकि सक्रिय हुआ ब्रेकर माइक्रोसेकंड में अनुरोध अस्वीकार कर देता है। लोड के समय यही अंतर सेवा के बचने और ढहने के बीच होता है।

तुलना करें: 2 सेकंड की समय-सीमा वाली निर्भरता तक पहुँचने वाले 100 अनुरोधों की लागत और सक्रिय होते ही तुरंत अस्वीकार कर देने वाले ब्रेकर की लागत।

function estimate(reqs, timeoutMs, openAfter) {
  let totalMs = 0;
  for (let i = 0; i < reqs; i++) {
    if (i < openAfter) {
      totalMs += timeoutMs; // these waited for the full timeout
    } else {
      totalMs += 0.01; // breaker rejected instantly
    }
  }
  return totalMs;
}

const reqs = 100, timeout = 2000, openAfter = 5;
const withBreaker = estimate(reqs, timeout, openAfter);
const noBreaker = reqs * timeout;
console.log(`No breaker: ${noBreaker} ms of blocked time`);
console.log(`With breaker: ${withBreaker.toFixed(2)} ms of blocked time`);
console.log(`Saved: ${(noBreaker - withBreaker).toFixed(0)} ms`);

उत्पादन में Opossum का उपयोग

अपने हाथ से बनाया हुआ ब्रेकर उत्पादन में न भेजें। Node.js की व्यवहारिक मानक लाइब्रेरी opossum है। इसमें चलती विंडो की त्रुटि दरें, एक fallback, प्रति-कॉल समय-सीमाएँ और विस्तृत घटनाएँ/मेट्रिक्स मिलते हैं।

  • timeout — बहुत देर तक अटकी कॉल को रद्द करें।
  • errorThresholdPercentage — विंडो में इतनी प्रतिशत कॉल विफल होने पर ब्रेकर सक्रिय करें।
  • resetTimeout — जाँच करने से पहले OPEN अवस्था में रहने की अवधि।

इस अंश के लिए opossum पैकेज और नेटवर्क कॉल आवश्यक है, इसलिए यह केवल उदाहरणात्मक है और यहाँ चलाया नहीं जा सकता।

const CircuitBreaker = require('opossum');

async function getInventory(sku) {
  const res = await fetch(`http://inventory.internal/items/${sku}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

const breaker = new CircuitBreaker(getInventory, {
  timeout: 3000,                 // fail a call after 3s
  errorThresholdPercentage: 50,  // open at 50% failures in the window
  resetTimeout: 10000,           // try again after 10s
  rollingCountTimeout: 10000,    // 10s stats window
});

// Serve stale/cached data instead of erroring out
breaker.fallback((sku) => ({ sku, stock: 'unknown', cached: true }));

breaker.on('open', () => console.warn('inventory breaker OPEN'));
breaker.on('halfOpen', () => console.info('inventory breaker probing'));

module.exports = (sku) => breaker.fire(sku);

वैकल्पिक व्यवस्थाएँ: कमजोर बनें, मरें नहीं

सक्रिय हुआ ब्रेकर आम तौर पर 500 लौटाने के बजाय कुछ उपयोगी लौटाए। इसे सुगम अवनयन कहते हैं:

  • कैश किया हुआ या पुराना मान दिखाएँ।
  • सुरक्षित डिफ़ॉल्ट लौटाएँ (खाली सुझाव, आखिरी ज्ञात कीमत)।
  • काम को बाद के लिए कतार में रखें (बफ़र में लिखें और निर्भरता ठीक होने पर उसे संसाधित करें)।

व्यावसायिक नियमों के आधार पर हर निर्भरता के लिए अलग वैकल्पिक व्यवस्था चुनें। मूल्य-निर्धारण सेवा को सुरक्षित रूप से विफल होना चाहिए (बिक्री रोकनी चाहिए); सुझाव सेवा को खुला विफल होना चाहिए (कुछ न दिखाए), ताकि पृष्ठ फिर भी लोड हो सके।

बल्कहेड प्रतिरूप

ब्रेकर चीज़ें बिगड़ने के बाद कॉल रोकता है। बल्कहेड किसी एक निर्भरता को शुरू से ही संसाधनों पर एकाधिकार करने से रोकता है। यह नाम जहाज़ों से आया है: जलरोधी खंड, ताकि एक हिस्से में पानी भरने से पूरा जहाज़ न डूबे।

बैकएंड में हर निर्भरता को अपना सीमित पूल दिया जाता है:

  • एक साथ चल रही कॉलों की सीमित संख्या।
  • हर डेटाबेस या HTTP लक्ष्य के लिए अलग कनेक्शन पूल।
  • वैकल्पिक रूप से सीमित कतार; अतिरिक्त अनुरोध तुरंत अस्वीकार किए जाते हैं।

इसलिए यदि भुगतान API अटक जाए, तो अधिकतम N स्थान ही वहाँ अटकेंगे—आपकी इन्वेंटरी और प्रमाणीकरण कॉल अपने-अपने स्थान बनाए रखेंगी और स्वस्थ रहेंगी।

समवर्ती बल्कहेड लागू करना

सबसे सरल बल्कहेड समवर्तीता सीमित करने वाला उपकरण (सेमाफोर) है। यह किसी दी गई निर्भरता के लिए एक साथ चलने वाली कॉलों की संख्या सीमित करता है और बाकी को अस्वीकार (या कतार में) कर देता है, इसलिए धीमी निर्भरता कभी भी maxConcurrent से अधिक स्थान नहीं रोक सकती।

class Bulkhead {
  constructor(maxConcurrent, maxQueue = 0) {
    this.max = maxConcurrent;
    this.maxQueue = maxQueue;
    this.active = 0;
    this.queue = [];
  }

  async run(task) {
    if (this.active >= this.max) {
      if (this.queue.length >= this.maxQueue) {
        throw new Error('Bulkhead full - rejected');
      }
      await new Promise((resolve) => this.queue.push(resolve));
    }
    this.active++;
    try {
      return await task();
    } finally {
      this.active--;
      const next = this.queue.shift();
      if (next) next();
    }
  }
}

const bh = new Bulkhead(2, 2);
const slow = (id) => () => new Promise((r) => setTimeout(() => { console.log('done', id); r(id); }, 50));

(async () => {
  const results = await Promise.allSettled(
    [1, 2, 3, 4, 5, 6].map((id) => bh.run(slow(id)))
  );
  results.forEach((r, i) =>
    console.log(`task ${i + 1}: ${r.status}${r.reason ? ' - ' + r.reason.message : ''}`)
  );
})();

प्रति-निर्भरता कनेक्शन पूल

वास्तविक दुनिया में सबसे आम बल्कहेड HTTP कनेक्शन पूल है। Node का डिफ़ॉल्ट वैश्विक एजेंट सभी लक्ष्यों के बीच सॉकेट साझा करता है। इसके बजाय हर डाउनस्ट्रीम सेवा को सीमित maxSockets वाला अपना Agent दें। अटकी हुई निर्भरता केवल अपने पूल को समाप्त कर सकती है।

इसमें http मॉड्यूल का Agent और एक सक्रिय सॉकेट उपयोग होता है, इसलिए इसे चलने योग्य कार्यक्रम के बजाय कॉन्फ़िगरेशन प्रतिरूप समझें।

const http = require('http');

// One isolated pool per downstream service
const paymentsAgent = new http.Agent({
  keepAlive: true,
  maxSockets: 10,        // at most 10 concurrent connections to payments
  maxFreeSockets: 5,
});

const inventoryAgent = new http.Agent({
  keepAlive: true,
  maxSockets: 20,        // inventory gets its own, independent budget
});

function callPayments(path) {
  return new Promise((resolve, reject) => {
    const req = http.request(
      { host: 'payments.internal', path, agent: paymentsAgent, timeout: 3000 },
      (res) => { res.resume(); res.on('end', resolve); }
    );
    req.on('timeout', () => req.destroy(new Error('payments timeout')));
    req.on('error', reject);
    req.end();
  });
}

module.exports = { callPayments, paymentsAgent, inventoryAgent };

ब्रेकर और बल्कहेड को मिलाना

उत्पादन-स्तरीय लचीलापन हर निर्भरता पर दोनों प्रतिरूप लागू करता है:

  • बल्कहेड समवर्तीता सीमित करता है, ताकि धीमी निर्भरता प्रक्रिया को भर न दे।
  • समय-सीमा सुनिश्चित करती है कि कोई एक कॉल हमेशा के लिए न अटके।
  • सर्किट ब्रेकर विफलता दर अधिक होने पर कॉल करना रोक देता है।
  • वैकल्पिक व्यवस्था कमतर, लेकिन उपयोगी प्रतिक्रिया लौटाती है।

क्रम महत्वपूर्ण है: मूल कॉल को समय-सीमा से लपेटें, उसे बल्कहेड के माध्यम से चलाएँ और ब्रेकर को बाहर रखें, ताकि बल्कहेड का स्थान लेने से पहले ही वह कॉल को रोक सके। opossum के साथ ब्रेकर का timeout और उसके capacity/volume विकल्प इसका अधिकांश भाग सँभाल सकते हैं, लेकिन प्रति-निर्भरता स्पष्ट पूल सबसे मजबूत पृथक्करण देते हैं।

// Compose: breaker(bulkhead(timeout(call)))
function withTimeout(fn, ms) {
  return (...args) => Promise.race([
    fn(...args),
    new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), ms)),
  ]);
}

function resilient(rawCall, { bulkhead, breaker, timeoutMs }) {
  const timed = withTimeout(rawCall, timeoutMs);
  // breaker on the OUTSIDE: it can fail fast before we ever take a bulkhead slot
  return (...args) => breaker.exec(() => bulkhead.run(() => timed(...args)));
}

async function demo() {
  await withTimeout(() => Promise.resolve('fast'), 50)().then(console.log);
  try { await withTimeout(() => new Promise(() => {}), 30)(); }
  catch (e) { console.log('slow call ->', e.message); }
  console.log('Compose order: breaker -> bulkhead -> timeout -> rawCall');
}
demo();

त्वरित जाँच

आपकी भुगतान निर्भरता धीमी प्रतिक्रिया देने लगती है (3-8 सेकंड), लेकिन त्रुटि नहीं देती। उसके लिए किया गया हर अनुरोध इवेंट-लूप का एक स्थान और एक सॉकेट रोकता है। कौन-सा संयोजन इस एक धीमी निर्भरता को आपकी पूरी Node.js सेवा को गिराने से सबसे अच्छी तरह रोकता है?

पुनरावलोकन

Node.js बैकएंड में श्रृंखलाबद्ध विफलताओं को रोकने के लिए हर निर्भरता को अलग रखें और उसकी रक्षा करें:

  • श्रृंखलाबद्ध विफलता केवल मृत निर्भरता से नहीं, बल्कि धीमी निर्भरता से भी शुरू होती है—लंबित कॉल सॉकेट और इवेंट लूप समाप्त कर देती हैं।
  • सर्किट ब्रेकर (CLOSED / OPEN / HALF_OPEN) हाल की त्रुटि दर अधिक होने पर तुरंत विफल होता है और निर्भरता को सँभलने की गुंजाइश देता है। उत्पादन में opossum का उपयोग करें।
  • वैकल्पिक व्यवस्थाएँ खराबी को सुगम अवनयन में बदल देती हैं—कैश किए हुए मान, सुरक्षित डिफ़ॉल्ट या कतार में रखा काम।
  • बल्कहेड समवर्तीता सीमित करता है और हर निर्भरता को अपना कनेक्शन पूल देता है, इसलिए एक बीमार सेवा बाकी सेवाओं के संसाधन नहीं रोक सकती।
  • इन्हें मिलाकर उपयोग करें प्रति निर्भरता: समय-सीमा वाली कॉल के चारों ओर बल्कहेड और उसके चारों ओर ब्रेकर, साथ में OPEN/अस्वीकृत मार्ग के लिए वैकल्पिक व्यवस्था।

बिना सोचे किए गए पुनः प्रयास जाल हैं: वे ठीक उसी समय लोड बढ़ाते हैं जब प्रणाली सबसे कमजोर होती है।

शुरुआत निःशुल्क

एआई शिक्षक के साथ JavaScript सीखें — निःशुल्क

अपने ब्राउज़र में वास्तविक कोड लिखें और चलाएँ, चौबीसों घंटे एआई शिक्षक से तुरंत सहायता पाएँ, और वेब या ऐप पर वहीं से शुरू करें जहाँ आपने छोड़ा था।

पाठ्यक्रम
22
पाठ
92

अक्सर पूछे जाने वाले प्रश्न

क्या “Circuit Breaker पैटर्न और Bulkhead पृथक्करण” पाठ निःशुल्क है?

हाँ—“Circuit Breaker पैटर्न और Bulkhead पृथक्करण” का पूरा पाठ यहाँ वेब पर निःशुल्क पढ़ा जा सकता है। इंटरैक्टिव अभ्यास (अंतर्निहित कोड संपादक और 24/7 एआई ट्यूटर) करने और Node.js बैकएंड विकास बूटकैंप पाठ्यक्रम का बाकी हिस्सा अनलॉक करने के लिए CoddyKit PRO लें। Node.js बैकएंड विकास बूटकैंप पाठ्यक्रम में कुल 4 पाठ शामिल हैं।

“Circuit Breaker पैटर्न और Bulkhead पृथक्करण” में मैं क्या सीखूँगा?

Circuit को ट्रिप करके और प्रत्येक निर्भरता के लिए संसाधन पूल अलग करके श्रृंखलाबद्ध विफलताओं को रोकें। आप ब्राउज़र में सीधे चलाए जाने वाले व्यावहारिक कोड के साथ Node.js बैकएंड विकास बूटकैंप का अभ्यास करते हैं, और पाठ पूरा करते समय 24/7 एआई ट्यूटर आपके प्रश्नों के उत्तर देता है।

क्या Node.js बैकएंड विकास बूटकैंप शुरू करने के लिए मुझे किसी अनुभव की आवश्यकता है?

पहले के अनुभव की आवश्यकता नहीं है। CoddyKit पर Node.js बैकएंड विकास बूटकैंप शुरुआती से लेकर उन्नत शिक्षार्थियों तक सभी के लिए व्यवस्थित किया गया है, इसलिए आप यहीं से या शुरुआत से सीखना शुरू कर सकते हैं और अपनी गति से आगे बढ़ सकते हैं। यह 4 में से 2वाँ पाठ है।

“Circuit Breaker पैटर्न और Bulkhead पृथक्करण” पाठ पूरा करने में कितना समय लगता है?

CoddyKit का अधिकांश पाठ लगभग 5–10 मिनट में पूरा हो जाता है। हर पाठ छोटा और संवादात्मक है, इसलिए आप लगातार प्रगति करते हैं और वेब या ऐप पर वहीं से सीखना जारी रख सकते हैं जहाँ आपने छोड़ा था।

क्या मैं इस Node.js बैकएंड विकास बूटकैंप पाठ में कोड लिख और चला सकता हूँ?

हाँ। हर Node.js बैकएंड विकास बूटकैंप पाठ में एक अंतर्निर्मित कोड संपादक शामिल है, जिससे आप सीधे अपने ब्राउज़र में वास्तविक कोड लिख और चला सकते हैं और तुरंत एआई प्रतिक्रिया पा सकते हैं—स्थानीय सेटअप की आवश्यकता नहीं है।

इस पाठ्यक्रम के सभी पाठ

  1. Timeout, पुनःप्रयास और Jitter सहित घातीय Backoff
  2. Circuit Breaker पैटर्न और Bulkhead पृथक्करण
  3. सुगम Shutdown और चल रहे अनुरोधों की निकासी
  4. स्वास्थ्य जाँच, Readiness Probe और Load Shedding
← Node.js बैकएंड विकास बूटकैंप पर वापस जाएँ