Əsas məzmuna keçin

Load Balancer Sistem Dizaynı

Problem Təsviri​

Daxil olan client sorğularını çoxlu serverlər arasında paylaşdıran load balancer dizayn edin. Bu, heç bir serverin həddindən artıq yüklənməməsini təmin edərək tətbiqlərin etibarlılığını və availability-sini artırır.

Tələblər​

  1. Funksional Tələblər:

    • Daxil olan sorğuları çoxlu serverlər arasında paylaşdırmaq
    • Müxtəlif load balancing alqoritmlərini dəstəkləmək
    • Sağlam olmayan serverləre sorğu göndərməmək üçün health checking
    • Serverləri dinamik olaraq əlavə etmək/silmək
  2. Qeyri-Funksional Tələblər:

    • Yüksək availability
    • Aşağı latency
    • Concurrent sorğular üçün thread safety
    • Scalability

Əsas Komponentlər​

  1. Load Balancing Servisi: Seçilmiş alqoritm istifadə edərək sorğuları paylaşdırır
  2. Server Registry: Mövcud serverlərin siyahısını saxlayır
  3. Health Checker: Server sağlamlığını monitör edir
  4. Request Router: Sorğuları seçilmiş serverə yönləndirir

Dizayn Yanaşması​

Müxtəlif load balancing alqoritmlərini tətbiq etmək üçün Strategy Pattern istifadə edəcəyik:

  1. Round Robin: Serverləri ardıcıl olaraq dövr edir
  2. Least Connections: Ən az aktiv connection-a sahib serverə sorğu göndərir
  3. Random Selection: Hər sorğu üçün təsadüfi server seçir
  4. Weighted Round Robin: Round Robin kimi, lakin server weight-ləri ilə

Implementation​

Koda bax
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

// Server class representing a backend server
class Server {
private final String id;
private final String host;
private final int port;
private boolean healthy;
private final AtomicInteger activeConnections;
private final int weight;

public Server(String id, String host, int port, int weight) {
this.id = id;
this.host = host;
this.port = port;
this.healthy = true;
this.activeConnections = new AtomicInteger(0);
this.weight = weight;
}

public String getId() {
return id;
}

public String getHost() {
return host;
}

public int getPort() {
return port;
}

public boolean isHealthy() {
return healthy;
}

public void setHealthy(boolean healthy) {
this.healthy = healthy;
}

public int getActiveConnections() {
return activeConnections.get();
}

public void incrementConnections() {
activeConnections.incrementAndGet();
}

public void decrementConnections() {
activeConnections.decrementAndGet();
}

public int getWeight() {
return weight;
}

@Override
public String toString() {
return host + ":" + port;
}
}

// Strategy interface for load balancing algorithms
interface LoadBalancingStrategy {
Server getNextServer(List<Server> servers);
}

// Round Robin strategy
class RoundRobinStrategy implements LoadBalancingStrategy {
private final AtomicInteger counter = new AtomicInteger(0);

@Override
public Server getNextServer(List<Server> servers) {
if (servers.isEmpty()) {
return null;
}

int index = counter.getAndIncrement() % servers.size();
return servers.get(index);
}
}

// Least Connections strategy
class LeastConnectionsStrategy implements LoadBalancingStrategy {
@Override
public Server getNextServer(List<Server> servers) {
if (servers.isEmpty()) {
return null;
}

return servers.stream()
.min(Comparator.comparingInt(Server::getActiveConnections))
.orElse(null);
}
}

// Random Selection strategy
class RandomSelectionStrategy implements LoadBalancingStrategy {
private final Random random = new Random();

@Override
public Server getNextServer(List<Server> servers) {
if (servers.isEmpty()) {
return null;
}

int index = random.nextInt(servers.size());
return servers.get(index);
}
}

// Weighted Round Robin strategy
class WeightedRoundRobinStrategy implements LoadBalancingStrategy {
private final AtomicInteger counter = new AtomicInteger(0);

@Override
public Server getNextServer(List<Server> servers) {
if (servers.isEmpty()) {
return null;
}

// Calculate total weight
int totalWeight = servers.stream().mapToInt(Server::getWeight).sum();

// Get current position
int position = counter.getAndIncrement() % totalWeight;

// Find the server at this position
int weightSum = 0;
for (Server server : servers) {
weightSum += server.getWeight();
if (position < weightSum) {
return server;
}
}

// Fallback to first server (should not happen)
return servers.get(0);
}
}

// Load Balancer class
class LoadBalancer {
private final Map<String, Server> servers;
private final List<Server> healthyServers;
private LoadBalancingStrategy strategy;
private final ReadWriteLock lock;

public LoadBalancer(LoadBalancingStrategy strategy) {
this.strategy = strategy;
this.servers = new ConcurrentHashMap<>();
this.healthyServers = new ArrayList<>();
this.lock = new ReentrantReadWriteLock();
}

public void setStrategy(LoadBalancingStrategy strategy) {
this.strategy = strategy;
}

public void addServer(Server server) {
lock.writeLock().lock();
try {
servers.put(server.getId(), server);
if (server.isHealthy()) {
healthyServers.add(server);
}
} finally {
lock.writeLock().unlock();
}
}

public void removeServer(String serverId) {
lock.writeLock().lock();
try {
Server server = servers.remove(serverId);
if (server != null) {
healthyServers.remove(server);
}
} finally {
lock.writeLock().unlock();
}
}

public void updateServerHealth(String serverId, boolean healthy) {
lock.writeLock().lock();
try {
Server server = servers.get(serverId);
if (server != null) {
server.setHealthy(healthy);
if (healthy && !healthyServers.contains(server)) {
healthyServers.add(server);
} else if (!healthy) {
healthyServers.remove(server);
}
}
} finally {
lock.writeLock().unlock();
}
}

public Server getNextServer() {
lock.readLock().lock();
try {
if (healthyServers.isEmpty()) {
return null;
}
return strategy.getNextServer(new ArrayList<>(healthyServers));
} finally {
lock.readLock().unlock();
}
}

public void handleRequest(String request) {
Server server = getNextServer();
if (server == null) {
System.out.println("No available servers to handle request: " + request);
return;
}

try {
server.incrementConnections();
System.out.println("Routing request '" + request + "' to server " + server);
// Simulate request processing
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
server.decrementConnections();
}
}
}

// Example usage
public class LoadBalancerDemo {
public static void main(String[] args) {
// Create load balancer with Round Robin strategy
LoadBalancer loadBalancer = new LoadBalancer(new RoundRobinStrategy());

// Add servers
loadBalancer.addServer(new Server("s1", "192.168.1.1", 8080, 1));
loadBalancer.addServer(new Server("s2", "192.168.1.2", 8080, 1));
loadBalancer.addServer(new Server("s3", "192.168.1.3", 8080, 2));

// Process some requests
for (int i = 0; i < 10; i++) {
loadBalancer.handleRequest("Request-" + i);
}

// Change strategy to Least Connections
System.out.println("\nChanging to Least Connections strategy\n");
loadBalancer.setStrategy(new LeastConnectionsStrategy());

// Process more requests
for (int i = 10; i < 20; i++) {
loadBalancer.handleRequest("Request-" + i);
}
}
}

Thread Safety Mülahizələri​

  1. ConcurrentHashMap: Server registry-yə thread-safe giriş üçün istifadə edilir
  2. AtomicInteger: Müxtəlif strategiyalarda thread-safe counter-lər üçün istifadə edilir
  3. ReadWriteLock: Oxuma və yazma zamanı server siyahısını qorumaq üçün istifadə edilir
  4. Immutable Strategy-lər: Strategy implementasiyaları faktiki olaraq immutable-dır

Health Checking​

Real dünya implementasiyasında load balancer hər serverin sağlamlığını dövri olaraq yoxlayardı:

Koda bax
class HealthChecker implements Runnable {
private final LoadBalancer loadBalancer;
private final Map<String, Server> servers;

public HealthChecker(LoadBalancer loadBalancer, Map<String, Server> servers) {
this.loadBalancer = loadBalancer;
this.servers = servers;
}

@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
for (Map.Entry<String, Server> entry : servers.entrySet()) {
String serverId = entry.getKey();
Server server = entry.getValue();
boolean isHealthy = checkServerHealth(server);
loadBalancer.updateServerHealth(serverId, isHealthy);
}

try {
Thread.sleep(5000); // Hər 5 saniyədə bir yoxla
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}

private boolean checkServerHealth(Server server) {
// Real implementasiyada bu serverə health check sorğusu göndərərdi
// Sadəlik üçün təsadüfi yoxlama simulasiya edirik
return Math.random() > 0.1; // 90% ehtimal ilə sağlam
}
}

Scaling Mülahizələri​

  1. Distributed Load Balancer-lər: Paylaşılan state ilə çoxlu load balancer instance-ləri deploy edin
  2. Consistent Hashing: Serverlər əlavə ediləndə/silinəndə daha yaxşı paylaşdırma üçün consistent hashing istifadə edin
  3. Service Discovery: Dinamik server qeydiyyatı üçün service discovery sistemləri ilə inteqrasiya edin