IlmHamroh
JavaScript Full-stack/29-qism. Backend integratsiyalar: fayl, to'lov, bot, realtime62/69-dars18 daqiqa
Mundarija (28)

gRPC va Protocol Buffers Node.js'da: .proto, unary, stream va deadline

Qisqacha: gRPC — servislar orasidagi RPC freymvorki: shartnoma .proto faylida (Protocol Buffers), ma'lumot ixcham ikkilik formatda, transport HTTP/2. Servis metodlari to'rt xil: unary (bitta so'rov — bitta javob), server stream, client stream va ikki tomonlama stream. Har chaqiruvga deadline qo'yiladi — muddat o'tsa DEADLINE_EXCEEDED. Node'da @grpc/grpc-js va @grpc/proto-loader ishlatiladi. Brauzer gRPC'ni to'g'ridan gapira olmaydi — u uchun gRPC-Web yoki Connect RPC.

Bu darsda

  • .proto faylda message va service yozasiz va maydon raqamlari nega muqaddas ekanini bilasiz.
  • Protobuf ikkilik formatini qo'lda kodlab, uni JSON bilan solishtirasiz.
  • @grpc/grpc-js bilan unary va server stream metodlarini yozib, kanon natijasini o'qiysiz.
  • Deadline va gRPC status kodlarini ishlatasiz.
  • gRPC, REST, GraphQL va tRPC orasida servislararo aloqa uchun tanlay olasiz.

Oldin bilishingiz kerak: tRPC, HTTPS, TLS va HTTP/2, Stream nima, Server timeout'lari va keep-alive.

1. Nega bu kerak?

Hozirgacha API'larimiz brauzer yoki mobil ilova bilan gaplashardi. Endi boshqa vaziyat: «Bahor» o'sdi, backend bir nechta servisga bo'lindi — buyurtmalar, to'lovlar, bildirishnomalar, ombor. Ular bir-biri bilan soniyasiga minglab marta gaplashadi.

Bu yerda talablar boshqacha:

  • Tezlik va hajm. Har so'rovdagi JSON kalit nomlari ("bajarildi", "egasiId") — takrorlanadigan ortiqcha baytlar va tahlil vaqti.
  • Qat'iy shartnoma. Servislarni turli jamoalar, ba'zan turli tillarda (Go, Java, Python) yozadi. Tip bitta joyda e'lon qilinishi va hamma tilga generatsiya qilinishi kerak.
  • Oqim. "Ombordagi barcha qoldiqlarni jo'nat" — ming qator, bittalab, ulanish ochiq turgan holda.
  • Muddat. Zanjir: buyurtma → to'lov → bank. Bank sekin bo'lsa, hamma kutib qolmasligi kerak.

gRPC (Google, 2015-yilda ochiq) shu to'rt talabga javob. Uning asosi — Protocol Buffers (protobuf): sxema tili va ikkilik serializatsiya formati.

Kanon tajribasi (2026-10-27): @grpc/grpc-js 1.14.6, @grpc/proto-loader 0.8.1, Node 24.21.0 — unary, server stream, NOT_FOUND, deadline. Protobuf baytlarini biz alohida tekshirdik (protobufjs 7.6.6 — proto-loader'ning o'z bog'liqligi). Connect RPC, buf va gRPC-Web — sinalmagan, hujjat bo'yicha.

2. .proto fayl

2.1 Message va service

Kanon fayli (formatladik):

protobuf
syntax = "proto3";
package vazifalar.v1;

service VazifaXizmati {
  rpc Olish (OlishSorov) returns (Vazifa);
  rpc Royxat (RoyxatSorov) returns (stream Vazifa);
  rpc Sekin (OlishSorov) returns (Vazifa);
}

message OlishSorov { int64 id = 1; }
message RoyxatSorov { int32 egasi_id = 1; }

message Vazifa {
  int64 id = 1;
  string matn = 2;
  bool bajarildi = 3;
  int32 egasi_id = 4;
}
  • syntax = "proto3" — protobuf'ning 3-versiyasi (bugungi standart).
  • package vazifalar.v1 — nomlar maydoni va versiya. v1 — buzadigan o'zgarishda v2 paketi (API versiyalash).
  • message — ma'lumot tuzilmasi (TypeScript'dagi type). Maydon nomlari snake_case — protobuf konventsiyasi.
  • service va rpc — metodlar: nom, so'rov turi, javob turi. stream so'zi — oqim.

2.2 Maydon raqamlari

string matn = 2; dagi 2 — qiymat emas, maydon raqami (field number). Ikkilik formatda nom yo'q, faqat shu raqam. Shuning uchun qoidalar qat'iy:

  • Raqam e'lon qilingandan keyin o'zgartirilmaydi. matn ni 5 qilsangiz, eski klient yuborgan 2 maydonini yangi server tanimaydi.
  • O'chirilgan maydon raqami qayta ishlatilmaydi: reserved 3; deb belgilanadi. Aks holda eski klientning bajarildi baytlari yangi maydon sifatida o'qiladi.
  • Nomni o'zgartirish — ikkilik formatda xavfsiz (nom yuborilmaydi), lekin generatsiya qilingan kodni buzadi.
  • 1–15 raqamlar bitta bayt teg oladi — tez-tez ishlatiladigan maydonlarga.

Moslik qoidasi oldingi darsdagi breaking change'ga o'xshaydi, lekin yumshoqroq: proto3'da hamma maydon ixtiyoriy. Yangi maydon qo'shish — eski klient uni shunchaki o'tkazib yuboradi (noma'lum maydon), yangi klient esa eski serverdan sukut qiymatni oladi.

2.3 Sukut qiymatlar

proto3'da 0, "", false — sukut qiymatlar va ular simga yozilmaydi. Natijada "maydon yo'q" va "maydon 0" ni ajratib bo'lmaydi: bajarildi: false va "bajarildi yuborilmadi" bir xil. Kerak bo'lsa — optional bool bajarildi = 3; (proto3'da 2020-yildan qaytgan) yoki google.protobuf.BoolValue o'rami. Kanon defaults: true bilan yuklagan — sukut qiymatlar JS obyektida ko'rinadi.

3. Ikkilik format: o'zimiz kodlaymiz

3.1 Varint va teg

Protobuf har maydonni ikki qismda yozadi: teg (maydon raqami << 3 | sim turi) va qiymat. Sim turi 0 — varint (son, bool), 2 — uzunlik bilan (satr, ichki message).

Varint — o'zgaruvchan uzunlikdagi son: har baytning 7 biti ma'lumot, eng katta biti "yana bayt bor" belgisi. Kichik son — bitta bayt. Kodlovchini yozamiz:

js
function varint(n) {
  const baytlar = [];
  while (n > 127) {
    baytlar.push((n & 127) | 128);
    n = Math.floor(n / 128);
  }
  baytlar.push(n);
  return baytlar;
}

function maydon(raqam, tur, qiymat) {
  const teg = varint((raqam << 3) | tur);
  if (tur === 0) return [...teg, ...varint(qiymat)];
  const b = [...new TextEncoder().encode(qiymat)];
  return [...teg, ...varint(b.length), ...b];
}

function vazifaKodla(v) {
  const qism = [];
  if (v.id) qism.push(...maydon(1, 0, v.id));
  if (v.matn) qism.push(...maydon(2, 2, v.matn));
  if (v.bajarildi) qism.push(...maydon(3, 0, 1));
  if (v.egasi_id) qism.push(...maydon(4, 0, v.egasi_id));
  return qism;
}

const hex = (b) =>
  b.map((x) => x.toString(16).padStart(2, "0")).join(" ");
const v = { id: 7, matn: "Vazifa 7", bajarildi: true, egasi_id: 2 };
const bin = vazifaKodla(v);
console.log(bin.length, "bayt:", hex(bin));
console.log(JSON.stringify(v).length, "bayt JSON");
console.log("300 varint:", hex(varint(300)));
const sukut = { id: 0, matn: "", bajarildi: false };
console.log("sukutlar:", vazifaKodla(sukut));

Konsolda:

text
16 bayt: 08 07 12 08 56 61 7a 69 66 61 20 37 18 01 20 02
56 bayt JSON
300 varint: ac 02
sukutlar: []

Biz xuddi shu xabarni haqiqiy protobufjs 7.6.6 bilan ham kodladik:

text
protobuf: 16 bayt: 08 07 12 08 56 61 7a 69 66 61 20 37 18 01 20 02
JSON: 56 bayt

Baytlar aynan bir xil. O'qiymiz: 08 — 1-maydon, varint (1 << 3 | 0); 07 — qiymat 7. 12 — 2-maydon, uzunlik bilan (2 << 3 | 2 = 18 = 0x12); 08 — 8 bayt; keyin Vazifa 7 ning UTF-8 baytlari. 18 01 — 3-maydon, true. 20 02 — 4-maydon, 2. JSON'da 56 bayt edi — 3.5 barobar kichik. Kalit nomlari yo'q, sonlar matn emas.

300 → ac 02: 300 = 0b1_0010_1100. Birinchi bayt — pastki 7 bit (010_1100) + "davomi bor" biti = 0xac; ikkinchi — qolgan 10 = 0x02. Sukut qiymatlar umuman yozilmadi — bo'sh massiv.

Tekshirib ko'ring: Serverdagi .proto da matn maydoni raqami 2 dan 5 ga o'zgartirildi, klient eskicha qoldi. Klient matn: "Non" yuborsa nima bo'ladi?

Javob

Klient 12 03 4e 6f 6e yuboradi — 2-maydon. Yangi server 2-maydonni tanimaydi va jim o'tkazib yuboradi, 5-maydon (matn) esa bo'sh qoladi: vazifa matnsiz saqlanadi. Hech qanday xato yo'q — shuning uchun maydon raqamlari o'zgartirilmaydi va buf breaking kabi vositalar buni CI'da ushlaydi.

3.2 Narxi

Ikkilik format o'qib bo'lmaydi: curl bilan ko'rib, log'ga yozib, brauzerda tekshirib bo'lmaydi. Xabarni ochish uchun .proto fayl kerak. Bu — jamoalar gRPC'ni faqat ichki aloqada ishlatishining sabablaridan biri. Debug uchun grpcurl va buf curl kabi vositalar .proto (yoki server reflection) yordamida xabarni JSON qilib ko'rsatadi.

4. Node.js'da server va klient

4.1 Dinamik yuklash

Node'da ikki yo'l bor: dinamik — .proto faylni ishga tushishda o'qish (@grpc/proto-loader), va statik — oldindan TypeScript kodini generatsiya qilish (buf generate, ts-proto). Kanon dinamik yo'lni ishlatgan:

js
import * as grpc from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";

const def = grpc.loadPackageDefinition(
  protoLoader.loadSync("vazifa.proto", {
    keepCase: true,
    longs: Number,
    defaults: true,
  }),
);
const Xizmat = def.vazifalar.v1.VazifaXizmati;
  • keepCase: true — maydon nomlari .proto dagidek (egasi_id), aks holda egasiId bo'lardi.
  • longs: Number — int64 JS'ga Number bo'lib keladi. Ehtiyot bo'ling: int64 2⁶³ gacha, Number esa 2⁵³ gacha aniq (BigInt: juda katta butun sonlar). Katta ID'lar (Twitter Snowflake, ba'zi bank raqamlari) jim buziladi. Xavfsiz variant — longs: String.
  • defaults: true — sukut qiymatlarni obyektga qo'yish.

4.2 Server: unary va server stream

js
const server = new grpc.Server();
server.addService(Xizmat.service, {
  Olish: ({ request }, cb) => {
    const v = VAZ.find((x) => x.id === request.id);
    if (v) cb(null, { ...v, egasi_id: v.egasiId });
    else {
      const code = grpc.status.NOT_FOUND;
      cb({ code, details: "vazifa topilmadi" });
    }
  },
  Royxat: (call) => {
    const { egasi_id } = call.request;
    const mos = VAZ.filter((x) => x.egasiId === egasi_id);
    for (const v of mos) call.write({ ...v, egasi_id: v.egasiId });
    call.end();
  },
  Sekin: (_call, cb) => setTimeout(() => cb(null, {}), 500),
});
server.bindAsync(
  "127.0.0.1:50051",
  grpc.ServerCredentials.createInsecure(),
  () => {},
);
  • Unary (Olish) — callback uslubi: cb(xato, javob). Xato — { code, details }.
  • Server stream (Royxat) — call.write() bilan har elementni yuboradi, call.end() bilan tugatadi. Bu Stream nima darsidagi Writable g'oyasi.
  • createInsecure() — TLS'siz, faqat lokal. Production'da ServerCredentials.createSsl(...) yoki servislar orasida mTLS (mutual TLS — ikki tomon ham bir-biriga sertifikat ko'rsatadi). Katta tizimlarda mTLS'ni ko'pincha service mesh (servislar orasidagi trafikni boshqaradigan alohida qatlam, masalan Istio) avtomatik qo'yadi.

Qolgan ikki tur — client stream (mijoz ko'p xabar yuboradi, server bitta javob beradi: fayl bo'laklari) va bidirectional stream (ikki tomon ham erkin yuboradi: chat, o'yin). Kanon ularni sinamagan.

4.3 Kanon natijasi

Kanon serverni ishga tushirib, to'rt narsani sinadi. Har qatorning boshiga qarang — qaysi metod chaqirildi va nima qaytdi:

text
Olish(7): {"id":7,"matn":"Vazifa 7","bajarildi":true,"egasi_id":2} | Olish(999): NOT_FOUND: vazifa topilmadi
Royxat stream (egasi 2): 10 ta: 2,7,12,17,22,27,32,37,42,47
Sekin, deadline 100 ms: DEADLINE_EXCEEDED: Deadline exceeded after 0.102s,remote_addr=127.0.0.1:63702
unary mediana 0.840 ms (HTTP/2, protobuf) — insecure (TLS'siz) faqat lokal

Nima bo'ldi? Olish(7) vazifani qaytardi, Olish(999) — NOT_FOUND. Stream egasi 2 bo'lgan 10 ta vazifani bittalab yubordi. Sekin 100 ms deadline'dan o'tib ketdi. Uchinchi qatordagi port (63702) va vaqt (0.102s) — har ishga tushishda boshqacha.

Klient tomoni:

js
const c = new Xizmat(
  "127.0.0.1:50051",
  grpc.credentials.createInsecure(),
);

c.Olish({ id: 7 }, { deadline: Date.now() + 100 }, (xato, v) => {
  if (xato) console.log(grpc.status[xato.code], xato.details);
  else console.log(v);
});

const oqim = c.Royxat({ egasi_id: 2 });
oqim.on("data", (v) => console.log(v.id));
oqim.on("end", () => console.log("tugadi"));

Unary mediana 0.84 ms — lokal, TLS'siz, bitta mashina, kichik xabar: bu "gRPC tez" degan da'vo emas, faqat tartib. Haqiqiy farq tarmoq, xabar hajmi va ulanishni qayta ishlatishda ko'rinadi: HTTP/2 bitta TCP ulanishda ko'p chaqiruvni parallel (multiplexing) olib boradi.

5. Deadline va status kodlari

5.1 Deadline

Deadline — "javob shu vaqtgacha kerak, keyin kerak emas". Timeout'dan farqi: deadline — vaqt nuqtasi va u zanjir bo'ylab uzatiladi. Buyurtma servisi to'lov servisini 2 soniya deadline bilan chaqiradi; to'lov servisi bankni chaqirganda qolgan 1.4 soniyani uzatadi. Shunday qilib hech kim "keraksiz" ish qilmaydi.

sequenceDiagram
  participant B as Buyurtma
  participant T as To'lov
  participant K as Bank
  B->>T: Tolash (deadline 2 s)
  T->>K: Yechish (qolgani 1.4 s)
  Note over K: sekin
  K--xT: DEADLINE_EXCEEDED
  T--xB: DEADLINE_EXCEEDED
  Note over B: foydalanuvchiga "keyinroq" javobi

Kanon Sekin metodi 500 ms uxlagan, klient 100 ms deadline bergan — 0.102 s da DEADLINE_EXCEEDED. Deadline'siz chaqiruv — gRPC'dagi eng ko'p uchraydigan xato: server qotsa, klient abadiy kutadi. Qoida: har chaqiruvga deadline. Server tomonda esa call.cancelled ni tekshirib, muddati o'tgan ishni to'xtatish kerak — aks holda klient ketgan, server hamon ishlayapti. Bu Server timeout'lari va 24-qismdagi AbortSignal g'oyasining servislararo ko'rinishi.

5.2 Status kodlari

gRPC'da HTTP status kodlari o'rniga o'zining 17 ta kodi bor. Javob har doim HTTP 200, gRPC kodi esa grpc-status trailerida keladi. Trailer — javob tanasidan keyin yuboriladigan sarlavhalar: server oqimni oxirigacha yuborib bo'lgach, "natija: OK" yoki "natija: xato" deb yozadi.

gRPC kodi Ma'nosi HTTP'dagi yaqini
OK (0) muvaffaqiyat 200
INVALID_ARGUMENT (3) noto'g'ri so'rov 400
DEADLINE_EXCEEDED (4) muddat o'tdi 504
NOT_FOUND (5) topilmadi 404
PERMISSION_DENIED (7) ruxsat yo'q 403
UNAUTHENTICATED (16) autentifikatsiya yo'q 401
UNAVAILABLE (14) servis vaqtincha yo'q 503

UNAVAILABLE — servis vaqtincha yo'q, keyinroq javob berishi mumkin. Rasmiy hujjat aynan shu kodda qayta urinishni (backoff bilan) tavsiya qiladi. DEADLINE_EXCEEDED da esa qayta urinish xavfli: server ishni bajargan bo'lishi mumkin — Idempotency-Key g'oyasi bu yerda ham kerak.

Qo'shimcha ma'lumot (autentifikatsiya tokeni, correlation ID) — metadata orqali: HTTP sarlavhalarining gRPC'dagi o'xshashi.

6. Brauzer, Connect RPC va buf

gRPC HTTP/2'ning trailerlarini ishlatadi — brauzer fetch API'si ularni o'qiy olmaydi. Shuning uchun brauzer gRPC serverini to'g'ridan chaqira olmaydi. Uch yechim (hujjat bo'yicha, sinalmagan):

  • gRPC-Web — maxsus protokol + proksi (Envoy) brauzer va gRPC server o'rtasida.
  • Connect RPC (Buf kompaniyasi) — bitta server uch protokolni gapiradi: gRPC, gRPC-Web va oddiy HTTP+JSON. Brauzer fetch bilan JSON yuboradi, boshqa servislar esa gRPC. Node'da @connectrpc/connect.
  • gRPC-Gateway / transcoding — .proto dagi izohlar asosida REST endpoint'larni avtomatik yasash.

buf — protobuf uchun zamonaviy vositalar to'plami: buf lint (konventsiyalar), buf breaking (oldingi darsdagi oasdiff'ning .proto dagi o'xshashi — maydon raqami o'zgarganini ushlaydi), buf generate (TS, Go va boshqa tillarga kod). Katta protobuf loyihalarida protoc o'rnini egallab bormoqda.

7. Qaysi API qachon

REST + OpenAPI GraphQL
Asosiy mijoz hamma veb/mobil, ko'p xil
Format va shartnoma JSON, OpenAPI JSON, SDL
Ko'p til ha ha
Oqim SSE/WS alohida subscriptions
Brauzer to'g'ridan to'g'ridan
tRPC gRPC
Asosiy mijoz TS frontend boshqa servislar
Format va shartnoma JSON, TS tiplari protobuf, .proto
Ko'p til yo'q ha (generatsiya)
Oqim subscriptions ichida (4 tur)
Brauzer to'g'ridan proksi yoki Connect

Odatiy arxitektura: tashqi dunyo uchun REST yoki GraphQL (API gateway), ichkarida servislar orasida gRPC. Bitta Node monolitda (vazifalar-api kabi) gRPC kerak emas — servislar yo'q.

8. Hujumchi nigohi

  1. TLS'siz trafik. "Ichki tarmoq — xavfsiz" degan taxmin bilan createInsecure() production'da qoladi. Tarmoqqa kirgan hujumchi hamma xabarni o'qiydi va o'zgartiradi. Himoya: TLS, servislar orasida mTLS (har servisning o'z sertifikati).
  2. Autentifikatsiyasiz metod. Ichki servis "faqat ichkaridan chaqiriladi" deb ochiq qoladi. Himoya: metadata'da token yoki mTLS identifikatori va har metodda tekshiruv — interceptor orqali (har chaqiruv oldidan ishlaydigan funksiya, gRPC'dagi middleware).
  3. Server reflection ochiq. Reflection yoqilgan serverdan barcha servis va metodlar ro'yxatini olish mumkin — GraphQL introspection kabi. Himoya: production'da o'chirish yoki faqat ichki tarmoqda.
  4. Katta xabar va cheksiz oqim. grpc-js sukut bo'yicha kiruvchi xabarni 4 MB bilan cheklaydi (hujjatdagi grpc.max_receive_message_length); uni "muammo bo'lmasin" deb -1 qilish — xotira hujumiga yo'l. Client stream'da xabarlar soniga ham chegara qo'ying.
  5. int64 → Number. Bu hujum emas, lekin jim ma'lumot buzilishi: 2⁵³ dan katta ID boshqa yozuvga ishora qilib qolishi mumkin. Himoya: longs: String.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Klient abadiy kutadi deadline yo'q har chaqiruvga deadline
Yangi server eski klient maydonini noto'g'ri o'qiydi maydon raqami o'zgartirildi yoki qayta ishlatildi raqamlar o'zgarmaydi, reserved
egasiId o'rniga egasi_id kutilgan keepCase sozlamasi farqli ikki tomonda bir xil sozlama
Katta ID boshqa raqamga aylandi int64 → Number longs: String
Brauzerdan chaqirib bo'lmaydi brauzer HTTP/2 trailer'larini o'qimaydi gRPC-Web yoki Connect
false yubordim, server "yo'q" deb tushundi proto3 sukut qiymatlari yozilmaydi optional yoki o'ram tip

10. Mashqlar

Mashqlar kurs/mashqlar/29/62-grpc/ da.

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

  • gRPC shartnomasi fayli kengaytmasi — .
  • O'chirilgan maydon raqamini band qilish —
  • Muddat o'tganda status — DEADLINE_
  • Bitta so'rov — ko'p javob turi — server
  • gRPC transporti — HTTP/
Yechim
  • proto — Protocol Buffers.
  • reserved — reserved 3; yoki reserved "bajarildi";.
  • EXCEEDED — DEADLINE_EXCEEDED, kod 4.
  • stream — returns (stream Vazifa).
  • 2 — trailer'lar va multiplexing.

2-mashq (o'rta): varint'ni ochish

Darsdagi varint ga teskari funksiya yozing: varintOch(baytlar, i) — i pozitsiyadan varint o'qib, { qiymat, keyingi } qaytarsin. Keyin uni [0xac, 0x02], [0x07] va [0x80, 0x80, 0x01] bilan sinang. Oxirgisi nega 16384?

Yechim
js
function varintOch(baytlar, i = 0) {
  let qiymat = 0;
  let kopaytuvchi = 1;
  while (true) {
    const b = baytlar[i++];
    qiymat += (b & 127) * kopaytuvchi;
    if ((b & 128) === 0) break;
    kopaytuvchi *= 128;
  }
  return { qiymat, keyingi: i };
}

console.log(varintOch([0xac, 0x02]));
console.log(varintOch([0x07]));
console.log(varintOch([0x80, 0x80, 0x01]));

Konsolda:

text
{ qiymat: 300, keyingi: 2 }
{ qiymat: 7, keyingi: 1 }
{ qiymat: 16384, keyingi: 3 }

0x80, 0x80, 0x01: birinchi ikki baytning ma'lumot qismi 0, faqat "davomi bor" biti yoqilgan; uchinchi bayt 1 — u 128 × 128 = 16384 ga ko'paytiriladi. Bitlar siljitish (<<) o'rniga ko'paytirish ishlatdik: JS'da << 32 bitli, katta varint'larda noto'g'ri natija berardi.

3-mashq (qiyin): xabarni ochish

vazifaOch(baytlar) yozing: darsdagi 16 baytli xabarni { id, matn, bajarildi, egasi_id } ga qaytaring. Har maydonda tegni o'qing (raqam = teg >> 3, tur = teg & 7), tur 0 — varint, tur 2 — uzunlik + baytlar (TextDecoder). Noma'lum maydon raqamini xato bermasdan o'tkazib yuboring — protobuf'ning oldinga moslik qoidasi. Sinash uchun xabar oxiriga 5-maydon (varint 9) qo'shing.

Yechim
js
function varintOch(baytlar, i) {
  let qiymat = 0;
  let k = 1;
  while (true) {
    const b = baytlar[i++];
    qiymat += (b & 127) * k;
    if ((b & 128) === 0) break;
    k *= 128;
  }
  return { qiymat, keyingi: i };
}

const NOMLAR = { 1: "id", 2: "matn", 3: "bajarildi", 4: "egasi_id" };

function vazifaOch(baytlar) {
  const v = { id: 0, matn: "", bajarildi: false, egasi_id: 0 };
  const notanish = [];
  let i = 0;
  while (i < baytlar.length) {
    const t = varintOch(baytlar, i);
    i = t.keyingi;
    const raqam = t.qiymat >> 3;
    const tur = t.qiymat & 7;
    let qiymat;
    if (tur === 0) {
      const x = varintOch(baytlar, i);
      qiymat = x.qiymat;
      i = x.keyingi;
    } else if (tur === 2) {
      const u = varintOch(baytlar, i);
      const oxiri = u.keyingi + u.qiymat;
      const b = Uint8Array.from(baytlar.slice(u.keyingi, oxiri));
      qiymat = new TextDecoder().decode(b);
      i = u.keyingi + u.qiymat;
    } else {
      throw new Error(`qo'llanmagan sim turi ${tur}`);
    }
    const nom = NOMLAR[raqam];
    if (!nom) notanish.push(raqam);
    else v[nom] = nom === "bajarildi" ? qiymat === 1 : qiymat;
  }
  return { v, notanish };
}

const xabar = [
  0x08, 0x07, 0x12, 0x08, 0x56, 0x61, 0x7a, 0x69, 0x66, 0x61,
  0x20, 0x37, 0x18, 0x01, 0x20, 0x02, 0x28, 0x09,
];
console.log(vazifaOch(xabar));
console.log(vazifaOch([0x08, 0x2a]));

Konsolda:

text
{
  v: { id: 7, matn: 'Vazifa 7', bajarildi: true, egasi_id: 2 },
  notanish: [ 5 ]
}
{
  v: { id: 42, matn: '', bajarildi: false, egasi_id: 0 },
  notanish: []
}

0x28 — 5-maydon, varint (5 << 3 | 0 = 40). Bizning "eski" kodimiz 5-maydonni bilmaydi, lekin xato bermadi — o'tkazib yubordi. Yangi server qo'shgan maydon eski klientni buzmasligi shu qoidaga tayanadi. Ikkinchi xabarda faqat id: 42 bor — qolgan maydonlar sukut qiymat bilan to'ldirildi. bajarildi: false va "yuborilmagan" ni ajrata olmaymiz — darsdagi proto3 sukut qiymatlari muammosi.

11. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi

Bu darsda vazifalar-api ga kod qo'shilmaydi: u bitta servis, gRPC esa servislar orasidagi aloqa uchun. Kanon unary, server stream, NOT_FOUND va deadline'ni (DEADLINE_EXCEEDED, 100 ms) lokal, TLS'siz sinadi; protobuf baytlarini biz alohida tekshirdik. Mikroservislar va servislararo aloqa arxitekturasi 36-qismda (system design) qaytadi; NestJS'da gRPC transporti bor (30-qism).

O'zingiz sinab ko'ring: alohida papkada npm i @grpc/grpc-js@1.14.6 @grpc/proto-loader@0.8.1, darsdagi .proto va serverni yozing. Klientdan Sekin ni deadline'siz chaqiring va nima bo'lishini kuzating; keyin 100 ms deadline qo'shing. Server tomonda call.cancelled ni tekshirib, muddati o'tgan ishni to'xtating.

12. Real ishda

  • Mikroservislar — Google, Netflix, Uber va ko'p bank tizimlarida ichki aloqa gRPC'da.
  • Kubernetes ekotizimi — etcd, containerd va boshqa ko'p komponentlar API'si gRPC.
  • Mobil ↔ backend — ba'zan gRPC (mobil kutubxonalari HTTP/2'ni to'liq biladi), ko'pincha esa Connect yoki REST.
  • Ma'lumot oqimlari — telemetriya (OpenTelemetry OTLP protokoli protobuf'da), birja kotirovkalari, IoT.
  • Intervyuda: "gRPC REST'dan nimasi bilan farq qiladi?", "Protobuf maydon raqamini nega o'zgartirib bo'lmaydi?", "Deadline va timeout farqi?", "Brauzerdan gRPC'ni qanday chaqirasiz?"

Xulosa

  • gRPC = .proto shartnoma + protobuf ikkilik format + HTTP/2; asosan servislar orasida.
  • Maydon raqami — ikkilik formatdagi yagona identifikator: o'zgartirilmaydi, o'chirilgani reserved.
  • Protobuf: teg + varint/uzunlik; kanon xabari 16 bayt, JSON'da 56; proto3 sukut qiymatlarni yozmaydi.
  • To'rt tur: unary, server stream, client stream, bidi; Node'da @grpc/grpc-js + proto-loader (longs: String xavfsizroq).
  • Har chaqiruvga deadline; DEADLINE_EXCEEDED da qayta urinish ehtiyotkorlik bilan; UNAVAILABLE — backoff bilan.
  • Brauzer uchun gRPC-Web yoki Connect RPC; buf — lint, breaking va generatsiya. Ular sinalmagan.

Keyingi dars: Fastify — Express'ga tez muqobil: sxema asosidagi validatsiya va serializatsiya, plaginlar va kanon o'lchagan tezlik farqi.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

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