Core Load Balancing Strategies and Implementation Patterns

Load balancing distributes network traffic or compuattional tasks across a cluster of servers. This ensures optimal resource utilization, minimizes response times, and prevents system bottlenecks. Below are six fundamental algorithms used in modern distributed architectures.

1. Round Robin

The Round Robin strategy cycles through the available server list sequentially. It treats every node equally, ignoring current hardware load or connection count.

import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

public class SimpleRoundRobin {
    private final List<String> nodeAddresses;
    private final AtomicLong counter = new AtomicLong(0);

    public SimpleRoundRobin(List<String> nodeAddresses) {
        this.nodeAddresses = nodeAddresses;
    }

    public String getNextNode() {
        int pos = (int) (counter.getAndIncrement() % nodeAddresses.size());
        return nodeAddresses.get(Math.abs(pos));
    }
}

2. Session-Sticky Balancing

This variation ensures that requests from a specific client consistently reach the same backend server. This is critical for applications that maintain stateful sessions locally.

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class StickyLoadBalancer {
    private final List<String> nodes;
    private final Map<String, String> sessionStore = new ConcurrentHashMap<>();
    private int cursor = 0;

    public StickyLoadBalancer(List<String> nodes) { this.nodes = nodes; }

    public synchronized String getRoute(String sessionId) {
        return sessionStore.computeIfAbsent(sessionId, k -> {
            String node = nodes.get(cursor % nodes.size());
            cursor++;
            return node;
        });
    }
}

3. Weighted Round Robin

When backend hardware varies in performance, Weighted Round Robin assigns a capacity value to each server. Nodes with higher capacity receive a proportionally larger share of traffic.

import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

public class WeightedBalancer {
    private final Map<String, Integer> registry;
    private final int totalWeight;

    public WeightedBalancer(Map<String, Integer> registry) {
        this.registry = registry;
        this.totalWeight = registry.values().stream().mapToInt(Integer::intValue).sum();
    }

    public String selectNode() {
        int target = ThreadLocalRandom.current().nextInt(totalWeight);
        for (Map.Entry<String, Integer> entry : registry.entrySet()) {
            target -= entry.getValue();
            if (target < 0) return entry.getKey();
        }
        return null;
    }
}

4. Consistent Hash Distribution

This algorithm uses a hashing function on the client identity (such as IP address) to determine the target server. This mapping remains deterministic, ensuring that a specific source is routed to a consistent destination.

import java.util.List;

public class HashBalancer {
    private final List<String> nodes;

    public HashBalancer(List<String> nodes) { this.nodes = nodes; }

    public String routeRequest(String clientKey) {
        int hash = Math.abs(clientKey.hashCode());
        return nodes.get(hash % nodes.size());
    }
}

5. Least Connections

The Least Connections algorithm dynamically routes new tasks to the server currently handling the fewest active requests, preventing hotspots.

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.LongAdder;

public class LeastConnectionsBalancer {
    private final ConcurrentHashMap<String, LongAdder> loadMap = new ConcurrentHashMap<>();

    public void registerNode(String node) { loadMap.put(node, new LongAdder()); }

    public String getOptimalNode() {
        return loadMap.entrySet().stream()
                .min(Comparator.comparingLong(e -> e.getValue().sum()))
                .map(Map.Entry::getKey)
                .orElseThrow();
    }

    public void increment(String node) { loadMap.get(node).increment(); }
    public void decrement(String node) { loadMap.get(node).decrement(); }
}

6. Least Response Time

This intelligent approach monitors the actual latency of servers. By selecting the node that responds fastest, the load balancer effectively steers traffic away from lagging or congested nodes.

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class LatencyAwareBalancer {
    private final Map<String, Double> latencyMetrics = new ConcurrentHashMap<>();

    public void updateLatency(String node, double latencyMs) {
        latencyMetrics.put(node, latencyMs);
    }

    public String selectFastest() {
        return latencyMetrics.entrySet().stream()
                .min(Map.Entry.comparingByValue())
                .map(Map.Entry::getKey)
                .orElse(null);
    }
}

Tags: Load Balancing Architecture System Design Distributed Systems

Posted on Thu, 03 Sep 2026 16:39:03 +0000 by seavers