Mundarija (28)
- Bu darsda
- 1. Nega bu kerak?
- 2. Transportlar xaritasi
- 2.1 Jadval va paketlar
- 2.2 Bir xil kod — boshqa transport
- 3. Redis va NATS
- 4. RabbitMQ: ack va durable
- 4.1 Sozlama
- 4.2 Qo'lda ack
- 5. Kafka
- 6. gRPC — sinab ko'rdik
- 6.1 Shartnoma
- 6.2 Server
- 6.3 Mijoz va natija
- 7. Serializer
- 8. Retry
- 9. O'z transportingiz
- 10. JS modelida ack
- 11. Kanon tajribasi: nega loyihaga kirmaydi
- 12. Ko'p uchraydigan xatolar
- 13. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): serializer va deserializer
- 3-mashq (qiyin): takrorni tashlaydigan iste'molchi
- 14. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi
- 15. Real ishda
- Xulosa
- Manbalar
Transport'lar chuqur NestJS'da: Redis, NATS, RabbitMQ, Kafka, gRPC va o'z transportingiz
Qisqacha: Nest mikroservis kodi transportdan deyarli mustaqil:
@MessagePatternvaClientProxybir xil, faqattransportsozlamasi o'zgaradi. Lekin ishonchlilik transportga bog'liq. TCP — to'g'ridan ulanish: xizmat o'chiq bo'lsa, xabar yo'qoladi. Broker'lar (RabbitMQ, Kafka, NATS JetStream) xabarni saqlaydi. RabbitMQ transportida Nest sukut bo'yichanoAck: true— ishchi yiqilsa, xabar yo'qoladi; kafolat uchunnoAck: falseva qo'ldaack. gRPC —.protoshartnoma, deadline va status kodlari bilan. Har transportning npm paketi alohida: masalan Redis transportiioredisni talab qiladi.
Bu darsda
- Nest transportlarini (TCP, Redis, NATS, RabbitMQ, Kafka, MQTT, gRPC) va har biriga kerak paketni bilasiz.
- gRPC transportida
@GrpcMethod, server oqimi,RpcExceptionstatus kodi va deadline'ni ishlatasiz. - RabbitMQ'da
noAck,durableva qo'ldaacknima uchun kerakligini tushuntirasiz. - Serializer bilan xabar formatini boshqarasiz va
retrybilan ulanish xatosini yumshatasiz. - O'z transportingizni (
CustomTransportStrategy) yozib, Nest transportlari ichidan qanday tuzilganini ko'rasiz.
Oldin bilishingiz kerak: Mikroservis asoslari NestJS'da, Boshqa xabar broker'lari, gRPC va Protocol Buffers, BullMQ ishonchlilik.
Oldingi darsda TCP bilan mikroservis yozdik. Shu darsning oxirgi qismida xizmatni o'chirib, gateway nima qaytarishini ko'rishni taklif qilgandik. Bu darsni shu savoldan boshlaymiz.
1. Nega bu kerak?
Hisobot xizmati qayta ishga tushirilyapti — 2 soniya. Shu orada gateway send qildi (biz ishga tushirdik, NestJS 12.2.0):
3) servissiz: connect ECONNREFUSED 127.0.0.1:4595Tarjimasi: "ulanish rad etildi: 127.0.0.1:4595". TCP transportida xabar hech qayerda saqlanmaydi: mijoz to'g'ridan xizmat soketiga yozadi. Xizmat yo'q — xabar yo'q. Hodisa (emit) bo'lsa, hech kim bilmaydi ham.
Brokerli transportda zanjirning o'rtasida uchinchi dastur — broker turadi. Mijoz xabarni brokerga beradi, broker uni navbatda saqlaydi va xizmat tayyor bo'lganda yetkazadi. Boshqa xabar broker'lari darsida RabbitMQ, Kafka va NATS'ni xom kutubxonalar bilan sinagandik. Bu dars — ularning Nest transporti sifatidagi ko'rinishi.
O'xshatish: telefon va pochta qutisi. TCP — telefon qo'ng'irog'i: odam javob bermasa, gap yetmaydi. Broker — pochta qutisi: xat qutida kutadi, egasi qaytganda o'qiydi. Lekin quti ham "xat o'qildi" degan belgini (ack) olmaguncha xatni o'chirmasligi kerak.
2. Transportlar xaritasi
2.1 Jadval va paketlar
Nest @nestjs/microservices paketida hamma transport kodi bor, lekin har birining klient kutubxonasi alohida o'rnatiladi. Ro'yxat — 12.2.0 paketining peerDependencies idan (biz paket ichidan ko'chirdik):
| Transport | Kerakli paket | Xususiyati |
|---|---|---|
TCP |
— | brokersiz, eng sodda |
REDIS |
ioredis |
Pub/Sub: saqlamaydi |
NATS |
@nats-io/transport-node |
tez, queue group; saqlash — JetStream |
RMQ |
amqplib, amqp-connection-manager |
navbat, ack, marshrutlash |
KAFKA |
kafkajs |
jurnal, partition, qayta o'qish |
MQTT |
mqtt |
IoT qurilmalari |
GRPC |
@grpc/grpc-js, @grpc/proto-loader |
.proto shartnoma, HTTP/2 |
Ikki diqqat qiladigan joy:
- NATS — Nest 12 yangi
@nats-io/transport-nodepaketini ishlatadi (nats.js 3-avlodi). Eski maqolalardaginatspaketi 12 da ishlamaydi. - Redis —
ioredis.vazifalar-apiesa 27-qismdan beri yagona Redis klienti sifatida node-redis ishlatadi (29-qism qarori). Redis transportini qo'shish — ikkinchi Redis klienti, ikkinchi ulanish sozlamasi. Bu ham kanon uchun "yo'q" deyish sabablaridan biri.
2.2 Bir xil kod — boshqa transport
Controller kodi o'zgarmaydi. Faqat createMicroservice va ClientsModule sozlamasi:
// TCP
{ transport: Transport.TCP,
options: { host: "127.0.0.1", port: 4591 } }
// Redis
{ transport: Transport.REDIS,
options: { host: "localhost", port: 6379 } }
// NATS
{ transport: Transport.NATS,
options: { servers: ["nats://localhost:4222"], queue: "hisobot" } }Bu Nest mikroservislarining eng kuchli tomoni: bugun TCP bilan boshlab, ertaga RabbitMQ'ga o'tish — sozlama o'zgarishi. Lekin "kod o'zgarmaydi" — "xulq o'zgarmaydi" degani emas. Keyingi bo'limlar shu farq haqida.
Diqqat: Redis, NATS, RabbitMQ va Kafka transportlarini biz sinamadik: ular Docker'da broker talab qiladi. Bu bo'limlardagi sozlamalar — Nest 12.2.0 paketining tur fayllaridan (
microservice-configuration.interface.d.ts) va manba kodidan, natija chiqishlari yo'q. gRPC, TCP va o'z transportimiz — ishga tushirilgan.
3. Redis va NATS
Redis transporti — Pub/Sub ustida (Pub/Sub va Redis Streams darsida ko'rgansiz). Xabar faqat shu daqiqada obuna bo'lganlarga yetadi: xizmat o'chiq bo'lsa — xabar yo'qoladi, TCP'dagidek. Afzalligi — xizmatlar bir-birining manzilini bilmaydi, hammasi Redis'ga ulanadi; bir nechta nusxa bo'lsa, hodisa hammasiga boradi. wildcards: true — Redis PSUBSCRIBE naqshlari (vazifa.*).
NATS transporti — tez xabar tizimi. Muhim sozlama — queue (queue group): bir xil queue dagi xizmat nusxalaridan har xabarni bittasi oladi — yuk taqsimlanadi. queue siz — har nusxa oladi. Oddiy NATS ham xabarni saqlamaydi; saqlash va qayta yetkazish — JetStream. Nest 12.2.0 ning NATS transporti manba kodida JetStream yo'q (biz qidirib ko'rdik) — u oddiy NATS ustida ishlaydi.
Ikkalasi ham "tez, lekin kafolatsiz" toifasida. Kafolat kerak bo'lsa — RabbitMQ yoki Kafka.
4. RabbitMQ: ack va durable
4.1 Sozlama
{
transport: Transport.RMQ,
options: {
urls: ["amqp://localhost:5672"],
queue: "hisobot",
queueOptions: { durable: true },
noAck: false,
prefetchCount: 10,
},
}Uch sozlama ishonchlilikni hal qiladi:
queueOptions: { durable: true }— navbat broker qayta ishga tushganda ham saqlanadi (Boshqa xabar broker'lari). Usiz — RabbitMQ qayta yonganda navbat va undagi xabarlar yo'qoladi.noAck— xabar qachon "bajarildi" deb hisoblanadi. Nest manbasida sukut qiymati:RMQ_DEFAULT_NOACK = true(biz 12.2.0 paketiningconstants.jsfaylida ko'rdik). Rasmiy hujjatning RabbitMQ sahifasi ham shuni yozadi: sukut —true,falseqo'lda tasdiqlash rejimini yoqadi. Ya'ni xabar ishchiga berilishi bilan broker uni o'chiradi. Handler xato bersa yoki jarayon yiqilsa — xabar yo'q.prefetchCount— bitta ishchi bir vaqtda nechta tasdiqlanmagan xabar oladi. Kichik qiymat — yuk nusxalar orasida tekis taqsimlanadi.
4.2 Qo'lda ack
noAck: false bo'lsa, handler xabarni o'zi tasdiqlashi shart:
import {
Ctx, EventPattern, Payload, RmqContext,
} from "@nestjs/microservices";
@EventPattern("vazifa.bajarildi")
async bajarildi(@Payload() d: Vazifa, @Ctx() ctx: RmqContext) {
const kanal = ctx.getChannelRef();
const xabar = ctx.getMessage();
await this.email.yubor(d); // ish
kanal.ack(xabar); // faqat muvaffaqiyatdan keyin
}ack chaqirilmasa — xabar "tasdiqlanmagan" bo'lib qoladi va ishchi ulanishi uzilganda broker uni boshqa ishchiga qayta beradi. Xato bo'lsa, kanal.nack(xabar, false, true) — qayta navbatga, yoki false — tashlash (dead letter'ga). Nest manbasida yana bir xulq bor: noAck: false bo'lganda handler'i yo'q pattern kelsa, Nest xabarni o'zi nack qiladi (qayta navbatga qo'ymasdan) va ogohlantirish yozadi.
Kafolat bepul emas. "Kamida bir marta" (at-least-once) yetkazishda bir xabar ikki marta kelishi mumkin: ishchi ishni bajardi, ack dan oldin yiqildi — broker xabarni qayta beradi. Shuning uchun handler idempotent bo'lishi kerak (3-mashq) — BullMQ ishonchlilik darsidagi qoida.
Tekshirib ko'ring:
noAck: falseqilindi, lekin handler'dakanal.ack(xabar)yozish unutildi. Nima bo'ladi?
Javob
Xabarlar bajariladi, lekin broker ularni "tasdiqlanmagan" deb ushlab turadi. prefetchCount (masalan 10) to'lgach, broker bu ishchiga yangi xabar bermaydi — ishchi "qotib" qoladi. Ishchi qayta ishga tushsa, tasdiqlanmagan xabarlarning hammasi qayta yetkaziladi va ikkinchi marta bajariladi. Ya'ni ack'ni unutish — ham to'xtash, ham takror.
5. Kafka
{
transport: Transport.KAFKA,
options: {
client: { clientId: "hisobot", brokers: ["localhost:9092"] },
consumer: { groupId: "hisobot-iste'molchi" },
},
}Kafka — xabarlarni o'chirmaydigan jurnal. groupId — iste'molchi guruhi: guruh ichida har partition'ni bitta nusxa o'qiydi. Har xabar handler'ga KafkaContext bilan keladi: ctx.getTopic(), ctx.getPartition(), ctx.getMessage().offset — qaysi bo'lak, qaysi o'rin.
Kafka transportida pattern — topic nomi. Nest manbasida Kafka serveri pattern sifatida RegExp ni ham qabul qiladi: @EventPattern(/^vazifa\./) — bir nechta topic'ni bitta handler bilan tinglash (biz faqat kodda ko'rdik, sinamadik). So'rov-javob (send) Kafka'da ham ishlaydi, lekin javob uchun alohida "reply" topic kerak va mijoz subscribeToResponseOf(pattern) ni oldindan chaqirishi shart — Kafka tabiatan hodisa uchun.
6. gRPC — sinab ko'rdik
6.1 Shartnoma
gRPC brokersiz, shuning uchun Docker'siz ishga tushirdik. gRPC darsidagi kabi, avval .proto fayl:
syntax = "proto3";
package hisobot;
service HisobotXizmati {
rpc Statistika (StatSorovi) returns (Stat);
rpc Oqim (StatSorovi) returns (stream Stat);
rpc Sekin (StatSorovi) returns (Stat);
}
message StatSorovi {
int32 egasi = 1;
}
message Stat {
int32 egasi = 1;
int32 jami = 2;
int32 bajarilgan = 3;
}6.2 Server
import { GrpcMethod, RpcException } from "@nestjs/microservices";
import { status } from "@grpc/grpc-js";
@Controller()
class HisobotGrpc {
@GrpcMethod("HisobotXizmati", "Statistika")
statistika(d: { egasi: number }) {
if (d.egasi === 99) {
throw new RpcException({
code: status.NOT_FOUND, message: "Egasi 99 topilmadi",
});
}
return { egasi: d.egasi, jami: 12, bajarilgan: 5 };
}
@GrpcMethod("HisobotXizmati", "Oqim")
oqim(d: { egasi: number }): Observable<object> {
return interval(10).pipe(
take(3),
map((i) => ({ egasi: d.egasi, jami: i + 1, bajarilgan: 0 })),
);
}
}
const srv = await NestFactory
.createMicroservice<MicroserviceOptions>(GrpcModule, {
transport: Transport.GRPC,
options: {
package: "hisobot", protoPath: PROTO, url: "127.0.0.1:4593",
},
});@GrpcMethod("Xizmat", "Metod") — .proto dagi metodga bog'lanadi; pattern o'rnida xizmat va metod nomi. Observable qaytargan metod — server oqimi (server streaming): har next — bitta Stat xabari.
6.3 Mijoz va natija
const mijoz = ClientProxyFactory.create({
transport: Transport.GRPC,
options: {
package: "hisobot", protoPath: PROTO, url: "127.0.0.1:4593",
},
}) as unknown as ClientGrpc;
const x = mijoz.getService<Xizmat>("HisobotXizmati");
await firstValueFrom(x.statistika({ egasi: 7 }));
await firstValueFrom(x.oqim({ egasi: 7 }).pipe(toArray()));
await firstValueFrom(x.statistika({ egasi: 99 }));
const muddat = { deadline: Date.now() + 100 };
await firstValueFrom(x.sekin({ egasi: 1 }, new Metadata(), muddat));getService — .proto dan yasalgan klient: har rpc metodi — Observable qaytaradigan funksiya (nomlar kichik harf bilan: Statistika → statistika). Natija (biz ishga tushirdik, @grpc/grpc-js 1.14.6):
1) gRPC unary: {"egasi":7,"jami":12,"bajarilgan":5}
server stream: [{"egasi":7,"jami":1,"bajarilgan":0},{"egasi":7,"jami":2,"bajarilgan":0},{"egasi":7,"jami":3,"bajarilgan":0}]
RpcException → code 5, details: Egasi 99 topilmadi
deadline 100 ms → code 4, details: Deadline exceeded after 0.100s,remote_addr=127.0.0.1:4593
sekin, deadline yo'q: {"egasi":1,"jami":0,"bajarilgan":0}Uch narsaga qarang:
RpcException+code— gRPC'ning standart status kodi mijozga yetdi: 5 =NOT_FOUND. TCP'daRpcExceptionobyekti aynan borardi; gRPC'da ucodevadetailsga aylanadi — gRPC protokoli faqat shuni tashiydi.- Deadline — mijoz "100 ms ichida javob ber" dedi, xizmat 300 ms uxladi: kod 4 =
DEADLINE_EXCEEDED. Bu oldingi darsdagi rxjstimeoutdan yaxshiroq: deadline protokolning o'zida, server ham uni ko'radi va kerak bo'lsa ishni to'xtatadi. Uchinchi argument — gRPC'ning chaqiruv sozlamasi, Nest uni@grpc/grpc-jsga o'zgarishsiz uzatadi. - Server oqimi — uch xabar,
toArray()ularni bitta massivga yig'di.
Tekshirib ko'ring: Gateway gRPC chaqiruviga 2 soniya deadline berdi. Xizmat 1.5 soniyada o'zi boshqa xizmatni chaqiradi. Ikkinchi chaqiruvga qancha deadline berish to'g'ri?
Javob
Qolgan vaqtdan kam: 2 − 1.5 = 0.5 soniyadan kichik. Aks holda gateway allaqachon voz kechgan javob uchun ikkinchi xizmat ishlab yotadi. gRPC'da deadline so'rov bilan birga (grpc-timeout sarlavhasida) keladi va server uni ko'radi; keyingi chaqiruvga qolgan vaqtni o'zingiz uzatasiz. Har bo'g'in o'z ulushini oladi — buni "timeout budjeti" deyishadi (Idempotentlik va timeout budjeti arxitektura darsida).
7. Serializer
Har transport xabarni tarmoqqa chiqarishdan oldin seriyalaydi. TCP va Redis — JSON. Sukut seriyalash nimani yo'qotadi:
const tana = {
vaqt: new Date("2026-11-02T00:00:00Z"),
royxat: new Map([[1, "a"]]),
};
// xizmat tanani o'zgarishsiz qaytaradi
await firstValueFrom(oddiy.send("aks", tana));2) oddiy: {"vaqt":"2026-11-02T00:00:00.000Z","royxat":{}}Date — satr, Map — bo'sh obyekt. Ma'lumot jim yo'qoldi.
Serializer — mijoz yoki xizmatning xabarni tarmoq formatiga aylantiradigan funksiyasi; deserializer — teskarisi. Nest ularni sozlamada qabul qiladi:
const versiyali = ClientProxyFactory.create({
transport: Transport.TCP,
options: {
host: "127.0.0.1", port: 4594,
serializer: {
serialize: (p: ReadPacket) => ({
...p,
data: { v: 2, manba: "gateway", ...(p.data as object) },
}),
},
},
}); serializer bilan: {"v":2,"manba":"gateway","matn":"salom"}Serializer har xabarga versiya va manbani qo'shdi — xizmat uni qaytardi. Amalda serializer uch ish uchun ishlatiladi: format konverti (versiya, kim yubordi, kuzatuv id'si), boshqa tilda yozilgan xizmat bilan moslashish (masalan Go xizmati Nest'ning {pattern, data, id} formatini bilmaydi) va boshqa format (Protobuf, MessagePack).
8. Retry
Xizmat qayta ishga tushayotganda bir-ikki urinish xatosi — normal. rxjs retry operatori Observable'ni xatoda qayta obuna qiladi. Demoda xizmat 350 ms dan keyin ishga tushdi, mijoz esa darhol so'radi:
const natija = await firstValueFrom(
new Observable((o) => {
urinish++;
return mijoz.send("aks", { urinish }).subscribe(o);
}).pipe(retry({ count: 5, delay: 200 })),
); retry({count:5, delay:200}) → {"urinish":3} (3-urinishda)Birinchi ikki urinish ECONNREFUSED oldi, uchinchisi (≈400 ms) ishga tushgan xizmatga yetdi. new Observable o'rami nega kerak? send sovuq, retry unga qayta obuna bo'ladi — o'ram urinishlarni sanash uchun. Oddiy holatda mijoz.send(...).pipe(retry(...)) yetarli.
Retry'ning uchta qoidasi:
- Faqat idempotent amalda. "Statistikani o'qi" — xavfsiz. "Pul o'tkaz" — timeout'dan keyin qayta yuborsangiz, ikki marta o'tishi mumkin (Idempotency-Key).
- Kutish bilan.
delaysiz retry xizmatni urinishlar bilan "bo'g'adi". Eksponensial kutish va jitter — BullMQ ishonchlilik darsidan. - Chegara bilan.
countsiz — cheksiz urinish.
TCP mijozining o'z retryAttempts va retryDelay sozlamalari ham bor (tur faylida). Ular ulanishni qayta tiklash uchun; xabarni qayta yuborish — retry operatori ishi.
9. O'z transportingiz
Nest'ning har transporti ikki klass: server (Server dan meros, CustomTransportStrategy) va mijoz (ClientProxy dan meros). O'zimiz yozib ko'rsak, ular qanday ishlashi ochiq ko'rinadi. "Broker" sifatida oddiy EventEmitter (bitta jarayon ichida):
const shina = new EventEmitter();
class ShinaServer extends Server implements CustomTransportStrategy {
listen(cb: () => void) {
type Javob = (w: WritePacket) => void;
shina.on("xabar", async (p: Paket, javob: Javob) => {
const h = this.getHandlerByPattern(p.pattern);
if (!h) {
javob({ err: `handler yo'q: ${JSON.stringify(p.pattern)}`,
isDisposed: true });
return;
}
const ctx = new BaseRpcContext([]);
const natija = await h(p.data, ctx);
const natija$ = this.transformToObservable(natija);
this.send(natija$, (w) => javob(w));
});
shina.on("hodisa", (p: ReadPacket) => {
void this.handleEvent(p.pattern, p, new BaseRpcContext([]));
});
cb();
}
close() { shina.removeAllListeners(); }
on() { throw new Error("kerak emas"); }
unwrap<T>(): T { return shina as T; }
}
class ShinaMijoz extends ClientProxy {
async connect() {}
async close() {}
unwrap<T>(): T { return shina as T; }
protected publish(p: ReadPacket, cb: (w: WritePacket) => void) {
shina.emit("xabar", { ...p, id: String(Math.random()) }, cb);
return () => {};
}
protected async dispatchEvent<T>(p: ReadPacket): Promise<T> {
shina.emit("hodisa", p);
return undefined as T;
}
}
const srv = await NestFactory.createMicroservice(ShinaModule, {
strategy: new ShinaServer(),
});4) custom transport: Salom, Malika!
hodisa shinadan keldi: 7
noma'lum: "handler yo'q: {\"cmd\":\"yoq\"}"Uch qatorli natija — @MessagePattern({ cmd: "salom" }) va @EventPattern("vazifa.bajarildi") li oddiy controller o'zgarishsiz ishladi. Transport yozuvchining vazifasi kichik:
- Server:
listenda xabar manbasiga obuna bo'lish;getHandlerByPattern(pattern'ni o'zi normallashtiradi) bilan handler'ni topish;transformToObservable+sendbilan javob qaytarish; hodisanihandleEventga berish. - Mijoz:
publish— xabarni yuborish va javobnicbga berish;dispatchEvent— hodisani yuborish.
Shu ikki klass bilan Nest'ni istalgan tizimga ulash mumkin: AWS SQS, Google Pub/Sub, o'z ichki shinangiz. Ommaviy jamiyat paketlarida ko'plari shunday yozilgan.
flowchart LR
C["Controller (o'zgarmaydi)"] --- S["Server: listen, send, handleEvent"]
S --- T{"Transport"}
T --- TCP["TCP"]
T --- RMQ["RabbitMQ"]
T --- G["gRPC"]
T --- O["o'zingizniki"]
M["ClientProxy: publish, dispatchEvent"] --- TDiagrammaning markazi — Transport bo'g'ini: controller va ClientProxy uni bilmaydi.
10. JS modelida ack
RabbitMQ'dagi noAck farqini kichik modelda ko'raylik: ikki xabar, ishchi birinchisini birinchi marta olganda yiqiladi. Blok brauzerda ishlaydi:
// noAck: true — olindi = bajarildi;
// noAck: false — faqat ack'dan keyin
class Broker {
#navbat = [];
constructor(noAck) {
this.noAck = noAck;
}
yubor(xabar) {
this.#navbat.push(xabar);
}
yetkaz(ishlovchi) {
while (this.#navbat.length > 0) {
const xabar = this.#navbat.shift(); // navbatdan olindi
try {
ishlovchi(xabar);
// noAck: false bo'lsa — shu yerda ack
} catch (e) {
console.log(` ${xabar}: ${e.message}`);
if (!this.noAck) this.#navbat.push(xabar); // qayta navbatga
}
}
}
}
for (const noAck of [true, false]) {
const b = new Broker(noAck);
["xat-1", "xat-2"].forEach((x) => b.yubor(x));
let birinchiMarta = true;
const bajarildi = [];
b.yetkaz((x) => {
if (x === "xat-1" && birinchiMarta) {
birinchiMarta = false;
throw new Error("ishchi yiqildi");
}
bajarildi.push(x);
});
console.log(`noAck: ${noAck} → bajarildi: ${bajarildi.join(", ")}`);
}Konsolda:
xat-1: ishchi yiqildi
noAck: true → bajarildi: xat-2
xat-1: ishchi yiqildi
noAck: false → bajarildi: xat-2, xat-1noAck: true — Nest RabbitMQ transportining sukuti — xat-1 ni yo'qotdi: navbatdan olindi va yiqildi, broker uni qayta bermaydi. noAck: false da xat navbatga qaytdi va ikkinchi urinishda bajarildi. Soddalashtirilgan joyi: haqiqiy RabbitMQ'da xabar ishchi ulanishi uzilganda yoki nack bilan qaytadi; modelda esa catch uni darhol qaytaradi.
11. Kanon tajribasi: nega loyihaga kirmaydi
Kanon tajribasi faqat TCP transportini o'lchadi (oldingi dars). Redis, NATS, RabbitMQ, Kafka, gRPC transportlari va hybrid ilova — kanonda sinalmagan: brokerlar Docker konteyner talab qiladi, ularni xom kutubxonalar bilan Boshqa xabar broker'lari darsida sinagandik. gRPC va o'z transportimiz bu darsda bizning demomizda sinaldi.
vazifalar-api da xabar almashish ehtiyoji bor — 29-qismda u BullMQ navbati va transactional outbox bilan hal qilingan. Nest mikroservis transporti bu ishni qayta qilardi, lekin yomonroq: BullMQ'da qayta urinish, kechiktirish va DLQ tayyor, Nest RMQ transportida esa ularni o'zingiz yozasiz. Kanon qarori — navbat uchun BullMQ, mikroservis transporti — kerak emas.
12. Ko'p uchraydigan xatolar
| Xato | Nima bo'ladi | Tuzatish |
|---|---|---|
RMQ sukut noAck: true |
ishchi yiqilsa xabar yo'qoladi | noAck: false + ack |
durable yo'q |
broker qayta yonsa navbat yo'qoladi | queueOptions: { durable: true } |
Eski nats paketi |
Nest 12 NATS transporti ishlamaydi | @nats-io/transport-node |
Date/Map tarmoqqa |
satr yoki {} |
JSON turlari yoki serializer |
| Idempotent bo'lmagan amalga retry | ikki marta bajariladi | idempotentlik kaliti |
| gRPC'da deadline yo'q | sekin xizmat zanjirni osadi | { deadline: Date.now() + ms } |
13. Mashqlar
Mashqlar kurs/mashqlar/30/46-transportlar/ da.
1-mashq (oson): bo'shliqlarni to'ldiring
- Nest RabbitMQ transportida
noAckning sukut qiymati — - Nest 12 NATS transporti talab qiladigan paket —
@nats-io/ - gRPC'da
NOT_FOUNDstatusining raqami — - Muddat o'tganda gRPC qaytaradigan status nomi —
- Nest Redis transporti qaysi Redis klientini ishlatadi? —
Yechim
- true —
RMQ_DEFAULT_NOACK = true; kafolat uchunfalseva qo'ldaack. - transport-node — nats.js 3-avlodi.
- 5 —
status.NOT_FOUND; deadline — 4. - DEADLINE_EXCEEDED —
Deadline exceeded after 0.100s. - ioredis — loyihadagi node-redis'dan farqli, ikkinchi klient bo'lardi.
2-mashq (o'rta): serializer va deserializer
Xabarni "konvert"ga soladigan juftlik yozing. serializer.serialize(data) — JSON.stringify({ v: 2, data }) qaytarsin. deserializer.deserialize(matn) — JSON'ni ochsin, v 2 bo'lmasa versiya N qo'llanmaydi xatosini tashlasin, aks holda data ni qaytarsin. Ishora: JSON.parse va JSON.stringify.
Yechim
// Serializer va deserializer: xabar "konvert"ga solinadi
const serializer = {
serialize: (data) => JSON.stringify({ v: 2, data }),
};
const deserializer = {
deserialize(matn) {
const k = JSON.parse(matn);
if (k.v !== 2) throw new Error(`versiya ${k.v} qo'llanmaydi`);
return k.data;
},
};
const tarmoqdan = serializer.serialize({ id: 7, matn: "Non olish" });
console.log(tarmoqdan);
console.log(deserializer.deserialize(tarmoqdan));
try {
deserializer.deserialize(JSON.stringify({ v: 1, data: {} }));
} catch (e) {
console.log(e.message);
}Konsolda:
{"v":2,"data":{"id":7,"matn":"Non olish"}}
{ id: 7, matn: 'Non olish' }
versiya 1 qo'llanmaydiVersiyali konvert xizmatlar alohida deploy qilinganda kerak: yangi gateway 2-versiyani yuboradi, eski xizmat hali 1-versiyani kutadi. Aniq xato "jim noto'g'ri ishlash"dan yaxshi. Haqiqiy tizimlarda bir muddat ikkala versiyani ham qabul qilish (expand-contract g'oyasi) odatiy.
3-mashq (qiyin): takrorni tashlaydigan iste'molchi
noAck: false bilan yetkazish — "kamida bir marta": broker bir xabarni ikki marta berishi mumkin. To'lov xabarlari ro'yxati berilgan, unda a1 ikki marta keldi. Balansni hisoblang, lekin bir id faqat bir marta qo'shilsin. Ishora: Set — bajarilgan id lar; tekshiruv ishdan oldin, qo'shish — ishdan keyin.
Yechim
// "Kamida bir marta" yetkazish: takrorni iste'molchi o'zi tashlaydi
const qayta = [
{ id: "a1", summa: 35000 },
{ id: "a2", summa: 28000 },
{ id: "a1", summa: 35000 }, // broker qayta yubordi
];
const bajarilgan = new Set();
let balans = 0;
for (const x of qayta) {
if (bajarilgan.has(x.id)) {
console.log(`${x.id}: takror — o'tkazildi`);
continue;
}
balans += x.summa;
bajarilgan.add(x.id);
console.log(`${x.id}: +${x.summa}`);
}
console.log(`balans: ${balans}`);Konsolda:
a1: +35000
a2: +28000
a1: takror — o'tkazildi
balans: 6300098 000 o'rniga 63 000 — to'g'ri. Haqiqiy tizimda Set xotirada emas, bazada bo'ladi: UNIQUE (xabar_id) ustunli jadval va ish bilan bitta tranzaksiyada yoziladi. 29-qismdagi webhook va to'lov darslarida (inbox naqshi) aynan shu qilingan. "Bir marta" kafolatini hech bir broker to'liq bermaydi — uni "kamida bir marta" + idempotent iste'molchi yasaydi.
14. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi
vazifalar-api o'zgarmaydi. Oldingi darsdagi demo loyihangizga gRPC transportini qo'shib ko'ring: .proto yozing, @GrpcMethod bilan hisobot metodini ulang, mijozda deadline bering va xizmatni ataylab sekinlashtiring. DEADLINE_EXCEEDED ni o'z ko'zingiz bilan ko'rish — bu darsning eng foydali mashqi.
15. Real ishda
- RabbitMQ + Nest — buyurtma, email, hisobot ishlari; review'da birinchi savol: "
noAcknima?durablebormi?". - Kafka + Nest — katta oqimli tizimlar: to'lov hodisalari, analitika, audit jurnali.
- gRPC — ichki xizmatlar orasida, ayniqsa boshqa tillar (Go, Java) bilan:
.proto— umumiy shartnoma. - Intervyu savollari: "At-most-once va at-least-once farqi?", "Nest'da transportni almashtirsa, kod o'zgaradimi?", "gRPC deadline va timeout farqi?", "Idempotent iste'molchi nima?".
Xulosa
- Controller kodi transportdan mustaqil; ishonchlilik — transportga bog'liq. Har transportning o'z npm paketi bor.
- TCP, Redis Pub/Sub, oddiy NATS — xabarni saqlamaydi; RabbitMQ, Kafka — saqlaydi.
- RabbitMQ: Nest sukuti
noAck: true— kafolat uchunnoAck: false, qo'ldaack,durable: true. - gRPC:
@GrpcMethod, Observable — server oqimi,RpcException({ code })→ status kodi, deadline protokolda. - Serializer — format va versiya;
retry— faqat idempotent amalga, kutish va chegara bilan. - O'z transportingiz:
Server+ClientProxy—listen,send,publish,dispatchEvent.
Keyingi dars: CQRS moduli — buyruqlar (command) va so'rovlarni (query) ajratish: CommandBus, QueryBus, EventBus, saga va qachon CQRS ortiqcha.
Manbalar
- NestJS hujjati — Microservices: Redis, NATS, RabbitMQ, Kafka, gRPC, Custom transporters: https://docs.nestjs.com/microservices/basics
@nestjs/microservices12.2.0 manba kodi (server-rmq.js,server-kafka.js,constants.js,transform-pattern.utils.js): https://github.com/nestjs/nest/tree/master/packages/microservices- NestJS hujjati — RabbitMQ (
noAcksukuttrue, 2026-11-02 da o'qildi): https://docs.nestjs.com/microservices/rabbitmq - RabbitMQ — Consumer Acknowledgements and Publisher Confirms: https://www.rabbitmq.com/docs/confirms
- gRPC — Deadlines; Status codes: https://grpc.io/docs/guides/deadlines/ , https://grpc.io/docs/guides/status-codes/
- Kanon (IlmHamroh, 2026-10-28): Redis, NATS, RabbitMQ, Kafka, gRPC transportlari — kanonda sinalmagan. gRPC (unary, oqim,
RpcException, deadline), serializer,retry, custom transporter — biz ishga tushirdik (2026-11-02, Node 24.21.0, NestJS 12.2.0,@grpc/grpc-js1.14.6,@grpc/proto-loader0.8.1). Broker transportlari sozlamalari — paket tur fayllari va manba kodidan, ishga tushirilmagan.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!