Ə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