4.2 איזון עומס הרצאה
איזון עומס - Load Balancing¶
כשיש לנו כמה שרתים, צריך מישהו שיחליט לאיזה שרת לשלוח כל בקשה. זה ה-Load Balancer.
מה זה Load Balancer?¶
Load Balancer הוא רכיב שיושב בין הלקוחות לשרתים ומפזר בקשות.
מטרות:
1. פיזור עומס שווה בין שרתים
2. ביצוע health checks - הסרת שרתים שנפלו
3. SSL termination - פענוח HTTPS פעם אחת
אלגוריתמי פיזור¶
Round Robin¶
שולחים בקשות לשרתים לפי תור - 1, 2, 3, 1, 2, 3...
class RoundRobinLoadBalancer:
def __init__(self, servers: list[str]):
self.servers = servers
self.current = 0
def get_server(self) -> str:
server = self.servers[self.current]
self.current = (self.current + 1) % len(self.servers)
return server
lb = RoundRobinLoadBalancer(["server1:8000", "server2:8000", "server3:8000"])
print(lb.get_server()) # server1
print(lb.get_server()) # server2
print(lb.get_server()) # server3
print(lb.get_server()) # server1 - מתחיל מחדש
מתי: כשכל הבקשות דומות בכבדותן.
חסרון: לא לוקח בחשבון שבקשה אחת יכולה לקחת 0.1ms ואחרת 10 שניות.
Weighted Round Robin¶
כמו Round Robin, אבל שרתים חזקים יותר מקבלים יותר בקשות.
class WeightedRoundRobinLoadBalancer:
def __init__(self, servers: list[tuple[str, int]]):
# servers: [(address, weight), ...]
self.pool = []
for address, weight in servers:
self.pool.extend([address] * weight)
self.current = 0
def get_server(self) -> str:
server = self.pool[self.current]
self.current = (self.current + 1) % len(self.pool)
return server
lb = WeightedRoundRobinLoadBalancer([
("server1:8000", 3), # שרת חזק - 3x בקשות
("server2:8000", 1), # שרת חלש - 1x בקשות
])
# pool: [server1, server1, server1, server2, server1, server1, server1, server2, ...]
Least Connections¶
שולחים לשרת עם הכי פחות חיבורים פעילים.
class LeastConnectionsLoadBalancer:
def __init__(self, servers: list[str]):
self.connections = {server: 0 for server in servers}
def get_server(self) -> str:
return min(self.connections, key=self.connections.get)
def add_connection(self, server: str):
self.connections[server] += 1
def remove_connection(self, server: str):
self.connections[server] -= 1
מתי: כשבקשות שונות בזמן הטיפול. שרת עסוק לא מקבל בקשות חדשות.
IP Hash¶
שולחים תמיד לאותו שרת לפי כתובת ה-IP של הלקוח.
class IPHashLoadBalancer:
def __init__(self, servers: list[str]):
self.servers = servers
def get_server(self, client_ip: str) -> str:
index = hash(client_ip) % len(self.servers)
return self.servers[index]
מתי: כשצריך session affinity - אותו לקוח צריך תמיד להגיע לאותו שרת (למשל session בזיכרון השרת).
Layer 4 לעומת Layer 7¶
Layer 4 - Transport Layer¶
Load Balancer שרואה רק IP ו-port. לא מבין את תוכן הבקשה.
מהיר מאוד, אבל לא יכול לנתב לפי URL, headers, cookies.
Layer 7 - Application Layer¶
Load Balancer שמבין HTTP. יכול לנתב לפי כל פרמטר של הבקשה.
GET /api/tasks → task-service-cluster
GET /api/users → user-service-cluster
GET /api/reports → report-service-cluster (שרת חזק יותר)
Nginx ו-HAProxy הם load balancers פופולריים. AWS Application Load Balancer הוא Layer 7.
Health Checks¶
Load Balancer צריך לדעת אילו שרתים פעילים.
import requests
import threading
import time
class HealthAwareLoadBalancer:
def __init__(self, servers: list[str]):
self.all_servers = servers
self.healthy_servers = list(servers)
self.current = 0
self._start_health_checks()
def _check_health(self, server: str) -> bool:
try:
response = requests.get(f"http://{server}/health", timeout=2)
return response.status_code == 200
except Exception:
return False
def _health_check_loop(self):
while True:
for server in self.all_servers:
is_healthy = self._check_health(server)
if is_healthy and server not in self.healthy_servers:
print(f"[LB] שרת {server} חזר לפעולה")
self.healthy_servers.append(server)
elif not is_healthy and server in self.healthy_servers:
print(f"[LB] שרת {server} הוסר (לא בריא)")
self.healthy_servers.remove(server)
time.sleep(10)
def _start_health_checks(self):
thread = threading.Thread(target=self._health_check_loop, daemon=True)
thread.start()
def get_server(self) -> str:
if not self.healthy_servers:
raise Exception("אין שרתים זמינים!")
server = self.healthy_servers[self.current % len(self.healthy_servers)]
self.current += 1
return server
Consistent Hashing - Hash עקבי¶
הבעיה: כשמוסיפים או מסירים שרת cache, hash רגיל יגרום ל-invalidation של רוב ה-cache.
מעבר מ-3 ל-4 שרתים: 75% מהkeys ישנו את השרת שלהם.
הפתרון: Consistent Hashing - מניח את השרתים ו-keys על "טבעת":
כל key הולך לשרת הראשון שנמצא "ימינה" ממנו בטבעת.
תוצאה: הוספה/הסרה של שרת אחד מחייבת re-distribution של ~1/N מה-keys בלבד.
Consistent Hashing משמש ב-AWS DynamoDB, Apache Cassandra, ו-Akamai CDN.
סיכום¶
| אלגוריתם | מתאים ל |
|---|---|
| Round Robin | בקשות דומות בכובד |
| Weighted Round Robin | שרתים בעלי כוח שונה |
| Least Connections | בקשות שונות מאוד בזמן |
| IP Hash | session affinity |
| Consistent Hashing | cache distribution |