နားလည်ထားရမယ့် အချက်
သေတ္တာသုံးလုံးကို လူတွေ အမြဲ ရောထွေးတတ်ကြပြီး ကွာခြားချက်ဟာ သူတို့ ဘယ်ဘက်ကို ကိုယ်စားပြုလဲ ဆိုတာအပေါ် လုံးဝ မူတည်တယ်။ Forward proxy က client တွေရဲ့ ရှေ့မှာ ထိုင်ပြီး သူတို့ကိုယ်စား လုပ်ဆောင်ပေးတယ် — corporate egress proxy, caching proxy တို့လိုပေါ့။ Server က client ကို မမြင်ဘဲ proxy ကိုသာ မြင်တယ်။ Reverse proxy ကတော့ server တွေရဲ့ ရှေ့မှာ ထိုင်ပြီး သူတို့ကိုယ်စား လုပ်ဆောင်ပေးတယ် — client က သင့် site နဲ့ စကားပြောနေတယ်လို့ ထင်ပြီး၊ ဘယ် backend က တကယ် ဖြေမလဲ ဆိုတာကို proxy က ဆုံးဖြတ်တယ်။ Load balancer ဆိုတာ အဲဒီ traffic ကို ဖြန့်ဝေပေးတာကို အဓိက အလုပ်အဖြစ် ထားတဲ့ reverse proxy တစ်မျိုးပါပဲ။ ကြိုးတူတူ၊ သစ္စာစောင့်တဲ့ဘက် ဆန့်ကျင်ဘက်။
နောက်ထပ် ကွာခြားချက်က သူ အလုပ်လုပ်တဲ့ layer ဖြစ်တယ်။ L4 load balancer က TCP connection တွေကို forward လုပ်တယ်။ Address, port နဲ့ byte တွေကိုသာ မြင်တယ် — တခြားဘာမှ မမြင်ဘူး။ ဒါက သူ့ကို အလွန်မြန်စေပြီး protocol မရွေး အလုပ်လုပ်စေတယ်၊ ဒါပေမယ့် URL path အလိုက် route လုပ်လို့ မရဘူး၊ header ဖတ်လို့ မရဘူး၊ TLS terminate လုပ်လို့လည်း မရဘူး။ L7 load balancer ကတော့ connection ကို terminate လုပ်၊ HTTP ကို parse လုပ်ပြီး /api ကို pool တစ်ခု၊ /images ကို နောက်တစ်ခုဆီ ပို့နိုင်တယ်၊ header တွေ ပြင်နိုင်တယ်၊ idempotent request တွေ retry လုပ်နိုင်တယ်၊ cookie တွေ စစ်နိုင်တယ်။ ကုန်ကျစရိတ်က CPU နဲ့ certificate ကို သူ ကိုင်ထားရမယ် ဆိုတဲ့အချက် ဖြစ်ပြီး၊ ဒါကြောင့်ပဲ L7 balancing နဲ့ TLS termination ဟာ တစ်နေရာတည်းမှာ ရှိနေတာ များတယ်။
Algorithm တွေက လူတွေ ထင်သလောက် အရေးမကြီးဘူး။ Round-robin က request တွေကို ညီညီ ဖြန့်ပေးပြီး၊ request တွေရဲ့ ကုန်ကျမှု တူညီမှသာ load က ညီညီ ဖြန့်သွားမယ်။ Least-connections က အလုပ်အများဆုံး မရှိသေးသူဆီ အလုပ် ပို့ခြင်းဖြင့် မညီညာတဲ့ ကုန်ကျမှုတွေကို လိုက်လျောညီထွေ လုပ်ပေးပြီး၊ များသောအားဖြင့် ပိုကောင်းတဲ့ default ဖြစ်တယ်။ Client IP ဒါမှမဟုတ် header တစ်ခုပေါ် hash လုပ်ခြင်းက client တစ်ယောက်ကို backend တစ်ခုတည်းဆီ အမြဲ ပို့ပေးလို့ shared state မလိုဘဲ cache locality ရတယ်။
Health check တွေက pool ကို ရိုးသားစေတဲ့ အရာ ဖြစ်တယ် — check ကျရှုံးတဲ့ backend တစ်ခုကို ဖယ်ထုတ်လိုက်လို့ crash တစ်ခုဟာ outage မဟုတ်ဘဲ နှေးသွားခြင်းသာ ဖြစ်သွားတယ်။ ပြီးတော့ user တစ်ယောက်ကို backend တစ်ခုဆီ ပင်ထိုးထားတဲ့ sticky session တွေက load balancing ဖြေရှင်းပေးခဲ့တဲ့ ပြဿနာကို တိတ်တဆိတ် ပြန်ခေါ်လာတယ် — ပင်ထိုးထားတဲ့ backend ကို drain လုပ်လို့ မရတော့ဘူး၊ ပြီးတော့ အဲဒါ ဆုံးရှုံးရင် အဲဒီ session တွေ ဆုံးရှုံးတယ်။
LOAD BALANCER TOPOLOGY AND L4 VS L7 VISIBILITY
----------------------------------------------
clients balancer backend pool
------- ---------- --------------
+------------+
[c1] --\ +----------+ /-->| 10.0.1.11 |
\ | | / +------------+
[c2] ----------> | VIP |---+ +------------+
/ | 1.2.3.4 | \--->| 10.0.1.12 |
[c3] --/ | | \ +------------+
+----------+ \ +------------+
| \->| 10.0.1.13 |
| +------------+
\-- health checks --> all three
L4 can see: src IP, dst IP, ports, TCP connection state
-> very fast, works for any protocol
-> CANNOT read a URL path, a header, or a cookie
-> CANNOT terminate TLS
L7 can see: all of the above PLUS method, path, headers,
cookies, and the body
-> route /api and /images to different pools
-> retry a failed idempotent request on another backend
-> must hold the certificate and pay CPU to parse
forward proxy: client -> [PROXY] -> internet (acts FOR client)
reverse proxy: client -> [PROXY] -> servers (acts FOR servers)လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်
လက်တွေ့ scenario တစ်ခု — Tutorial Platform ဟာ balancer တစ်ခုနောက်မှာ application server သုံးလုံး run နေပြီး user တွေက ရံဖန်ရံခါ log out ခံရတယ်။ Team ရဲ့ ပထမဆုံး theory က app ထဲက session bug ဖြစ်တယ်။
များသောအားဖြင့် မဟုတ်ပါဘူး။ Session တွေဟာ login ကို serve လုပ်ခဲ့တဲ့ backend ရဲ့ process memory ထဲမှာ နေထိုင်ပြီး၊ နောက် request က တခြား backend တစ်ခုဆီ ကျသွားလို့ ဖြစ်တာ အမြဲနီးပါးပါပဲ။ ဖြေရှင်းနည်း နှစ်ခု ရှိပြီး တန်းတူ မဟုတ်ဘူး။ Sticky session က user တစ်ယောက်စီကို cookie ဒါမှမဟုတ် source IP နဲ့ backend တစ်ခုဆီ ပင်ထိုးထားတာမို့ လက္ခဏာ ချက်ချင်း ပျောက်သွားတယ်။ ဒါပေမယ့် အဲဒါက backend တစ်ခုကို rotation ကနေ ဖယ်ထုတ်ရင် အဲဒီမှာ ပင်ထိုးထားတဲ့ လူအားလုံး ပြုတ်ကျမယ်၊ load က သက်တမ်းရှည် user တွေ စုမိသွားတဲ့ backend ဘက် စောင်းသွားမယ်၊ ပြီးတော့ deploy တစ်ခုက အနှောင့်အယှက် ဖြစ်လာမယ် ဆိုတာကိုပါ ဆိုလိုတယ်။ ပိုကောင်းတဲ့ ဖြေရှင်းနည်းက session state ကို process ပြင်ပဆီ ရွှေ့ဖို့ပါ — Redis ဒါမှမဟုတ် signed cookie ဆီ — ပြီးရင် backend မှန်သမျှက request မှန်သမျှကို serve လုပ်နိုင်ပြီး stickiness မလိုတော့ဘူး။
ဒုတိယ မှန်အောင် လုပ်သင့်တာက health check ပါ။ TCP connection ဖွင့်ရုံသာ လုပ်တဲ့ check တစ်ခုက process run နေတယ်ဆိုတာကိုသာ ပြောတယ် — အလုပ်လုပ်နေတယ်နဲ့ မတူဘူး။ Database connection ပြတ်သွားတဲ့ backend တစ်ခုဟာ တစ်နေကုန် TCP လက်ခံနေမယ်၊ ပြီးတော့ request တိုင်းကို 500 ပြန်နေမယ် — L4 check တစ်ခုက အဲဒါဆီ traffic ဆက်ပို့နေဦးမယ်။ တကယ့် endpoint တစ်ခုကို တောင်းပြီး 200 လိုတဲ့ L7 check တစ်ခုက ဒါကို ဖမ်းမိတယ်။ Interval နဲ့ failure threshold ကိုလည်း သေချာ ချိန်ပါ — အလွန်အကျွံ ကြပ်လွန်းရင် garbage-collection pause တစ်ခုက ကျန်းမာနေတဲ့ server တစ်လုံးကို ဖယ်ထုတ်ပစ်နိုင်တယ်။
အတူတူ စမ်းရေးကြည့်မယ်
import hashlib
# A backend pool. 'inflight' is how many requests each server is still
# working on -- the number a least-connections balancer actually reads.
BACKENDS = ["10.0.1.11", "10.0.1.12", "10.0.1.13"]
# Hardcoded request costs, so both algorithms see the same workload.
# Request 3 is a slow one: it occupies whichever backend takes it.
REQUESTS = [
("GET /", 1),
("GET /style.css", 1),
("POST /report", 5), # slow
("GET /logo.png", 1),
("GET /about", 1),
("GET /favicon", 1),
]
def round_robin(requests, backends):
inflight = {b: 0 for b in backends}
idx = 0
log = []
for name, cost in requests:
chosen = backends[idx % len(backends)]
idx += 1
inflight[chosen] += cost
log.append((name, chosen))
return log, inflight
def least_connections(requests, backends):
inflight = {b: 0 for b in backends}
log = []
for name, cost in requests:
# min() over a sorted list breaks ties by address, deterministically
chosen = min(sorted(backends), key=lambda b: inflight[b])
inflight[chosen] += cost
log.append((name, chosen))
return log, inflight
def show(title, log, inflight):
print(title)
for name, chosen in log:
print(" " + name.ljust(14) + " -> " + chosen)
print(" load: " + " ".join(b + "=" + str(inflight[b])
for b in BACKENDS))
print("")
show("round-robin", *round_robin(REQUESTS, BACKENDS))
show("least-connections", *least_connections(REQUESTS, BACKENDS))
# Hashing: the same client always lands on the same backend, with no
# shared state between balancers. That is also its weakness -- a hot
# client cannot be spread out.
print("consistent-ish hashing by client IP")
for client in ["192.0.2.7", "198.51.100.4", "203.0.113.5"]:
digest = hashlib.sha256(client.encode()).hexdigest()
chosen = BACKENDS[int(digest, 16) % len(BACKENDS)]
print(" " + client.ljust(14) + " -> " + chosen)round-robin
GET / -> 10.0.1.11
GET /style.css -> 10.0.1.12
POST /report -> 10.0.1.13
GET /logo.png -> 10.0.1.11
GET /about -> 10.0.1.12
GET /favicon -> 10.0.1.13
load: 10.0.1.11=2 10.0.1.12=2 10.0.1.13=6
least-connections
GET / -> 10.0.1.11
GET /style.css -> 10.0.1.12
POST /report -> 10.0.1.13
GET /logo.png -> 10.0.1.11
GET /about -> 10.0.1.12
GET /favicon -> 10.0.1.11
load: 10.0.1.11=3 10.0.1.12=2 10.0.1.13=5
consistent-ish hashing by client IP
192.0.2.7 -> 10.0.1.11
198.51.100.4 -> 10.0.1.12
203.0.113.5 -> 10.0.1.13၅ မိနစ် စမ်းကြည့်
REQUESTS ထဲမှာ 'POST /report' ကုန်ကျမှုကို 5 ကနေ 20 လို့ ပြောင်းပြီး နှစ်ခုလုံး ပြန် run ကြည့်ပါ — round-robin နဲ့ least-connections ကြား load ကွာဟမှု ဘယ်လောက် ကြီးလာလဲ။ ပြီးရင် BACKENDS ထဲက backend တစ်ခုကို ဖယ်လိုက်ပါ၊ hashing နဲ့ ရွေးထားတဲ့ client သုံးယောက်စလုံး နေရာ ပြောင်းသွားလား စစ်ကြည့်ပြီး ဒါက ဘာကြောင့် ပြဿနာလဲ ရေးပါ။
သတိလေးတစ်ချက်
TCP-only health check သုံးခြင်း — database ပြတ်သွားတဲ့ backend တစ်ခုဟာ connection ကို လက်ခံနေဆဲဖြစ်ပြီး request တိုင်းကို 500 ပြန်နေလို့ pool ထဲမှာ ဆက်ကျန်နေတယ်။
Logout ပြဿနာကို session state ကို အပြင်ရွှေ့မယ့်အစား sticky session နဲ့ ဖြေရှင်းခြင်း — deploy အတွက် backend တစ်ခုကို drain လုပ်လို့ မရတော့ဘူး။
Cloudflare Learning Center - What is load balancing? — Computer Networking