19-bo‘lim
Proksi, balanslovchi, CDN va P2P
To'g'ri va teskari proksi, yuk balanslash usullari, CDN nima uchun kechikishni kamaytiradi va P2P ning o'rni.
Ushbu bo‘lim mundarijasi
Shu paytgacha ikki tomon bor edi: mijoz va server. Haqiqiy tizimlarda ular orasida yana bir nechta qatlam turadi.
Proksi - oradagi vositachi #
Proksi ikki xil bo'ladi va ular butunlay boshqa vazifani bajaradi:
| To'g'ri proksi | Teskari proksi | |
|---|---|---|
| Kim uchun ishlaydi | Mijoz uchun | Server uchun |
| Kim biladi | Mijoz sozlagan | Mijoz bilmaydi |
| Odatiy vazifasi | Filtrlash, kesh, anonimlik | TLS, balanslash, himoya |
| Misol | Korxona proksisi | nginx, Cloudflare |
Teskari proksi bugun deyarli har bir saytda bor. Uni yozib ko'ramiz:
import socket
import threading
ORQA_PORT = 9970 # haqiqiy ilova
PROKSI_PORT = 9971 # mijoz shu yerga ulanadi
# --- orqadagi ilova
orqa = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
orqa.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
orqa.bind(("127.0.0.1", ORQA_PORT))
orqa.listen(4)
def ilova():
u, _ = orqa.accept()
sorov = u.recv(4096).decode()
yol = sorov.split(" ")[1]
tana = f"ilova javobi, yo'l: {yol}".encode()
u.sendall(b"HTTP/1.1 200 OK\r\nContent-Length: "
+ str(len(tana)).encode() + b"\r\n\r\n" + tana)
u.close()
# --- proksi
proksi = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
proksi.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
proksi.bind(("127.0.0.1", PROKSI_PORT))
proksi.listen(4)
jurnal = []
def proksi_ishla():
mijoz_ulanishi, kim = proksi.accept()
sorov = mijoz_ulanishi.recv(4096)
jurnal.append(sorov.split(b"\r\n")[0].decode())
# Orqadagi ilovaga uzatamiz - va o'zimiz haqimizda sarlavha qo'shamiz
qoshimcha = b"X-Forwarded-For: 127.0.0.1\r\n"
uzatiladigan = sorov.replace(b"\r\n\r\n", b"\r\n" + qoshimcha + b"\r\n")
orqaga = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
orqaga.connect(("127.0.0.1", ORQA_PORT))
orqaga.sendall(uzatiladigan)
javob = b""
while True:
b_ = orqaga.recv(4096)
if not b_:
break
javob += b_
orqaga.close()
mijoz_ulanishi.sendall(javob)
mijoz_ulanishi.close()
t1 = threading.Thread(target=ilova, daemon=True)
t2 = threading.Thread(target=proksi_ishla, daemon=True)
t1.start()
t2.start()
# --- mijoz PROKSIga ulanadi, ilovani umuman bilmaydi
m = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
m.settimeout(5)
m.connect(("127.0.0.1", PROKSI_PORT))
m.sendall(b"GET /darslik HTTP/1.1\r\nHost: sayt.uz\r\n\r\n")
javob = b""
while True:
b_ = m.recv(4096)
if not b_:
break
javob += b_
m.close()
t1.join()
t2.join()
orqa.close()
proksi.close()
sarlavhalar, tana = javob.decode().split("\r\n\r\n", 1)
print("mijoz ulangan port:", PROKSI_PORT)
print("javob birinchi qatori:", sarlavhalar.split("\r\n")[0])
print("javob tanasi:", tana)
print()
print("proksi jurnali:", jurnal)
print("mijoz orqadagi port", ORQA_PORT, "ni bilmaydi ham")
mijoz ulangan port: 9971
javob birinchi qatori: HTTP/1.1 200 OK
javob tanasi: ilova javobi, yo'l: /darslik
proksi jurnali: ['GET /darslik HTTP/1.1']
mijoz orqadagi port 9970 ni bilmaydi ham
Ellik qatorlik kod - va bu aslida nginx qiladigan ishning mag'zi. Qolgani sozlanish, tezlik va xatolarni ishlash.
X-Forwarded-For ga ehtiyot bo'lib ishoningProksi orqada turgan ilovaga mijozning haqiqiy manzilini
X-Forwarded-For sarlavhasida yetkazadi - chunki ilova
uchun ulanish proksidan kelayotgandek ko'rinadi.
Muammo shundaki, bu shunchaki sarlavha - uni har kim yozib yuborishi mumkin.
| Holat | Ishonish mumkinmi |
|---|---|
| Ilova faqat proksi orqali ochiq | Ha - proksi qo'ygan qiymatga |
| Ilova to'g'ridan-to'g'ri ham ochiq | Yo'q |
| Bir necha proksi zanjiri | Faqat oxirgisiga |
Shuning uchun ilova X-Forwarded-For ni faqat ishonchli
proksidan kelgan bo'lsa qabul qilishi kerak. Aks holda
kimdir o'zini boshqa manzildan kelayotgandek ko'rsatib,
IP bo'yicha cheklovlarni chetlab o'tadi.
Bu "Kiberxavfsizlik asoslari" darsligining 5-bo'limidagi kirish nazorati mavzusiga bevosita bog'liq.
Yuk balanslash #
Bitta server yetmay qolganda, bir nechtasi qo'yiladi. Savol - so'rovni qaysi biriga berish kerak?
class Balanslovchi:
def __init__(self, serverlar):
self.serverlar = serverlar
self.joriy = 0
self.ulanishlar = {s: 0 for s in serverlar}
def navbat_bilan(self):
s = self.serverlar[self.joriy % len(self.serverlar)]
self.joriy += 1
return s
def eng_bosh(self):
return min(self.serverlar, key=lambda s: self.ulanishlar[s])
SERVERLAR = ["server-1", "server-2", "server-3"]
b = Balanslovchi(SERVERLAR)
print("navbat bilan (round-robin):")
for i in range(1, 7):
print(f" {i}-so'rov -> {b.navbat_bilan()}")
print()
print("eng bo'sh serverga (least connections):")
b2 = Balanslovchi(SERVERLAR)
# server-1 ga uchta uzoq ulanish osilib qolgan
b2.ulanishlar["server-1"] = 3
b2.ulanishlar["server-2"] = 1
b2.ulanishlar["server-3"] = 0
for i in range(1, 7):
s = b2.eng_bosh()
b2.ulanishlar[s] += 1
holat = " ".join(f"{k.split('-')[1]}:{v}"
for k, v in b2.ulanishlar.items())
print(f" {i}-so'rov -> {s} yuk: {holat}")
print()
print("navbat bilan usuli yukni HISOBGA OLMAYDI")
print("eng bo'sh usuli esa band serverni chetlab o'tadi")
navbat bilan (round-robin):
1-so'rov -> server-1
2-so'rov -> server-2
3-so'rov -> server-3
4-so'rov -> server-1
5-so'rov -> server-2
6-so'rov -> server-3
eng bo'sh serverga (least connections):
1-so'rov -> server-3 yuk: 1:3 2:1 3:1
2-so'rov -> server-2 yuk: 1:3 2:2 3:1
3-so'rov -> server-3 yuk: 1:3 2:2 3:2
4-so'rov -> server-2 yuk: 1:3 2:3 3:2
5-so'rov -> server-3 yuk: 1:3 2:3 3:3
6-so'rov -> server-1 yuk: 1:4 2:3 3:3
navbat bilan usuli yukni HISOBGA OLMAYDI
eng bo'sh usuli esa band serverni chetlab o'tadi
Asosiy usullar:
| Usul | Qachon yaxshi |
|---|---|
| Navbat bilan | So'rovlar bir xil og'irlikda |
| Eng bo'sh | So'rovlar turli davomiylikda |
| Xesh bo'yicha | Mijoz doim bir serverga tushishi kerak |
| Tasodifiy (ikkitadan) | Katta miqyosda, hisob yuritmasdan |
Oxirgisi qiziq: ikkita serverni tasodifan tanlab, ulardan bo'shrog'ini olish - deyarli "eng bo'sh" darajasida yaxshi natija beradi, lekin butun ro'yxatni tekshirish kerak emas.
Serverlardan biri ishdan chiqsa, balanslovchi buni bilmasa, u so'rovlarni o'sha yerga yuborishda davom etadi.
Natijada har uchinchi foydalanuvchi xato oladi.
Shuning uchun har balanslovchida sog'liq tekshiruvi bo'ladi:
| Sozlama | Ma'nosi |
|---|---|
| Yo'l | /health kabi yengil manzil |
| Davriylik | Har 2-5 soniyada |
| Nosoz deb hisoblash | Ketma-ket 2-3 xatodan keyin |
| Qaytarish | Ketma-ket 2-3 muvaffaqiyatdan keyin |
Tekshiruv manzili yengil bo'lishi kerak: agar u bazaga so'rov yuborsa, tekshiruvning o'zi yuk yaratadi.
CDN - kechikishga qarshi yagona qurol #
16-bo'limda ko'rgandik: tarqalish kechikishini kamaytirib bo'lmaydi. Uni faqat masofani qisqartirish bilan kamaytirish mumkin.
CDN aynan shuni qiladi: nusxalarni dunyo bo'ylab tarqatadi va foydalanuvchini eng yaqiniga yo'naltiradi.
| CDN siz | CDN bilan | |
|---|---|---|
| Masofa | Toshkent → Frankfurt | Toshkent → Toshkent |
| Kechikish | ~80 ms | ~5 ms |
| Asosiy serverdagi yuk | 100% | Faqat kesh yangilanishi |
| Server ishdan chiqsa | Sayt yotadi | Kesh hali xizmat qiladi |
CDN odatda statik fayllar uchun ishlatiladi: rasm, CSS, JS, video. Dinamik sahifalarni keshlash ancha nozik ish - foydalanuvchiga boshqa birovning ma'lumotini ko'rsatib qo'yish xavfi bor.
Ikki asosiy usul bor:
| Usul | Qanday ishlaydi |
|---|---|
| DNS orqali | Bir xil nomga turli hududda turli IP qaytariladi |
| Anycast | Bir xil IP manzil ko'p joyda e'lon qilinadi |
Ikkinchisi qiziqroq: bir necha o'nlab ma'lumot markazi aynan bir xil IP manzilni e'lon qiladi va marshrutlash (5-bo'lim) paketni eng yaqiniga olib boradi.
Ildiz DNS serverlari ham shunday ishlaydi: manzillari 13 ta, lekin jismoniy serverlari yuzlab.
P2P - serversiz almashinuv #
Mijoz-server sxemasining muqobili - qatnashchilar bir-biriga to'g'ridan-to'g'ri ulanadi.
| Mijoz-server | P2P | |
|---|---|---|
| Ma'lumot qayerda | Markazda | Qatnashchilarda |
| Yuk o'sganda | Server kuchaytiriladi | O'zi kuchayadi |
| Boshqarish | Oson | Qiyin |
| NAT ortida ishlashi | Muammo yo'q | Asosiy qiyinchilik |
Oxirgi qator 10-bo'lim bilan bog'liq: ikkala tomon ham NAT ortida bo'lsa, ular bir-biriga to'g'ridan-to'g'ri ulana olmaydi.
Yechim - teshik ochish (hole punching): ikkala tomon ham tashqaridagi umumiy serverga (STUN) ulanadi, u ularning tashqi manzil va portlarini bir-biriga aytadi, keyin ular bir vaqtda bir-biriga urinib ko'radi.
Ishlamasa - trafik oraliq server (TURN) orqali o'tadi. Video qo'ng'iroqlarda sifat ba'zan shuning uchun pasayadi.
- Teskari proksi misolini ishga tushiring.
- Proksiga ikkinchi orqa server qo'shing va navbat bilan taqsimlang.
- Proksida so'rov yo'liga qarab turli serverga yo'naltiring.
- Javobga
X-Proxy: biznikisarlavhasini qo'shing. - Balanslovchi misolini ishga tushiring.
- Unga "xesh bo'yicha" usulini qo'shing.
- Sog'liq tekshiruvini qo'shing: nosoz serverni ro'yxatdan chiqaring.
curl -Ibilan biror sayt CDN ortidami yoki yo'qligini tekshiring.- CDN kechikishni qanday kamaytirishini uch qatorda yozing.
- P2P mos keladigan va kelmaydigan bittadan holat yozing.
Xulosa #
- To'g'ri proksi mijoz uchun, teskari proksi server uchun ishlaydi.
- Teskari proksi TLS ni uzadi, yukni taqsimlaydi va ilovani yashiradi.
- Ellik qator kod - nginx qiladigan ishning asosiy mag'zi.
X-Forwarded-Forga faqat ishonchli proksidan kelganda ishoning.- Balanslash usullari: navbat bilan, eng bo'sh, xesh bo'yicha, ikkitadan tasodifiy.
- Sog'liq tekshiruvi bo'lmasa, balanslovchi nosoz serverga so'rov yuboraveradi.
- Tekshiruv manzili yengil bo'lsin - aks holda o'zi yuk yaratadi.
- CDN kechikishni masofani qisqartirish orqali kamaytiradi - boshqa yo'l yo'q.
- Eng yaqin nusxa DNS yoki anycast orqali tanlanadi.
- P2P yuk oshganda o'zi kuchayadi, lekin NAT ortida ishlash asosiy qiyinchilik.
Keyingi, yakuniy bo'limda hamma o'rganganimizni birlashtirib, xom soketlar ustida o'z HTTP serveringizni yozamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.