IlmHamroh
JavaScript Full-stack/29-qism. Backend integratsiyalar: fayl, to'lov, bot, realtime49/69-dars16 daqiqa
Mundarija (22)

Boshqa xabar broker'lari: RabbitMQ, Kafka, SQS, NATS va pg-boss

Qisqacha: BullMQ — Node ilovasi ichidagi ish navbati. Tizimlar va tillar o'rtasida xabar almashish uchun alohida brokerlar bor. RabbitMQ — marshrutlash ustasi: exchange xabarni qoidalar bo'yicha navbatlarga tarqatadi, ack/nack, prefetch, dead letter. Kafka — xabarlarni o'chirmaydigan jurnal: partition, offset, qayta o'qish. SQS — AWS'ning boshqariladigan navbati, visibility timeout bilan. NATS JetStream — yengil va tez. pg-boss — Redis'siz, Postgres'ning o'zida. Kanonda 10 000 xabar: NATS 1.9 s, RabbitMQ 3.2 s, pg-boss 25.3 s. Kafka va SQS — sinalmagan.

Bu darsda

  • RabbitMQ'ning exchange, queue, binding, ack/nack, prefetch va dead letter tushunchalarini kanon tajribasida ko'rasiz.
  • RabbitMQ 4 dagi kutilmagan qoidani bilasiz: nack qayta yetkazish limitiga hisoblanmaydi.
  • Kafka'ning topic, partition, offset va consumer group g'oyasini modelda tushunasiz.
  • SQS'dagi visibility timeout'ni modellashtirasiz.
  • NATS JetStream va pg-boss o'lchovlarini ko'rib, loyiha uchun broker tanlash mezonlarini bilasiz.

Oldin bilishingiz kerak: BullMQ ishonchlilik, Pub/Sub va Redis Streams, SKIP LOCKED bilan DB-asosli navbat, Nega navbat kerak.

Oldingi darsda vaqtga bog'langan ishlar bilan BullMQ bo'limini yakunladik. Endi BullMQ'dan tashqariga qaraymiz.

1. Nega bu kerak?

BullMQ bizning loyihaga yetadi: bitta Node ilovasi, Redis bor. Lekin kompaniya o'sadi:

  • to'lov servisi Go'da, bildirishnoma servisi Node'da, analitika Python'da — ular bir-biriga hodisa yuborishi kerak;
  • bitta hodisa ("vazifa bajarildi") uchta joyga borishi kerak: eslatma, audit, statistika;
  • hodisalar tarixini qayta o'qish kerak: yangi analitika servisi o'tgan oy hodisalaridan hisob yasashi kerak.

BullMQ — Node kutubxonasi va ish navbati (bitta ish — bitta bajaruvchi). Bu ehtiyojlarga mo'ljallangan vositalar — xabar broker'lari (message broker): alohida server, ko'p tilli klientlar, standart protokollar. Ularni ishlatishdan oldin bilish kerak — qaysi muammoga qaysi biri.

2. RabbitMQ

2.1 Tushunchalar

RabbitMQ (AMQP 0-9-1 protokoli) da producer xabarni to'g'ridan navbatga emas, exchange ga yuboradi. Exchange binding qoidalariga qarab uni bir yoki bir nechta navbatga tarqatadi. Consumer navbatdan oladi va ack qiladi.

flowchart LR
  P[Producer] -->|routing key eslatma| X{{vazifalar - direct exchange}}
  X -->|binding eslatma| Q[(eslatmalar - quorum queue)]
  Q -->|prefetch 100| C[Consumer]
  C -->|ack| Q
  C -->|nack requeue=false| Q
  Q -->|dead letter| DLX{{vazifalar.dlx - fanout}}
  DLX --> DLQ[(eslatmalar.dlq)]
Tushuncha Ma'nosi
Exchange xabarni qabul qilib, navbatlarga tarqatuvchi
direct routing key aynan mos navbatga
topic naqsh bo'yicha: vazifa.*.muddat, vazifa.#
fanout bog'langan hamma navbatga
Binding "exchange X dan kalit K bilan kelganini navbat Q ga" qoidasi
ack / nack "bajardim" / "bajarolmadim" (requeue — navbatga qaytarilsinmi)
Prefetch consumer'ga ack'siz bir vaqtda nechta xabar beriladi
DLX dead letter exchange — rad etilgan yoki limitdan oshgan xabarlar boradigan joy
Quorum queue bir nechta tugunga nusxalanadigan, ishonchli navbat turi

Prefetch — BullMQ'dagi concurrency'ning o'xshashi: prefetch(100) — consumer'da bir vaqtda 100 tadan ortiq tasdiqlanmagan xabar bo'lmaydi. Prefetchsiz RabbitMQ navbatdagi hamma xabarni bitta consumer'ga to'kib yuborishi mumkin.

2.2 Kanon tajribasi

Kanon RabbitMQ 4.4.0 (rabbitmq:4-alpine, Docker) va amqplib 2.2.0 bilan direct exchange, quorum queue va DLX qurdi (formatlab, qisqartirib):

js
import amqp from "amqplib";

const c = await amqp.connect("amqp://guest:guest@127.0.0.1:4472");
const ch = await c.createChannel();
await ch.assertExchange("vazifalar", "direct", { durable: true });
await ch.assertExchange("vazifalar.dlx", "fanout", { durable: true });
await ch.assertQueue("eslatmalar.dlq", { durable: true });
await ch.bindQueue("eslatmalar.dlq", "vazifalar.dlx", "");
await ch.assertQueue("eslatmalar", {
  durable: true,
  arguments: {
    "x-dead-letter-exchange": "vazifalar.dlx",
    "x-queue-type": "quorum",
    "x-delivery-limit": 2,
  },
});
await ch.bindQueue("eslatmalar", "vazifalar", "eslatma");

for (let i = 0; i < N; i++) {
  ch.publish("vazifalar", "eslatma",
    Buffer.from(JSON.stringify({ i })), { persistent: true });
}

const ish = await c.createChannel();
await ish.prefetch(100);
await ish.consume("eslatmalar", (m) => {
  // ... i === 7 bo'lsa nack, qolganlari ack
  ish.ack(m);
});

guest:guest — konteynerning sukut foydalanuvchisi, faqat lokal; production'da — o'z foydalanuvchingiz va parol muhit o'zgaruvchisidan. Natijada ikkinchi qatorga alohida qarang: 7-xabar necha marta qaytdi va qachon DLQ'ga tushdi:

text
10000 xabar (persistent, quorum queue): 3187 ms; ack 9999
i=7: nack(requeue=true) bilan 135 marta qaytdi — x-delivery-limit 2 ga QARAMAY DLQ'ga o'tmadi (RabbitMQ 4.4 kuzatuvi); nack(requeue=false) dan keyin DLQ'da: 1

2.3 Kutilmagan qoida: nack limitga hisoblanmaydi

Nima bo'ldi? 10 000 xabardan 9 999 tasi tasdiqlandi, bittasi (7-xabar) "issiq sikl"da aylanib qoldi. Kanon x-delivery-limit: 2 qo'ydi: "xabar 2 martadan ortiq qayta yetkazilsa — DLQ'ga". Keyin 7-xabarni nack(requeue=true) bilan 135 marta qaytardi. Xabar DLQ'ga o'tmadi — har safar yana navbatga keldi. Faqat nack(requeue=false) dan keyin DLQ'da paydo bo'ldi.

Kanon buni "RabbitMQ 4.4 kuzatuvi" deb yozdi. Biz rasmiy hujjatni tekshirdik (rabbitmq.com, Quorum Queues sahifasi, 2026-10-28): 4.3 versiyasidan boshlab limit delivery-count ga asoslanadi va u faqat haqiqiy muvaffaqiyatsizlikda oshadi. Hujjat jadvaliga ko'ra basic.nack delivery-count ni oshirmaydi va limitga hisoblanmaydi, basic.reject esa oshiradi. Ya'ni bu xato emas — hujjatlashtirilgan xatti-harakat. 4.0 dan boshlab quorum queue'larda sukut limit — 20.

Amaliy xulosa:

  • "Zaharli" xabarni cheksiz qaytarmaslik uchun nack(requeue=true) ga tayanmang. O'zingiz urinishlarni sanang (sarlavhada yoki bazada) va chegaradan keyin nack(m, false, false) — DLQ'ga.
  • Broker xatti-harakati versiyalar orasida o'zgaradi. Eski maqoladagi "delivery-limit nack'ni ham sanaydi" gapi bugun noto'g'ri bo'lishi mumkin — versiyangiz hujjatini o'qing va o'zingiz o'lchang.

Tekshirib ko'ring: Nega nack(requeue=true) bilan 135 marta qaytgan xabar consumer uchun xavfli, hatto DLQ bo'lmasa ham?

Javob

Xabar darhol navbatga qaytadi va darhol yana keladi — consumer CPU'si va tarmoq shu bitta xabarga sarflanadi ("issiq sikl"). Boshqa xabarlar sekinlashadi, loglar to'ladi. BullMQ'dagi backoff kabi kutish yo'q. Shuning uchun qayta urinish kerak bo'lsa — kechiktiruvchi navbat (TTL + DLX) yoki urinishlarni sanash.

3. NATS JetStream

NATS — yengil xabar tizimi (Go'da yozilgan, bitta kichik binar). JetStream — uning saqlovchi qatlami: stream xabarlarni diskda saqlaydi, consumer ulardan o'qiydi, ack kutadi. Kanon NATS 2.15.1 va @nats-io/* 3.4.0 bilan sinadi (formatlab):

js
import { connect } from "@nats-io/transport-node";
import { jetstream, jetstreamManager, AckPolicy }
  from "@nats-io/jetstream";

const nc = await connect({ servers: "127.0.0.1:4422" });
const jsm = await jetstreamManager(nc);
await jsm.streams.add({ name: "VAZIFA", subjects: ["vazifa.>"] });
await jsm.consumers.add("VAZIFA", {
  durable_name: "eslatmachi",
  ack_policy: AckPolicy.Explicit,
  ack_wait: 1_000_000_000, // nanosoniya — 1 s
  max_deliver: 3,
});

Chiqishda vaqtdan tashqari i=7 yetkazish raqamlari ga qarang:

text
10000 xabar: publish 860 ms, jami 1875 ms; i=7 yetkazish raqamlari: [1,2] (ack_wait 1 s dan keyin qayta keldi)

7-xabar birinchi marta ack qilinmadi (worker o'ldi taqlidi) — ack_wait (1 s) dan keyin u ikkinchi marta keldi, deliveryCount 2. Bu BullMQ'dagi stalled va SQS'dagi visibility timeout'ning o'xshashi: tasdiq kelmasa — qayta. E'tibor bering: ack_wait nanosoniyada — 1_000 yozsangiz, 1 mikrosoniya bo'ladi.

4. pg-boss: Postgres'dagi navbat

SKIP LOCKED darsida yozgan navbatimizning tayyor, to'liq versiyasi — pg-boss. Redis kerak emas: navbat — Postgres sxemasidagi jadvallar. Kanon pg-boss 12.38.0 bilan (formatlab):

js
import { PgBoss } from "pg-boss";

const boss = new PgBoss({ connectionString, schema: "pgboss_t49" });
await boss.start();
await boss.createQueue("eslatma");
await boss.insert("eslatma",
  Array.from({ length: N }, (_x, i) => ({ data: { i } })));
await boss.work("eslatma",
  { batchSize: 200, pollingIntervalSeconds: 0.5 },
  async (ishlar) => { olindi += ishlar.length; });

await boss.createQueue("bir-marta", { policy: "short" });
const a = await boss.send("bir-marta", { x: 1 },
  { singletonKey: "hisobot-2026-10-27" });
const b = await boss.send("bir-marta", { x: 1 },
  { singletonKey: "hisobot-2026-10-27" });
text
10000 ish: insert 405 ms, ishlash jami 25338 ms (polling 0.5 s, batch 200)
policy short + singletonKey bilan ikki send: id / null (navbatda bor — rad)
  • Kiritish tez — 405 ms (bitta INSERT bilan ko'p qator).
  • Ishlash sekin — 25.3 s. Sabab — polling: worker har 0.5 s da bir marta bazaga "ish bormi" deb so'raydi va bir martada 200 tadan oladi. 10 000 / 200 = 50 so'rov × 0.5 s ≈ 25 s. Bu pg-boss'ning sekinligi emas, sozlamaning natijasi — lekin Postgres navbati tabiatan "so'rab turadi" (BullMQ esa Redis'da bloklovchi kutish bilan ish kelishini darhol biladi).
  • singletonKey — ikkinchi send null qaytardi: shu kalitli ish navbatda bor. BullMQ'dagi jobId ning o'xshashi.

pg-boss'ning asosiy afzalligi — ish ma'lumot bilan bitta tranzaksiyada qo'shilishi mumkin (bitta baza). Bu Transactional outbox muammosini boshqa yo'l bilan hal qiladi.

Yana bir yangilik: kanon ishlatgan bullmq 6.3.19 paketi ichida PostgreSQL backend kodi bor (createPostgresBackend, pg — ixtiyoriy peer bog'liqlik) — biz paket fayllarida ko'rdik. Kanon uni sinamagan; ishlatishdan oldin BullMQ hujjatidagi holatini tekshiring.

5. Kafka — sinalmagan

5.1 G'oya

Kafka — navbat emas, taqsimlangan jurnal (log). Xabar o'qilgandan keyin o'chirilmaydi, sozlangan muddatgacha (masalan 7 kun) saqlanadi. Har consumer jurnalda qayerga kelganini — offset ni — o'zi eslab qoladi. Yangi servis jurnalni boshidan o'qishi mumkin (replay).

  • Topic — jurnal nomi (vazifa-hodisalari).
  • Partition — topic bo'laklari; parallellik birligi. Bir kalitli xabarlar doim bitta partition'ga tushadi — ular orasida tartib saqlanadi.
  • Consumer group — bir guruhdagi consumer'lar partition'larni bo'lishib oladi; har partition — guruhdagi bitta consumer'ga.
js
// Kafka g'oyasi: topic → partition'lar;
// kalit bir xil — partition bir xil.
// (Kafka murmur2 xeshini ishlatadi; bu yerda o'quv uchun oddiy xesh.)
const xesh = (s) => [...s]
  .reduce((h, c) => (h * 31 + c.charCodeAt(0)) >>> 0, 7);
const PARTITION = 3;
const log = Array.from({ length: PARTITION }, () => []);

const hodisalar = [
  ["vazifa-7", "yaratildi"], ["vazifa-8", "yaratildi"],
  ["vazifa-7", "tahrirlandi"], ["vazifa-9", "yaratildi"],
  ["vazifa-7", "bajarildi"], ["vazifa-8", "o'chirildi"],
];
for (const [kalit, tur] of hodisalar) {
  const p = xesh(kalit) % PARTITION;
  log[p].push(`${kalit}:${tur}`); // offset = massivdagi indeks
}
log.forEach((l, p) => console.log(`partition ${p}:`, l.join(", ")));

// Consumer group: har partition — guruhdagi bitta consumer'ga.
const guruh = ["c1", "c2"];
log.forEach((_, p) => {
  console.log(`p${p} → ${guruh[p % guruh.length]}`);
});

Konsolda:

text
partition 0: vazifa-9:yaratildi
partition 1: vazifa-7:yaratildi, vazifa-7:tahrirlandi, vazifa-7:bajarildi
partition 2: vazifa-8:yaratildi, vazifa-8:o'chirildi
p0 → c1
p1 → c2
p2 → c1

vazifa-7 ning uchala hodisasi bitta partition'da, tartib bilan — consumer "bajarildi" ni "yaratildi" dan oldin ko'rmaydi. Lekin partition'lar orasida tartib yo'q. Guruhda 2 consumer, partition 3 ta: c1 ikkitasini oladi. Consumer 4 ta bo'lsa, bittasi bo'sh turadi — parallellik partition sonidan oshmaydi.

5.2 Nega sinalmagan va qachon kerak

Kanon Kafka'ni sinamadi: Kafka Java mashinasida (JVM) ishlaydi, uning image'i ~400+ MB disk va katta RAM talab qiladi, kanon mashinasida joy tor edi. Yuqoridagi model — g'oya, Kafka API emas. Kafka kerak bo'ladigan joylar: sekundiga yuz minglab hodisa, hodisalarni qayta o'qish (replay), bir hodisani ko'p mustaqil servis o'qishi, oqimli analitika. Kichik va o'rta Node loyiha uchun bu ortiqcha murakkablik. Node klientlari (KafkaJS, @confluentinc/kafka-javascript) holatini hujjatdan tekshiring. Nazariya — Event streaming nazariyasi darsida.

6. SQS — sinalmagan

Amazon SQS — AWS'ning boshqariladigan navbati: server o'rnatmaysiz, so'rov uchun to'laysiz. Asosiy tushuncha — visibility timeout: consumer xabarni olganda u o'chirilmaydi, faqat ma'lum vaqtga boshqalardan yashiriladi. Consumer DeleteMessage qilmasa, vaqt tugagach xabar yana ko'rinadi.

js
// SQS g'oyasi: xabar o'chirilmaydi, faqat "ko'rinmas" bo'ladi.
// O'chirish (DeleteMessage) bo'lmasa — timeout'dan keyin
// yana ko'rinadi.
const VISIBILITY = 30; // soniya
const xabar = { id: "m-1", korinmasGacha: 0, qabul: 0 };

function receive(soat) {
  if (soat < xabar.korinmasGacha) return null;
  xabar.korinmasGacha = soat + VISIBILITY;
  xabar.qabul++;
  const n = xabar.qabul;
  return `t=${soat}s: ${xabar.id} olindi (ReceiveCount ${n})`;
}

for (const soat of [0, 10, 29, 30, 45, 61]) {
  console.log(receive(soat) ?? `t=${soat}s: bo'sh (xabar ko'rinmas)`);
}

Konsolda:

text
t=0s: m-1 olindi (ReceiveCount 1)
t=10s: bo'sh (xabar ko'rinmas)
t=29s: bo'sh (xabar ko'rinmas)
t=30s: m-1 olindi (ReceiveCount 2)
t=45s: bo'sh (xabar ko'rinmas)
t=61s: m-1 olindi (ReceiveCount 3)

Ish 30 soniyadan uzoq davom etsa, xabar ikkinchi consumer'ga ham boradi — BullMQ'dagi lockDuration bilan aynan bir xil muammo. SQS'da DLQ "redrive policy" orqali: maxReceiveCount dan oshgan xabar DLQ'ga. Standard navbat at-least-once va tartibni kafolatlamaydi; FIFO navbat tartib va dedup (takroriy xabarni tashlab yuborish) beradi, lekin sekinroq (hujjat bo'yicha). Kanon sinamadi — AWS akkaunt kerak.

7. Qaysi birini tanlash

Kanon o'lchovlari (10 000 xabar, bitta mashina, Docker, bir marta):

Broker 10 000 xabar Izoh
NATS JetStream 2.15 1 875 ms (publish 860) eng tezi
RabbitMQ 4.4 (quorum, persistent) 3 187 ms persistent + quorum — ishonchlilik narxi
pg-boss 12.38 25 338 ms (insert 405) polling 0.5 s × batch 200
Kafka, SQS — sinalmagan

Tezlik tanlovning yagona mezoni emas:

Ehtiyoj Tanlov
Node ilova, Redis bor, fon vazifalari BullMQ
Redis yo'q, kichik/o'rta yuk, ish ma'lumot bilan bitta tranzaksiyada pg-boss
Ko'p tilli servislar, murakkab marshrutlash RabbitMQ
Yengil, tez, oqim va so'rov-javob NATS JetStream
Juda katta hajm, replay, ko'p mustaqil o'quvchi Kafka (sinalmagan)
AWS'da, server boshqarmasdan SQS (sinalmagan)

Kanon tanlovi o'zgarmadi: vazifalar uchun BullMQ — Redis 27-qismdan bor, bitta Node ilova. Yangi broker — yangi server, monitoring, zaxira nusxa va bilim. Uni ehtiyoj paydo bo'lganda qo'shing, "keyinroq kerak bo'lar" deb emas.

8. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi

Bu darsda loyihaga kod qo'shilmaydi. Kanon uchta brokerni o'lchab, BullMQ tanlovini tasdiqladi va RabbitMQ'ning nack qoidasini topdi. O'zingiz sinab ko'ring: Docker o'rnatilgan bo'lsa, docker run -d --name rabbit -p 5672:5672 rabbitmq:4-alpine va npm i amqplib@2.2.0 bilan darsdagi direct exchange'ni quring. Keyin nack(m, false, true) o'rniga ch.reject(m, true) ni sinab, xabar x-delivery-limit dan keyin DLQ'ga o'tishini tekshiring (hujjatga ko'ra o'tishi kerak; kanon buni sinamagan).

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Bitta xabar cheksiz qaytyapti nack(requeue=true) limitga hisoblanmaydi (RabbitMQ ≥ 4.3) urinishlarni sanash, requeue=false → DLX
Consumer xotirasi to'ldi prefetch yo'q prefetch(N)
Broker qayta yonganda xabarlar yo'q durable/persistent emas durable: true, persistent: true
JetStream xabarni qayta yuboryapti ack_wait juda kichik (nanosoniya!) birlikni tekshiring
pg-boss sekin polling oralig'i pollingIntervalSeconds, batchSize
Kafka'da tartib buzildi turli kalit — turli partition tartib kerak hodisalarga bir xil kalit
SQS'da xabar ikki marta ish visibility timeout'dan uzoq timeout'ni oshirish yoki uzaytirish, idempotentlik

10. Mashqlar

Mashqlar kurs/mashqlar/29/49-brokerlar/ da.

1-mashq (oson): bo'shliqlarni to'ldiring

  • RabbitMQ'da xabarni navbatlarga tarqatuvchi —
  • Consumer'ga ack'siz bir vaqtda beriladigan xabarlar soni —
  • Kafka'da consumer jurnalda qayerga kelgani —
  • SQS'da olingan xabarni vaqtincha yashiradigan sozlama — visibility
  • Redis'siz, Postgres'dagi Node navbat kutubxonasi —
Yechim
  • exchange — direct, topic, fanout.
  • prefetch — BullMQ concurrency'ning o'xshashi.
  • offset — consumer o'zi saqlaydi, replay mumkin.
  • timeout — o'chirilmasa, xabar qayta ko'rinadi.
  • pg-boss — polling, singletonKey.

2-mashq (o'rta): kanon raqamlaridan tezlik

Kanon o'lchovlari 10 000 xabar uchun: RabbitMQ 3187 ms, NATS 1875 ms, pg-boss 25338 ms. Har broker uchun soniyasiga xabar sonini hisoblang, kamayish tartibida chiqaring va eng tezi eng sekinidan necha baravar tez ekanini toping.

Yechim
js
const N = 10_000;
const olchov = [
  { nom: "RabbitMQ (quorum)", ms: 3187 },
  { nom: "NATS JetStream", ms: 1875 },
  { nom: "pg-boss", ms: 25338 },
];

const tezlik = olchov
  .map((o) => ({ ...o, sekundiga: Math.round(N / (o.ms / 1000)) }))
  .sort((a, b) => b.sekundiga - a.sekundiga);

for (const o of tezlik) {
  const son = String(o.sekundiga).padStart(5);
  console.log(`${o.nom.padEnd(18)} ${son} xabar/s`);
}
const engTez = tezlik[0].sekundiga;
const engSekin = tezlik.at(-1).sekundiga;
console.log("farq:", Math.round(engTez / engSekin), "baravar");

Konsolda:

text
NATS JetStream      5333 xabar/s
RabbitMQ (quorum)   3138 xabar/s
pg-boss              395 xabar/s
farq: 14 baravar

14 baravar — lekin bu "pg-boss yomon" degani emas. Kanon sozlamasida pg-boss 0.5 s da bir marta so'radi; kuniga 10 000 ta email yuboradigan ilova uchun 395 xabar/s ham ortiqcha. Bir o'lchov, bitta mashina, Docker ichida — production raqami emas. Tanlovda tezlikdan oldin: "Redis bormi?", "ish ma'lumot bilan bitta tranzaksiyada kerakmi?", "kim ishlatadi va kuzatadi?"

3-mashq (qiyin): RabbitMQ topic exchange

Topic exchange'da routing key nuqta bilan ajratilgan so'zlar (vazifa.7.muddat), binding naqshida * — aynan bitta so'z, # — nol yoki ko'p so'z. mos(pattern, kalit) funksiyasini yozing va uchta binding (eslatmalar: vazifa.*.muddat, audit: vazifa.#, email: #.email) uchun to'rt kalit qaysi navbatlarga borishini chiqaring.

Yechim
js
// "*" — aynan bitta so'z, "#" — nol yoki ko'p so'z
function mos(pattern, kalit) {
  const p = pattern.split(".");
  const k = kalit.split(".");
  function yur(i, j) {
    if (i === p.length) return j === k.length;
    if (p[i] === "#") {
      for (let n = j; n <= k.length; n++) {
        if (yur(i + 1, n)) return true;
      }
      return false;
    }
    if (j === k.length) return false;
    return (p[i] === "*" || p[i] === k[j]) && yur(i + 1, j + 1);
  }
  return yur(0, 0);
}

const bindinglar = {
  eslatmalar: "vazifa.*.muddat",
  audit: "vazifa.#",
  email: "#.email",
};
const kalitlar = [
  "vazifa.7.muddat", "vazifa.7.izoh.email", "foydalanuvchi.email",
  "vazifa",
];
for (const k of kalitlar) {
  const qayerga = Object.entries(bindinglar)
    .filter(([, p]) => mos(p, k)).map(([q]) => q);
  const matn = qayerga.join(", ") || "(hech qayer)";
  console.log(k.padEnd(20), "→", matn);
}

Konsolda:

text
vazifa.7.muddat      → eslatmalar, audit
vazifa.7.izoh.email  → audit, email
foydalanuvchi.email  → email
vazifa               → audit

Oxirgisiga e'tibor bering: vazifa.# yolg'iz vazifa ga ham mos, chunki # nol so'zni ham qabul qiladi. vazifa.*.muddat esa vazifa.7.izoh.muddat ga mos kelmaydi — * faqat bitta so'z. Bitta xabar bir nechta navbatga ketdi — bu fanout'siz ham "bir hodisa — ko'p iste'molchi". RabbitMQ'ning haqiqiy algoritmi trie (so'zlar daraxti) bilan ishlaydi; rekursiv versiya — tushunish uchun.

11. Real ishda

  • RabbitMQ — bank, logistika, ko'p tilli mikroservislar; NestJS mikroservis transporti sifatida ham (Mikroservis asoslari NestJS'da).
  • Kafka — katta kompaniyalarda hodisalar "magistrali", analitika va CDC (CDC va ma'lumot sinxronlash).
  • SQS/SNS, Google Pub/Sub, Azure Service Bus — bulutdagi boshqariladigan muqobillar.
  • pg-boss — "Redis qo'shmaymiz" degan kichik jamoalar va bitta Postgres'li loyihalar.
  • Intervyuda: "RabbitMQ va Kafka farqi?", "Visibility timeout nima?", "Partition va consumer group qanday ishlaydi?", "Navbat va event stream farqi?"

Xulosa

  • BullMQ — Node ichidagi ish navbati; broker'lar — tillar va servislar orasidagi xabar almashish.
  • RabbitMQ: exchange → binding → queue, ack/nack, prefetch, DLX; 10 000 xabar 3.2 s (quorum, persistent).
  • RabbitMQ ≥ 4.3: nack(requeue=true) x-delivery-limit ga hisoblanmaydi (kanonda 135 marta qaytdi; hujjat tasdiqlaydi); requeue=false → DLQ.
  • NATS JetStream: 1.9 s, ack_wait (nanosoniya) dan keyin qayta yetkazish.
  • pg-boss: Redis'siz, singletonKey; polling tufayli 25.3 s.
  • Kafka — jurnal, partition, offset, replay; SQS — visibility timeout; ikkalasi sinalmagan.

Keyingi dars: Transactional outbox naqshi — bazaga yozish va navbatga xabar yuborish ikkalasi birga bo'lishi uchun: kanonda outbox'siz 100 tadan 10 ta hodisa yo'qoldi, outbox bilan — 0.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!