Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. Ikki server: adaptersiz va adapter bilan
- 2.1 Kanon tajribasi
- 2.2 Ulash
- 2.3 Pub/Sub va Streams farqi
- 3. Sticky session
- 3.1 Qachon kerak
- 3.2 SSE uchun
- 4. Connection state recovery
- 4.1 Kanon tajribasi
- 4.2 Cheklovlari
- 5. Presence: kim onlayn
- 5.1 Kanon: Redis SET
- 5.2 Muammo: server o'lsa
- 5.3 Presence qimmat
- 6. Ulanish narxi va fan-out
- 6.1 Xotira
- 6.2 Fan-out
- 7. SSE'ni masshtablash
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): savollar
- 2-mashq (o'rta): fan-out hisobi
- 3-mashq (qiyin): o'z "adapter"ingiz
- 10. Loyiha qadami: kanon tajribasi va B rejasi
- 11. Real ishda
- Xulosa
- Manbalar
Realtime'ni masshtablash: Redis adapter, sticky session va presence
Qisqacha: Bitta server o'z ulanishlarini biladi, xolos. Ikki server bo'lsa, A serverdagi
emitB 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.
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): 2Nima 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):
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:
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:
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.recoveredfalse 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;
maxDisconnectionDurationni oqilona qiymatga qo'ying (Infinityemas); - klassik Redis (Pub/Sub) adapter bilan ishlamaydi;
skipMiddlewares: trueehtiyotkorlik 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):
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));
}
});
});onlayn: 1, 2fetchSockets() — 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:
// 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:
40 s: 1, 2, 3
70 s: 1B 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.
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 msServer 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:
- Bir marta serialize.
io.to(xona).emit(x)JSON'ni bir marta yasaydi. Har ulanish uchun alohidasocket.emit— har safar qayta. - Xonani kichik tuting. "Hamma foydalanuvchilar" xonasiga har kichik o'zgarishni yubormang — faqat tegishli loyiha yoki foydalanuvchi xonasiga.
- 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:
- A serverda
hub.yubor(7, …)B dagi oynaga yetmaydi — Socket.IO'dagi bilan bir xil. - Mijoz A dan uzilib, B ga qayta ulansa,
Last-Event-IDtarixi 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
- Kanon tajribasida adaptersiz
fetchSockets()nechta ulanishni ko'rdi va nega? - Qaysi holatda sticky session kerak emas?
- Server qulasa, Redis SET'dagi presence nima bo'ladi?
- Connection state recovery qaysi adapter bilan ishlamaydi?
Yechim
- 1 ta — A faqat o'z xotirasidagi ulanishni biladi; B haqida xabari yo'q.
- Mijoz faqat WebSocket transportini ishlatsa — bitta TCP ulanish bitta serverda qoladi. SSE uchun ham.
disconnectishlamaydi — yozuvlar qoladi, foydalanuvchilar "abadiy onlayn". TTL kerak.- 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
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:
20 yetkazish/s, 0.0 MB/s
4000 yetkazish/s, 1.1 MB/s
100000 yetkazish/s, 28.6 MB/sUchinchi 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
// 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:
shinasiz:
A/telefon oldi: vazifa yangilandi
shina bilan:
A/telefon oldi: vazifa yangilandi
B/noutbuk oldi: vazifa yangilandiKanon 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 —falseda 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
- Socket.IO, Using multiple nodes (sticky session): https://socket.io/docs/v4/using-multiple-nodes/ — 2026-10-28 da o'qildi
- Socket.IO, Connection state recovery (adapterlar bilan moslik): https://socket.io/docs/v4/connection-state-recovery — 2026-10-28 da o'qildi
- Redis adapter: https://socket.io/docs/v4/redis-adapter/ va Redis Streams adapter: https://socket.io/docs/v4/redis-streams-adapter/
- Redis Streams: https://redis.io/docs/latest/develop/data-types/streams/
- Kanon tajribasi (IlmHamroh, 2026-10-27): socket.io 4.8.4, @socket.io/redis-adapter 8.3.0, @socket.io/redis-streams-adapter 0.3.1, redis (node-redis) 6.3.0, Redis 8.10.2, Node 24.21.0.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!