Operations 33 min read

Common Load Balancing Algorithms and Their Java Implementations

This article introduces fundamental load balancing concepts and examines several static and dynamic algorithms—including round‑robin, weighted, smooth weighted round‑robin, consistent hashing, least‑active, and optimal‑response—providing Java code examples and discussing their advantages, disadvantages, and suitable use cases.

Architect's Guide
Architect's Guide
Architect's Guide
Common Load Balancing Algorithms and Their Java Implementations

Load balancing is a core concept in high‑availability architectures, appearing in micro‑services, middleware, cloud computing, and big‑data stacks. The article first distinguishes static scheduling (fixed configuration) from dynamic scheduling (runtime metrics) and then explores a range of algorithms.

Basic Load Balancing Algorithms

Simple strategies such as round‑robin, random selection, and basic weighted distribution are presented with concise Java implementations.

public class Servers {
    public static List
SERVERS = Arrays.asList(
        "44.120.110.001:8080",
        "44.120.110.002:8081",
        "44.120.110.003:8082"
    );
}

public class RoundRobin {
    private static AtomicInteger requestIndex = new AtomicInteger(0);
    public static String getServer() {
        int index = requestIndex.get() % Servers.SERVERS.size();
        requestIndex.incrementAndGet();
        return Servers.SERVERS.get(index);
    }
}

The round‑robin implementation cycles through the server list using an atomic counter, ensuring even request distribution with minimal overhead.

Weighted Algorithms

Weight‑based strategies enhance basic algorithms by assigning higher traffic to more capable nodes. Two variants are shown: random weighted and round‑robin weighted.

public class RandomWeight {
    static Map
WEIGHT_SERVERS = new LinkedHashMap<>();
    static {
        WEIGHT_SERVERS.put("44.120.110.001:8080", 17);
        WEIGHT_SERVERS.put("44.120.110.002:8081", 11);
        WEIGHT_SERVERS.put("44.120.110.003:8082", 30);
    }
    static Random random = new Random();
    public static String getServer() {
        int weightTotal = WEIGHT_SERVERS.values().stream().mapToInt(Integer::intValue).sum();
        int index = random.nextInt(weightTotal);
        for (Map.Entry
e : WEIGHT_SERVERS.entrySet()) {
            if (index < e.getValue()) return e.getKey();
            index -= e.getValue();
        }
        return null;
    }
}

These implementations calculate the total weight, generate a random offset, and select the server whose cumulative weight covers that offset.

Smooth Weighted Round‑Robin

The smooth weighted round‑robin algorithm solves the uneven distribution problem of the naive weighted round‑robin by maintaining a dynamic weight for each node that is adjusted on every request.

public class Weight {
    private String server;
    private int weight;
    private int currentWeight;
    // constructors, getters, setters omitted for brevity
}

public class SmoothRoundRobin {
    private static Map
weightMap = new HashMap<>();
    private static int weightTotal = 0;
    static { // initialization
        for (Map.Entry
e : Servers.WEIGHT_SERVERS.entrySet()) {
            weightMap.put(e.getKey(), new Weight(e.getKey(), e.getValue(), 0));
            weightTotal += e.getValue();
        }
    }
    public static String getServer() {
        for (Weight w : weightMap.values()) {
            w.setCurrentWeight(w.getCurrentWeight() + w.getWeight());
        }
        Weight max = Collections.max(weightMap.values(), Comparator.comparingInt(Weight::getCurrentWeight));
        max.setCurrentWeight(max.getCurrentWeight() - weightTotal);
        return max.getServer();
    }
}

This algorithm guarantees that over a full cycle each server receives requests proportional to its weight, while distributing them as evenly as possible.

Consistent Hashing

Consistent hashing maps both servers and keys onto a logical ring (2³² points). A request is routed to the first server encountered clockwise from the key’s position, providing stable mapping even when nodes join or leave.

public class ConsistentHash {
    private static final int VIRTUAL_NODES = 160;
    private static TreeMap
virtualNodes = new TreeMap<>();
    static {
        for (String ip : Servers.SERVERS) {
            virtualNodes.put(hash(ip), ip);
            for (int i = 0; i < VIRTUAL_NODES; i++) {
                virtualNodes.put(hash(ip + i), ip);
            }
        }
    }
    public static String getServer(String key) {
        int hash = hash(key);
        SortedMap
tail = virtualNodes.tailMap(hash);
        int nodeKey = tail.isEmpty() ? virtualNodes.firstKey() : tail.firstKey();
        return virtualNodes.get(nodeKey);
    }
    private static int hash(String s) { /* simple custom hash */ }
}

Virtual nodes improve distribution uniformity and reduce hotspot risk.

Dynamic Algorithms

Two adaptive strategies are covered:

Least‑Active : tracks the number of in‑flight requests per server and selects the one with the smallest count, periodically decaying the counters.

Optimal‑Response : issues parallel ping probes to all servers (using CompletableFuture ) and chooses the fastest responder for the actual request.

public class LeastActive {
    public static String getServer() {
        return Servers.SERVERS.stream()
            .min(Comparator.comparingInt(s -> s.getActive().get()))
            .map(Server::getIP)
            .orElse(null);
    }
}

public class ResponseTime {
    private static ExecutorService pool = Executors.newFixedThreadPool(Servers.SERVERS.size());
    public static String getServer() throws InterruptedException {
        List
> futures = Servers.SERVERS.stream()
            .map(s -> CompletableFuture.supplyAsync(s::ping, pool))
            .collect(Collectors.toList());
        return CompletableFuture.anyOf(futures.toArray(new CompletableFuture[0]))
            .thenApply(ip -> (String) ip).join();
    }
}

These methods adapt to real‑time load and latency, offering better performance in heterogeneous environments.

Conclusion

The article compares static and dynamic load‑balancing techniques, highlighting trade‑offs between simplicity (e.g., round‑robin) and intelligence (e.g., smooth weighted or optimal‑response). In high‑throughput scenarios, the simplest algorithm often yields the best overall performance when server capacities are uniform.

Distributed SystemsJavaLoad BalancingAlgorithmsconsistent hashing
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.