One door, many servers
When one server isn't enough, you run several identical ones behind a load balancer. Clients talk to one address; the balancer chooses a server for each request — fairly, favouring bigger machines, avoiding busy ones, and never sending traffic to a server that's down.
Strategies
- Round robin — take turns: a, b, c, a, b, c…
- Weighted — a server with weight 3 gets three turns for every one of a weight-1 server.
- Least connections — send the request to whoever is handling the fewest right now.
- Health checks — skip servers marked down until they recover.
function createBalancer(servers, strategy = "round-robin") {
const pool = servers.map((s) => (typeof s === "string" ? { name: s, weight: 1 } : { weight: 1, ...s }));
const active = new Map(pool.map((s) => [s.name, 0]));
const down = new Set();
let turn = 0;
const healthy = () => pool.filter((s) => !down.has(s.name));
return {
pick() {
const up = healthy();
if (up.length === 0) throw new Error("No healthy servers");
let name;
if (strategy === "least-connections") {
name = up.reduce((best, s) => (active.get(s.name) < active.get(best.name) ? s : best)).name;
} else {
const slots = up.flatMap((s) => Array(s.weight).fill(s.name)); // weight 3 = three turns
name = slots[turn++ % slots.length];
}
active.set(name, active.get(name) + 1);
return name;
},
done(name) {
active.set(name, Math.max(0, active.get(name) - 1));
},
markDown(name) {
down.add(name);
},
markUp(name) {
down.delete(name);
},
};
}