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

Realtime'ni masshtablash: Redis adapter, sticky session va presence

Qisqacha: Bitta server o'z ulanishlarini biladi, xolos. Ikki server bo'lsa, A serverdagi emit B serverga ulangan mijozga yetmaydi — kanonda aynan shunday bo'ldi. Yechim — serverlar o'rtasida umumiy shina: Socket.IO'da Redis adapter (Pub/Sub) yoki Redis Streams adapter, SSE'da Redis Pub/Sub va tarix uchun Redis Stream. Qo'shimcha masalalar: polling transportida sticky session, qisqa uzilishdan keyin hodisalarni qaytarish (connection state recovery), kim onlayn ekanini bilish (presence) va har ochiq ulanishning xotira narxi.

Bu darsda

  • Nega ikkinchi server qo'shilganda xabarlar yo'qolishini kanon tajribasida ko'rasiz.
  • Socket.IO Redis adapter va Redis Streams adapter farqini bilasiz.
  • Sticky session qachon kerak va qachon kerak emasligini aniqlaysiz.
  • Connection state recovery va presence'ni (TTL bilan) qurasiz.
  • Ulanishlar narxi va fan-out'ni raqamlar bilan baholaysiz.

Oldin bilishingiz kerak: Socket.IO, Pub/Sub va Redis Streams, Server-Sent Events, cluster va ko'p yadroli masshtablash.

Oldingi darsda xonalar va ack bilan ishladik — hammasi bitta server ichida. Endi server ikkita bo'ladi.

1. Nega bu kerak?

vazifalar foydalanuvchilari ko'paydi va API ikki nusxada ishlay boshladi: A va B. Ularning oldida load balancer (yuk taqsimlagich) turadi — kelgan so'rovlarni nusxalarga navbatma-navbat bo'lib beradigan dastur, masalan nginx (Reverse proxy orqasida Node). Malikaning telefoni A ga, noutbuki B ga ulangan. Telefonda vazifa bajarildi — so'rov A ga keldi, A esa io.to("foydalanuvchi:7").emit(...) qildi. Noutbuk hech narsa olmadi.

Sabab oddiy: A serverning xotirasida faqat o'z ulanishlari bor. B dagi ulanish haqida u bilmaydi.

O'xshatish: bitta binoning ikki qavatidagi ikki radiouzel. Birinchi qavatdagi e'lon ikkinchi qavatda eshitilmaydi. Ular o'rtasiga sim tortish kerak — bu sim Redis.

Socket.IO'da bu "sim"ni adapter ulaydi. Adapter — Socket.IO ichidagi qism: emit qilinganda xabarni kimga yetkazishni u hal qiladi. Sukut bo'yicha adapter faqat shu jarayon xotirasiga qaraydi. Redis adapter esa xabarni boshqa serverlarga ham uzatadi.

2. Ikki server: adaptersiz va adapter bilan

2.1 Kanon tajribasi

Kanon ikki Socket.IO serverini (A va B) ishga tushirdi, foydalanuvchi 1 ni ikkalasiga bittadan ulanish bilan uladi va A dan to("foydalanuvchi:1").emit qildi. Uch variant — adaptersiz, @socket.io/redis-adapter 8.3.0 va @socket.io/redis-streams-adapter 0.3.1 (Redis 8.10.2, node-redis 6.3.0). fetchSockets() — "menga shu xonadagi hamma ulanishlarni ber" degan so'rov; u server nechta ulanishni ko'ra olishini ko'rsatadi.

Chiqishda har qatorning ikki qismiga qarang: qabul ro'yxatida qaysi mijozlar xabar oldi va A nechta ulanishni ko'rdi.

text
adaptersiz                 → qabul: [A], A dan ko'rinadigan ulanishlar (fetchSockets): 1
redis-adapter (pub/sub)    → qabul: [A, B], A dan ko'rinadigan ulanishlar (fetchSockets): 2
redis-streams-adapter      → qabul: [A, B], A dan ko'rinadigan ulanishlar (fetchSockets): 2

Nima bo'ldi? Adaptersiz B dagi mijoz xabar olmadi, fetchSockets() ham faqat bitta ulanishni ko'rdi. Ikkala adapter bilan — ikkalasi oldi va A ikkala ulanishni ko'rdi. Demak, muammo kodda emas, serverlar orasida "sim" yo'qligida edi.

flowchart LR
  LB[Load balancer] --> A[Server A]
  LB --> B[Server B]
  A <--> R[(Redis)]
  B <--> R
  A --> T[Malika: telefon]
  B --> N[Malika: noutbuk]

Diagrammaga qarang: A emit qilganda o'z ulanishlariga yuboradi va xabarni Redis'ga nashr qiladi. B Redis'dan uni olib, o'z ulanishlariga yuboradi. Mijoz qaysi serverga ulanganini bilishi shart emas.

Xuddi shu yo'l vaqt bo'yicha, qadamma-qadam:

sequenceDiagram
  participant T as Telefon (A ga ulangan)
  participant A as Server A
  participant R as Redis
  participant B as Server B
  participant N as Noutbuk (B ga ulangan)
  T->>A: vazifa bajarildi
  A->>T: emit (o'z ulanishi)
  A->>R: PUBLISH xabar
  R->>B: xabar (B obuna bo'lgan)
  B->>N: emit (o'z ulanishi)

2.2 Ulash

Kanon kodi (formatlangan; REDIS_URL — lokal namuna):

js
import { createClient } from "redis";
import { createAdapter } from "@socket.io/redis-adapter";
import { createAdapter as streamsAdapter }
  from "@socket.io/redis-streams-adapter";

// 1) Pub/Sub adapter: ikkita ulanish — nashr va obuna
const pub = createClient({ url: process.env.REDIS_URL });
const sub = pub.duplicate();
await Promise.all([pub.connect(), sub.connect()]);
const io = new Server(httpServer, {
  adapter: createAdapter(pub, sub, { key: "sinov" }),
});

// 2) Streams adapter: bitta ulanish, Redis Stream'ga yozadi
const r = createClient({ url: process.env.REDIS_URL });
await r.connect();
const io2 = new Server(httpServer2, {
  adapter: streamsAdapter(r, { streamName: "sinov-oqim" }),
});

Pub/Sub adapter ikkita ulanish oladi: Redis'da obuna rejimidagi ulanish boshqa buyruq bajara olmaydi (Pub/Sub va Redis Streams darsida ko'rgansiz).

2.3 Pub/Sub va Streams farqi

Redis adapter (Pub/Sub) Redis Streams adapter
Qanday yetkazadi PUBLISH — "eshitgan eshitdi" XADD — stream'ga yoziladi
Server Redis'dan qisqa uzilsa o'sha paytdagi xabarlar yo'qoladi qayta ulanganda o'qib oladi
Connection state recovery yo'q bor
Redis xotirasi saqlamaydi stream hajmi bilan (maxLen)

Socket.IO hujjatidagi moslik jadvalini 2026-10-28 da o'qidik. Connection state recovery xotiradagi standart adapter, Redis Streams va MongoDB adapterlarida ishlaydi. Klassik Redis adapterda — yo'q, izohi: "Persisting the packets is not compatible with the Redis PUB/SUB mechanism". Kanon ikkala adapterni yetkazish bo'yicha sinadi; Redis uzilishini sinamadi.

Tekshirib ko'ring: Ikki server va Redis adapter bor. A server io.emit("e") qildi, lekin aynan shu paytda B server Redis bilan ulanishini 2 soniyaga yo'qotdi. B dagi mijozlar xabarni oladimi?

Javob

Pub/Sub adapter bilan — yo'q: PUBLISH faqat hozir obuna bo'lganlarga yetadi, Redis xabarni saqlamaydi. Streams adapter bilan — B qayta ulanganda stream'dan o'tkazib yuborilganlarni o'qiydi (stream'ning maxLen chegarasi ichida). Shuning uchun ishonchlilik muhim bo'lsa — Streams adapter.

3. Sticky session

3.1 Qachon kerak

Socket.IO sukut bo'yicha avval HTTP long polling bilan boshlaydi (Socket.IO darsi). Polling — ketma-ket ko'p HTTP so'rov, hammasi bitta sessiya (sid) ga tegishli. Load balancer ularni turli serverlarga tarqatsa, B server "bunday sid yo'q" deb rad etadi.

Yechim — sticky session (yopishqoq sessiya): load balancer bitta mijozning hamma so'rovlarini bitta serverga yuboradi. Socket.IO hujjati bo'yicha nginx'da buning uchun ip_hash yoki hash (mijoz IP manzili bilan) ishlatiladi.

Hujjatdagi muhim jumla: mijoz HTTP long polling'ni ishlatmasa (transports: ["websocket"]), sticky session kerak emas — WebSocket bitta TCP ulanish, u boshidan oxirigacha bitta serverda. Kanon tajribalari shunday sozlangan.

Diqqat: Sticky session kanonda load balancer bilan sinalmagan — yuqoridagi hujjat bo'yicha (socket.io/docs/v4/using-multiple-nodes, 2026-10-28). nginx sozlamasi 33-qismda.

3.2 SSE uchun

SSE — bitta uzun HTTP javob. Sticky session kerak emas: ulanish o'zi bitta serverda turadi. Lekin qayta ulanganda mijoz boshqa serverga tushishi mumkin — shuning uchun Last-Event-ID tarixi umumiy joyda bo'lishi kerak (pastdagi «SSE'ni masshtablash» bo'limi).

4. Connection state recovery

4.1 Kanon tajribasi

Telefon tunnelga kirdi va 2 soniyaga uzildi. Shu paytda server ikki hodisa yubordi. Sukut bo'yicha ular yo'qoladi: mijoz qayta ulanadi, lekin "nimani o'tkazib yubordim" demaydi. Socket.IO 4.6 dan beri buning uchun sozlama bor:

js
const io = new Server(httpServer, {
  connectionStateRecovery: { maxDisconnectionDuration: 60_000 },
});

Kanon mijozning past darajadagi ulanishini yopdi (c.io.engine.close() — tarmoq uzilishi taqlidi), shu paytda 2 va 3 hodisalarni yubordi:

text
qayta ulandi: recovered=true, olingan hodisalar: [1, 2, 3]

recovered=true — server sessiyani tikladi: socket.id, xonalar, socket.data va uzilish paytidagi hodisalar qaytdi.

4.2 Cheklovlari

Socket.IO hujjati (2026-10-28) bo'yicha:

  • tiklash har doim muvaffaqiyatli bo'lmaydi — socket.recovered false bo'lsa, mijoz holatni o'zi to'liq yangilashi kerak (masalan, vazifalar ro'yxatini REST bilan qayta olish);
  • u qisqa uzilishlar uchun, uzoq oflayn uchun emas; maxDisconnectionDuration ni oqilona qiymatga qo'ying (Infinity emas);
  • klassik Redis (Pub/Sub) adapter bilan ishlamaydi;
  • skipMiddlewares: true ehtiyotkorlik bilan: uzilish paytida bloklangan foydalanuvchi middleware'siz qaytib kirishi mumkin.

Bu SSE'dagi Last-Event-ID ning Socket.IO varianti. Ikkalasida ham qoida bitta: tarixning cheki bor, undan oshsa — to'liq qayta yuklash.

5. Presence: kim onlayn

5.1 Kanon: Redis SET

Kanon har ulanishda foydalanuvchini Redis to'plamiga qo'shdi (SADD), oxirgi ulanishi uzilganda olib tashladi (SREM):

js
io.on("connection", async (so) => {
  await r.sAdd("sinov:onlayn", String(so.data.fId));
  so.join(`f:${so.data.fId}`);
  so.on("disconnect", async () => {
    const qolgan = await io.in(`f:${so.data.fId}`).fetchSockets();
    if (qolgan.length === 0) {
      await r.sRem("sinov:onlayn", String(so.data.fId));
    }
  });
});
text
onlayn: 1, 2

fetchSockets() — adapter orqali hamma serverlardan so'raydi: foydalanuvchining boshqa serverda yana oynasi bo'lsa, u onlayn qoladi.

5.2 Muammo: server o'lsa

Server qulasa (kill -9, elektr), disconnect ishlovchisi ishlamaydi. Uning foydalanuvchilari Redis to'plamida abadiy "onlayn" bo'lib qoladi. Kanon buni sarlavhada aytgan: "server o'lsa tozalanmaydi — prodda TTL/heartbeat kerak".

Yechim — yozuvning yashash muddati (TTL) bo'lsin: har server o'z foydalanuvchilarini davriy yangilab turadi, yangilanmagan yozuv o'zi eskiradi. Bunday davriy "men tirikman" signali heartbeat (yurak urishi) deyiladi (String, TTL va kalit nomlash). Mantiqni Redis'siz modellaymiz:

js
// Presence: har server har 30 s da "men tirikman" yozadi.
// Kalit: foydalanuvchi → { server, oxirgi } (Redis'da — TTL kalit)
const TTL = 60_000; // ms

const onlayn = new Map();

function belgila(fId, server, vaqt) {
  onlayn.set(fId, { server, oxirgi: vaqt });
}

function onlaynlar(hozir) {
  return [...onlayn]
    .filter(([, h]) => hozir - h.oxirgi < TTL)
    .map(([fId]) => fId);
}

belgila(1, "A", 0);
belgila(2, "B", 0);
belgila(3, "B", 0);
// 30 s: A server tirik — o'z foydalanuvchilarini yangilaydi.
// B server 10-soniyada o'ldi va hech narsa yozmadi.
belgila(1, "A", 30_000);
belgila(1, "A", 60_000);

console.log("40 s:", onlaynlar(40_000).join(", "));
console.log("70 s:", onlaynlar(70_000).join(", "));

Konsolda:

text
40 s: 1, 2, 3
70 s: 1

B o'lgandan keyin ham 40-soniyada 2 va 3 "onlayn" — TTL hali tugamagan. 70-soniyada ular o'zi tushib qoldi. TTL — aniqlik va yuk o'rtasidagi kelishuv: qisqa TTL tez tozalaydi, lekin yangilash ko'p.

Redis'da buni ikki usulda qiladi: har foydalanuvchiga SET onlayn:7 A EX 60, yoki Sorted Set'da vaqt bilan (ZADD onlayn <vaqt> 7, eskilarni ZREMRANGEBYSCORE bilan tozalash, Hash, List, Set va Sorted Set). Ikkinchisi "hozir kim onlayn" ro'yxatini bitta buyruq bilan beradi.

5.3 Presence qimmat

Har onlayn/oflayn o'zgarishini hamma do'stlarga yuborish — fan-out (pastdagi bo'lim). Katta ilovalar presence'ni faqat ko'rinib turgan suhbat yoki loyiha a'zolari uchun hisoblaydi va "5 daqiqa oldin" kabi taxminiy ko'rsatadi.

6. Ulanish narxi va fan-out

6.1 Xotira

Socket.IO darsidagi kanon o'lchovini eslang. Qatorda ikki raqamga qarang: RSS (jarayon egallagan xotira) qancha o'sdi va bitta ulanishga necha KB to'g'ri keldi.

text
2000 ulanish 2120 ms; RSS +114.3 MB (server + 2000 mijoz, ~58.5 KB/ulanish ikkala tomon); broadcast 2000 ga: oxirgisi 100 ms, mediana 78 ms

Server va mijozlar bitta jarayonda edi — 58.5 KB ikkala tomonning yig'indisi. Shunga qaramay tartibi aniq: o'n minglab ochiq ulanish — yuzlab megabayt, va bu xabar yuborilmasa ham band.

Har ochiq ulanish yana bitta fayl deskriptori (OS resursi). Linux'da bir jarayonga ochiq fayllar limiti bor (ulimit -n); u kichik bo'lsa, server yangi ulanishlarni qabul qila olmaydi. Kanon Windows'da o'lchagan — Linux limiti 33-qismda.

6.2 Fan-out

Fan-out — bitta hodisani ko'p qabul qiluvchiga tarqatish. Narx — qabul qiluvchilar soniga ko'paytiriladi. Kanonda 2 000 ga broadcast — oxirgisi 100 ms. Agar shunday xabar sekundiga 50 ta bo'lsa-chi? 2-mashqda hisoblaysiz: 100 000 yetkazish sekundiga.

Uch amaliy qoida:

  1. Bir marta serialize. io.to(xona).emit(x) JSON'ni bir marta yasaydi. Har ulanish uchun alohida socket.emit — har safar qayta.
  2. Xonani kichik tuting. "Hamma foydalanuvchilar" xonasiga har kichik o'zgarishni yubormang — faqat tegishli loyiha yoki foydalanuvchi xonasiga.
  3. Birlashtiring. 100 ms ichidagi o'zgarishlarni bitta xabarga yig'ing. Inglizcha bu batch (to'plab yuborish) deyiladi; "oxirgi o'zgarishdan keyin 100 ms jimlik bo'lsa yubor" usuli esa debounce.

7. SSE'ni masshtablash

Kanon SseHub hozir bitta jarayon uchun: ulanishlar va tarix xotirada. Ikki nusxada ikki muammo:

  1. A serverda hub.yubor(7, …) B dagi oynaga yetmaydi — Socket.IO'dagi bilan bir xil.
  2. Mijoz A dan uzilib, B ga qayta ulansa, Last-Event-ID tarixi B da yo'q.

Kanon rejasi (loyihaga ulash): yubor hodisani Redis Pub/Sub kanaliga nashr qiladi, har nusxa obuna bo'lib o'z ulanishlariga yozadi; tarix esa Redis Stream da (XADD … MAXLEN ~ 100), qayta ulanishda XRANGE bilan o'qiladi. Stream id lari vaqtga asoslangan va hamma nusxalar uchun bir xil — Last-Event-ID shu id bo'ladi. Kanon modul izohida aynan shunday yozilgan. Bu B bosqichida sinaladi; hozir — reja.

Boshqa yo'l — PostgreSQL LISTEN/NOTIFY (LISTEN/NOTIFY): vazifa yozilgan tranzaksiya bilan birga xabar ketadi. Redis qo'shimcha xizmat bo'lmasa qulay, lekin har nusxa bazaga bitta doimiy ulanish ushlab turadi va xabarlar saqlanmaydi.

8. Ko'p uchraydigan xatolar

Xato Natijasi To'g'risi
ikki nusxa, adapter yo'q xabarlar "tasodifan" yetmaydi Redis adapter
polling yoqilgan, sticky yo'q sid xatolari, ulanish sakraydi sticky yoki transports: ["websocket"]
state recovery + Pub/Sub adapter tiklanmaydi Streams adapter
recovered ni tekshirmaslik uzoq uzilishdan keyin eski holat false da to'liq yangilash
presence faqat disconnect da tozalanadi o'lgan server foydalanuvchilari abadiy onlayn TTL yoki vaqtli Sorted Set
SSE tarixi xotirada, ko'p nusxa Last-Event-ID ishlamaydi Redis Stream
har ulanishga alohida emit JSON qayta-qayta, sekin xonaga bitta emit

9. Mashqlar

Mashqlar kurs/mashqlar/29/41-masshtab/ da.

1-mashq (oson): savollar

  1. Kanon tajribasida adaptersiz fetchSockets() nechta ulanishni ko'rdi va nega?
  2. Qaysi holatda sticky session kerak emas?
  3. Server qulasa, Redis SET'dagi presence nima bo'ladi?
  4. Connection state recovery qaysi adapter bilan ishlamaydi?
Yechim
  1. 1 ta — A faqat o'z xotirasidagi ulanishni biladi; B haqida xabari yo'q.
  2. Mijoz faqat WebSocket transportini ishlatsa — bitta TCP ulanish bitta serverda qoladi. SSE uchun ham.
  3. disconnect ishlamaydi — yozuvlar qoladi, foydalanuvchilar "abadiy onlayn". TTL kerak.
  4. Klassik Redis (Pub/Sub) adapter — Pub/Sub xabarlarni saqlamaydi.

2-mashq (o'rta): fan-out hisobi

fanOut({ azolar, xabarlar, bayt }) funksiyasini yozing: xonadagi a'zolar soni, sekundiga xabarlar va bitta xabar hajmi bo'yicha sekundiga yetkazishlar sonini va MB/s ni (bir kasr bilan) qaytarsin. Uch holat: 10 a'zo × 2 xabar, 2 000 × 2 va 2 000 × 50; xabar 300 bayt.

Yechim
js
function fanOut({ azolar, xabarlar, bayt }) {
  const yetkazish = azolar * xabarlar; // har xabar — har a'zoga
  const mb = (yetkazish * bayt) / 1024 / 1024;
  return `${yetkazish} yetkazish/s, ${mb.toFixed(1)} MB/s`;
}

console.log(fanOut({ azolar: 10, xabarlar: 2, bayt: 300 }));
console.log(fanOut({ azolar: 2000, xabarlar: 2, bayt: 300 }));
console.log(fanOut({ azolar: 2000, xabarlar: 50, bayt: 300 }));

Konsolda:

text
20 yetkazish/s, 0.0 MB/s
4000 yetkazish/s, 1.1 MB/s
100000 yetkazish/s, 28.6 MB/s

Uchinchi holat — 2 000 kishilik guruh chati, hamma faol. Kanonda bitta broadcast 2 000 ga ~100 ms oldi; sekundiga 50 ta shunday broadcast bitta jarayonga og'ir. Shuning uchun katta kanallar (Telegram kanallari kabi) ko'pincha xabarni "push" emas, mijoz o'zi so'raydigan qilib quriladi yoki xabarlar birlashtiriladi.

3-mashq (qiyin): o'z "adapter"ingiz

Bitta jarayonda Redis Pub/Sub'ni modellang. Shina klassi: obuna(fn), nashr(xabar) — hamma obunachilarni chaqiradi. Server klassi: ulan(ulanish, fId), yubor(fId, matn) — shina bo'lsa unga nashr qiladi, bo'lmasa faqat o'z ulanishlariga; shinadan kelgan xabarni o'z ulanishlariga tarqatadi va Server/ulanish oldi: matn chiqaradi. Ikki marta sinang: shinasiz va shina bilan; A da telefon (fId 1), B da noutbuk (fId 1) va boshqa (fId 2); A dan fId 1 ga yuboring.

Yechim
js
// Bitta jarayonda "Redis Pub/Sub" va ikki "server" modeli
class Shina {
  #obunachilar = new Set();
  obuna(fn) { this.#obunachilar.add(fn); }
  nashr(xabar) { for (const fn of this.#obunachilar) fn(xabar); }
}

class Server {
  constructor(nom, shina) {
    this.nom = nom;
    this.shina = shina;
    this.ulanishlar = new Map(); // ulanish → fId
    shina?.obuna((x) => this.#mahalliy(x));
  }

  ulan(ulanish, fId) { this.ulanishlar.set(ulanish, fId); }

  yubor(fId, matn) {
    if (this.shina) this.shina.nashr({ fId, matn });
    else this.#mahalliy({ fId, matn });
  }

  #mahalliy({ fId, matn }) {
    for (const [u, f] of this.ulanishlar) {
      if (f === fId) console.log(`  ${this.nom}/${u} oldi: ${matn}`);
    }
  }
}

for (const shina of [null, new Shina()]) {
  console.log(shina ? "shina bilan:" : "shinasiz:");
  const A = new Server("A", shina);
  const B = new Server("B", shina);
  A.ulan("telefon", 1);
  B.ulan("noutbuk", 1);
  B.ulan("boshqa", 2);
  A.yubor(1, "vazifa yangilandi");
}

Konsolda:

text
shinasiz:
  A/telefon oldi: vazifa yangilandi
shina bilan:
  A/telefon oldi: vazifa yangilandi
  B/noutbuk oldi: vazifa yangilandi

Kanon natijasining aynan o'zi: adaptersiz — faqat [A], adapter bilan — [A, B]. Shina bilan A o'ziga ham shina orqali yetkazadi — bu sodda, lekin har xabar Redis'ga borib qaytadi. Haqiqiy Redis adapter mahalliy ulanishlarga darhol yozadi va faqat boshqa serverlar uchun nashr qiladi; kelgan xabarda o'z ID sini ko'rsa, uni tashlaydi.

10. Loyiha qadami: kanon tajribasi va B rejasi

Bu dars kodi loyihaga hozir kirmaydi: vazifalar-api bitta nusxada, Docker Compose va bir nechta nusxa — 33-qismda. Darsdagi natijalar — kanon o'lchovidan (IlmHamroh, 2026-10; socket.io 4.8.4, Redis 8.10.2, 2026-10-27). Presence modeli va mashqlar — biz ishga tushirdik.

Loyihaga ulashda SseHub uchun reja yuqorida: Redis Pub/Sub (tarqatish) + Redis Stream (tarix). Kanon modul izohida "Bir nechta API nusxasi — tarqat ni Redis Pub/Sub orqali chaqirish (B bosqichi)" deb yozilgan.

11. Real ishda

  • Slack, Discord kabi xizmatlar yuz minglab ochiq ulanishni ko'p "gateway" serverlarga bo'lib, ular o'rtasida ichki shina ishlatadi — g'oya shu darsdagi bilan bir xil.
  • Socket.IO rasmiy adapterlari: Redis, Redis Streams, MongoDB, Postgres, cluster.
  • Kubernetes'da sticky session uchun ingress sozlamasi kerak bo'ladi — polling ishlatsangiz.
  • Intervyuda: "WebSocket serverni qanday masshtablaysiz?", "Sticky session nima uchun?", "Presence'ni qanday qilasiz va server o'lsa nima bo'ladi?"

Xulosa

  • Har server faqat o'z ulanishlarini biladi: kanonda adaptersiz B dagi mijoz xabar olmadi.
  • Redis adapter (Pub/Sub) va Streams adapter — ikkalasida ham A va B oldi; Streams xabarni saqlaydi va state recovery'ni qo'llaydi.
  • Sticky session faqat polling transportida kerak; WebSocket-only va SSE'da — yo'q.
  • Connection state recovery qisqa uzilishni tiklaydi (recovered=true), lekin har doim emas — false da to'liq yangilang.
  • Presence — TTL bilan, aks holda o'lgan server foydalanuvchilari abadiy onlayn.
  • Har ochiq ulanish xotira va fayl deskriptori; fan-out a'zolar soniga ko'paytiriladi.

Keyingi dars: Amaliyot: chat va bildirishnomalar — xabarni bazaga yozish, "yetkazildi" va "o'qildi", oflayn foydalanuvchiga push, "yozmoqda…" va tarixni sahifalash.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

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