Scalable Load Balancing Architectures: How to Implement Multiple Load Balancers for High Availability


2 views

When implementing multiple load balancers, DNS becomes your primary distribution mechanism. Modern DNS services support multiple A records (for IPv4) or AAAA records (for IPv6) for a single hostname, enabling round-robin distribution at the DNS level.


example.com.   300  IN  A  192.0.2.1
example.com.   300  IN  A  192.0.2.2
example.com.   300  IN  A  192.0.2.3

There are two primary approaches to multi-LB architectures:

  • Active-Passive: One LB handles traffic while others remain on standby
  • Active-Active: All LBs share traffic load simultaneously

For geographically distributed setups, GSLB solutions like:


# Example AWS Route53 latency-based routing
resource "aws_route53_record" "www" {
  zone_id = aws_route53_zone.primary.zone_id
  name    = "www.example.com"
  type    = "A"

  latency_routing_policy {
    region = "us-west-2"
    set_identifier = "us-west-2"
    alias {
      name                   = aws_lb.west.dns_name
      zone_id                = aws_lb.west.zone_id
      evaluate_target_health = true
    }
  }
  
  latency_routing_policy {
    region = "eu-west-1"
    set_identifier = "eu-west-1"
    alias {
      name                   = aws_lb.east.dns_name
      zone_id                = aws_lb.east.zone_id
      evaluate_target_health = true
    }
  }
}

Implement comprehensive health checks to ensure traffic only routes to healthy LBs:


# NGINX Plus active health checks
upstream backend {
  zone backend 64k;
  server 10.0.0.1:80;
  server 10.0.0.2:80;
  
  health_check interval=5s fails=3 passes=2 uri=/health;
  health_check_timeout 3s;
}

When using multiple active LBs, implement distributed session storage:


# Redis session storage configuration for Node.js
const session = require('express-session');
const RedisStore = require('connect-redis')(session);

app.use(session({
  store: new RedisStore({
    host: 'redis-cluster.example.com',
    port: 6379,
    ttl: 86400
  }),
  secret: 'your_secret_key',
  resave: false,
  saveUninitialized: false
}));

For configuration management across multiple LBs:


# Ansible playbook for consistent LB configuration
- hosts: loadbalancers
  become: true
  tasks:
    - name: Ensure haproxy is installed
      apt:
        name: haproxy
        state: present
    
    - name: Deploy haproxy config
      template:
        src: haproxy.cfg.j2
        dest: /etc/haproxy/haproxy.cfg
      notify: restart haproxy
  
  handlers:
    - name: restart haproxy
      service:
        name: haproxy
        state: restarted

Implement centralized monitoring for all LBs:


# Prometheus configuration for multiple LB instances
scrape_configs:
  - job_name: 'haproxy'
    static_configs:
      - targets: ['lb1.example.com:9101', 'lb2.example.com:9101', 'lb3.example.com:9101']
  
  - job_name: 'nginx'
    static_configs:
      - targets: ['lb4.example.com:9113', 'lb5.example.com:9113']

When architecting high-availability systems, many developers encounter the paradox: if DNS typically resolves to a single IP, how can we distribute traffic across multiple load balancers? The misconception stems from thinking about DNS as a static 1:1 mapping rather than a dynamic routing mechanism.

The simplest approach is DNS round-robin with multiple A records:

example.com.    IN  A   192.0.2.1
example.com.    IN  A   192.0.2.2
example.com.    IN  A   192.0.2.3

However, this has limitations due to DNS caching. For better control, consider weighted DNS responses using Route 53:

const params = {
  HostedZoneId: 'Z1PA6795UKMFR9',
  ChangeBatch: {
    Changes: [
      {
        Action: "CREATE",
        ResourceRecordSet: {
          Name: "api.example.com",
          Type: "A",
          SetIdentifier: "lb-east-1",
          Weight: 30,
          AliasTarget: {
            HostedZoneId: "Z35SXDOTRQ7X7K",
            DNSName: "dualstack.lb-east-1.elb.amazonaws.com",
            EvaluateTargetHealth: true
          }
        }
      }
    ]
  }
};

For global distribution, anycast routing allows multiple load balancers to share the same IP address. Cloud providers implement this differently:

# AWS Global Accelerator configuration
resource "aws_globalaccelerator_accelerator" "example" {
  name            = "multi-lb-accelerator"
  ip_address_type = "IPV4"
  enabled         = true
}

resource "aws_globalaccelerator_listener" "example" {
  accelerator_arn = aws_globalaccelerator_accelerator.example.id
  client_affinity = "SOURCE_IP"
  protocol        = "TCP"

  port_range {
    from_port = 80
    to_port   = 80
  }
}

Implementing proper health checks ensures traffic only reaches healthy endpoints. Here's a HAProxy configuration for active-passive setups:

frontend http-in
    bind *:80
    default_backend servers

backend servers
    balance roundrobin
    option httpchk GET /health
    server lb1 192.168.1.10:80 check backup
    server lb2 192.168.1.11:80 check

Common architectural approaches include:

  • Tiered load balancing (global LB → regional LB → service LB)
  • Active-active configurations with BGP peering
  • Cloud-specific solutions like AWS ALB + NLB combinations

For Kubernetes environments, consider this Ingress configuration:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-lb-ingress
  annotations:
    nginx.ingress.kubernetes.io/upstream-hash-by: "$request_uri"
spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-service
            port:
              number: 80