What is a Load Balancer?
Load Balancer is a system that distributes incoming network traffic across multiple backend servers to ensure optimal resource utilization, minimize response time, prevent server overload, and provide high availability. Load balancers are essential for scaling applications and maintaining reliability under heavy traffic.
Load Balancing Algorithms
Common Algorithms
interface LoadBalancingAlgorithms {
roundRobin: {
description: 'Distribute requests sequentially';
pros: 'Simple, fair distribution';
cons: 'Ignores server load';
useCase: 'Servers with equal capacity';
};
leastConnections: {
description: 'Route to server with fewest active connections';
pros: 'Accounts for server load';
cons: 'More complex tracking';
useCase: 'Varying request duration';
};
leastResponseTime: {
description: 'Route to fastest responding server';
pros: 'Optimizes performance';
cons: 'Requires monitoring';
useCase: 'Performance-critical apps';
};
ipHash: {
description: 'Hash client IP to determine server';
pros: 'Session persistence';
cons: 'Uneven distribution possible';
useCase: 'Stateful applications';
};
weighted: {
description: 'Distribute based on server capacity weights';
pros: 'Handles different server specs';
cons: 'Requires weight configuration';
useCase: 'Heterogeneous servers';
};
}
Implementation
Round Robin Load Balancer
class RoundRobinLoadBalancer {
private servers: string[];
private currentIndex: number = 0;
constructor(servers: string[]) {
this.servers = servers;
}
getNextServer(): string {
const server = this.servers[this.currentIndex];
this.currentIndex = (this.currentIndex + 1) % this.servers.length;
return server;
}
async handleRequest(req: any, res: any) {
const server = this.getNextServer();
console.log(`Routing to: ${server}`);
try {
const response = await fetch(`${server}${req.url}`, {
method: req.method,
headers: req.headers,
body: req.body
});
const data = await response.text();
res.send(data);
} catch (error) {
console.error(`Error routing to ${server}:`, error);
res.status(502).send('Bad Gateway');
}
}
}
// Usage
const lb = new RoundRobinLoadBalancer([
'http://server1:3000',
'http://server2:3000',
'http://server3:3000'
]);
Least Connections Load Balancer
interface ServerMetrics {
url: string;
activeConnections: number;
totalRequests: number;
}
class LeastConnectionsLoadBalancer {
private servers: Map<string, ServerMetrics>;
constructor(serverUrls: string[]) {
this.servers = new Map();
serverUrls.forEach(url => {
this.servers.set(url, {
url,
activeConnections: 0,
totalRequests: 0
});
});
}
getNextServer(): string {
let selected: ServerMetrics | null = null;
let minConnections = Infinity;
this.servers.forEach(server => {
if (server.activeConnections < minConnections) {
minConnections = server.activeConnections;
selected = server;
}
});
return selected!.url;
}
async handleRequest(req: any, res: any) {
const serverUrl = this.getNextServer();
const server = this.servers.get(serverUrl)!;
server.activeConnections++;
server.totalRequests++;
try {
const response = await fetch(`${serverUrl}${req.url}`, {
method: req.method,
headers: req.headers
});
const data = await response.text();
res.send(data);
} finally {
server.activeConnections--;
}
}
getStats() {
const stats: any[] = [];
this.servers.forEach(server => {
stats.push({
url: server.url,
activeConnections: server.activeConnections,
totalRequests: server.totalRequests
});
});
return stats;
}
}
Weighted Load Balancer
interface WeightedServer {
url: string;
weight: number;
currentWeight: number;
}
class WeightedLoadBalancer {
private servers: WeightedServer[];
constructor(serverConfigs: Array<{ url: string; weight: number }>) {
this.servers = serverConfigs.map(config => ({
...config,
currentWeight: 0
}));
}
getNextServer(): string {
let selected: WeightedServer | null = null;
let totalWeight = 0;
this.servers.forEach(server => {
server.currentWeight += server.weight;
totalWeight += server.weight;
if (!selected || server.currentWeight > selected.currentWeight) {
selected = server;
}
});
if (selected) {
selected.currentWeight -= totalWeight;
return selected.url;
}
return this.servers[0].url;
}
}
// Usage - Server 1 gets 3x more traffic than others
const lb = new WeightedLoadBalancer([
{ url: 'http://powerful-server:3000', weight: 3 },
{ url: 'http://normal-server1:3000', weight: 1 },
{ url: 'http://normal-server2:3000', weight: 1 }
]);
Health Checks
Active Health Monitoring
class HealthCheckLoadBalancer {
private servers: Map<string, {
url: string;
isHealthy: boolean;
lastCheck: number;
failureCount: number;
}>;
private healthCheckInterval: number = 10000; // 10 seconds
private maxFailures: number = 3;
constructor(serverUrls: string[]) {
this.servers = new Map();
serverUrls.forEach(url => {
this.servers.set(url, {
url,
isHealthy: true,
lastCheck: 0,
failureCount: 0
});
});
this.startHealthChecks();
}
private startHealthChecks() {
setInterval(() => {
this.servers.forEach((server, url) => {
this.checkHealth(url);
});
}, this.healthCheckInterval);
}
private async checkHealth(serverUrl: string) {
const server = this.servers.get(serverUrl);
if (!server) return;
try {
const response = await fetch(`${serverUrl}/health`, {
signal: AbortSignal.timeout(5000)
});
if (response.ok) {
server.isHealthy = true;
server.failureCount = 0;
console.log(`✓ ${serverUrl} is healthy`);
} else {
this.handleFailure(server);
}
} catch (error) {
this.handleFailure(server);
}
server.lastCheck = Date.now();
}
private handleFailure(server: any) {
server.failureCount++;
if (server.failureCount >= this.maxFailures) {
server.isHealthy = false;
console.error(`✗ ${server.url} marked unhealthy`);
}
}
getHealthyServers(): string[] {
return Array.from(this.servers.values())
.filter(s => s.isHealthy)
.map(s => s.url);
}
}
Session Persistence
Sticky Sessions
class StickySessionLoadBalancer {
private servers: string[];
private sessions: Map<string, string> = new Map();
constructor(servers: string[]) {
this.servers = servers;
}
getServerForSession(sessionId: string): string {
// Check if session exists
if (this.sessions.has(sessionId)) {
return this.sessions.get(sessionId)!;
}
// Assign new session to server
const server = this.selectServer();
this.sessions.set(sessionId, server);
return server;
}
private selectServer(): string {
// Use consistent hashing or round-robin
return this.servers[Math.floor(Math.random() * this.servers.length)];
}
async handleRequest(req: any, res: any) {
const sessionId = req.cookies?.sessionId || this.generateSessionId();
const server = this.getServerForSession(sessionId);
// Set session cookie if new
if (!req.cookies?.sessionId) {
res.cookie('sessionId', sessionId, {
httpOnly: true,
maxAge: 86400000 // 24 hours
});
}
// Forward to assigned server
const response = await fetch(`${server}${req.url}`);
const data = await response.text();
res.send(data);
}
private generateSessionId(): string {
return Math.random().toString(36).substring(7);
}
}
Layer 4 vs Layer 7 Load Balancing
Comparison
interface LoadBalancerLayers {
layer4: {
name: 'Transport Layer (TCP/UDP)';
inspection: 'IP addresses and ports only';
speed: 'Very fast';
features: ['TCP/UDP routing', 'Connection-based'];
limitations: ['No HTTP inspection', 'No path-based routing'];
useCase: 'High-performance, simple routing';
};
layer7: {
name: 'Application Layer (HTTP)';
inspection: 'Full HTTP request (headers, path, etc.)';
speed: 'Slower (more processing)';
features: ['Path-based routing', 'Header inspection', 'Cookie-based routing'];
limitations: ['Higher latency', 'More CPU intensive'];
useCase: 'Complex routing, microservices';
};
}
Nginx Load Balancer Configuration
upstream backend {
# Load balancing algorithm
least_conn; # or ip_hash, or round-robin (default)
# Backend servers
server backend1.example.com:8080 weight=3;
server backend2.example.com:8080 weight=1;
server backend3.example.com:8080 weight=1;
# Health checks
server backend4.example.com:8080 max_fails=3 fail_timeout=30s;
# Backup server (only used if all others fail)
server backup.example.com:8080 backup;
# Connection pooling
keepalive 32;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
# Headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Timeouts
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Health check
proxy_next_upstream error timeout http_502 http_503;
}
}
AWS Application Load Balancer
// AWS ALB configuration example
interface ALBConfiguration {
type: 'application'; // Layer 7
scheme: 'internet-facing';
targetGroups: [
{
name: 'web-servers';
protocol: 'HTTP';
port: 80;
healthCheck: {
path: '/health';
interval: 30;
timeout: 5;
healthyThreshold: 2;
unhealthyThreshold: 3;
};
targets: [
{ id: 'i-1234567890abcdef0', port: 80 },
{ id: 'i-1234567890abcdef1', port: 80 }
];
}
];
listeners: [
{
protocol: 'HTTPS';
port: 443;
certificates: ['arn:aws:acm:us-east-1:123456789012:certificate/abc123'];
defaultActions: [
{
type: 'forward';
targetGroup: 'web-servers';
}
];
rules: [
{
conditions: [{ field: 'path-pattern', values: ['/api/*'] }];
actions: [{ type: 'forward', targetGroup: 'api-servers' }];
}
];
}
];
}
Best Practices
For Performance
- Use health checks to avoid routing to failed servers
- Implement connection pooling
- Choose appropriate algorithm for workload
- Monitor server metrics continuously
- Set proper timeout values
For Reliability
- Deploy across multiple availability zones
- Use backup servers
- Implement circuit breakers
- Log all routing decisions
- Set up automated failover
For Security
- Terminate SSL at load balancer
- Implement rate limiting
- Use Web Application Firewall (WAF)
- Filter malicious traffic
- Implement DDoS protection