Weighted Round Robin Load Balancing in PHP

Weighted Round Robin (WRR) is a load-balancing algorithm that distributes requests among servers based on assigned weights. Servers with higher weights receive more traffic proportionally. This implementation uses an efficient approach leveraging the greatest common divisor (GCD) of all weights to minimize unnecessary iterations.

The core idea is to maintain a dynamic current weight threshold that decreases over cycles. For each request, the algortihm scans the server list starting from the last selected index and picks the first server whose weight meets or exceeds the current threshold. When the scan wraps around to the beginning, the current weight is reduced by the GCD of all weights. If it drops to zero or below, it resets to the maximum weight in the set.

<?php

class WeightedRoundRobin
{
    private static array $servers = [];
    private static int $currentIndex = -1;
    private static int $gcd = 1;
    private static int $currentWeight = 0;
    private static int $maxWeight = 0;
    private static int $totalServers = 0;

    public function __construct(array $serverList)
    {
        foreach ($serverList as $server) {
            if (!isset($server['id']) || !isset($server['weight']) || $server['weight'] <= 0) {
                throw new InvalidArgumentException('Each server must have a valid positive weight.');
            }
        }

        self::$servers = $serverList;
        self::$totalServers = count($serverList);
        $weights = array_column($serverList, 'weight');
        self::$maxWeight = max($weights);
        self::$gcd = self::calculateGcd($weights);
        self::$currentWeight = self::$maxWeight;
    }

    public function getNextServer(): ?array
    {
        while (true) {
            self::$currentIndex = (self::$currentIndex + 1) % self::$totalServers;

            // Reset current weight when completing a full cycle
            if (self::$currentIndex === 0) {
                self::$currentWeight -= self::$gcd;
                if (self::$currentWeight <= 0) {
                    self::$currentWeight = self::$maxWeight;
                    if (self::$currentWeight === 0) {
                        return null; // No valid servers
                    }
                }
            }

            if (self::$servers[self::$currentIndex]['weight'] >= self::$currentWeight) {
                return self::$servers[self::$currentIndex];
            }
        }
    }

    private static function calculateGcd(array $numbers): int
    {
        $gcd = array_reduce($numbers, function ($a, $b) {
            while ($b != 0) {
                $temp = $b;
                $b = $a % $b;
                $a = $temp;
            }
            return $a;
        });

        return $gcd ?: 1;
    }
}

To test the distirbution accuracy, run multiple selections and verify that the frequency of each server aligns with its relative weight:

$servers = [
    ['id' => 'A', 'weight' => 3],
    ['id' => 'B', 'weight' => 3],
    ['id' => 'C', 'weight' => 6],
    ['id' => 'D', 'weight' => 4],
    ['id' => 'E', 'weight' => 2],
];

$balancer = new WeightedRoundRobin($servers);

$stats = ['A' => 0, 'B' => 0, 'C' => 0, 'D' => 0, 'E' => 0];

for ($i = 0; $i < 100; $i++) {
    $selected = $balancer->getNextServer();
    $stats[$selected['id']]++;
}

foreach ($stats as $id => $count) {
    echo "$id: $count\n";
}

This implementation ensures smooth, proportional distribution without building large intermediate arrays, making it memory-efficient even for high-weight configurations.

Tags: PHP LoadBalancing WeightedRoundRobin gcd algorithms

Posted on Wed, 02 Sep 2026 16:13:26 +0000 by bigwatercar