How to Implement a Weighted DNS Resolver with Failover in Python
Simulates a weighted DNS load balancer that distributes traffic across IPs by weight and automatically fails over when a server is marked unhealthy.
Python code
51 linesimport random
import time
class WeightedDNSResolver:
def __init__(self, records):
self.records = records # list of (ip, weight)
self.total_weight = sum(weight for _, weight in records)
self.failed_ips = set()
def resolve(self):
available = [(ip, weight) for ip, weight in self.records if ip not in self.failed_ips]
if not available:
return None
total = sum(weight for _, weight in available)
rand = random.uniform(0, total)
cumulative = 0
for ip, weight in available:
cumulative += weight
if rand <= cumulative:
return ip
def mark_failed(self, ip):
self.failed_ips.add(ip)
def simulate_traffic(self, num_requests=10):
results = {}
for _ in range(num_requests):
ip = self.resolve()
if ip:
results[ip] = results.get(ip, 0) + 1
if random.random() < 0.2:
self.mark_failed(ip)
time.sleep(0.01)
return results
if __name__ == "__main__":
records = [("192.168.1.10", 50), ("192.168.1.11", 30), ("192.168.1.12", 20)]
resolver = WeightedDNSResolver(records)
print("Initial resolution test (10 requests):")
results = resolver.simulate_traffic(10)
for ip, count in sorted(results.items(), key=lambda x: -x[1]):
print(f" {ip}: {count} requests")
resolver.mark_failed("192.168.1.10")
print("\nAfter failover (10 more requests, .10 marked failed):")
results = resolver.simulate_traffic(10)
for ip, count in sorted(results.items(), key=lambda x: -x[1]):
print(f" {ip}: {count} requests")
Output
Initial resolution test (10 requests):
192.168.1.11: 4 requests
192.168.1.10: 3 requests
192.168.1.12: 3 requests
After failover (10 more requests, .10 marked failed):
192.168.1.11: 5 requests
192.168.1.12: 5 requests
How it works
The WeightedDNSResolver class stores the total weight across all records and, on each resolve() call, builds a list of available IPs that aren't marked as failed. A random number between 0 and the total available weight is generated, then the resolver walks through IPs in order, accumulating weight until the random value lands in a range — the heavier the weight, the higher the chance it gets picked. mark_failed() adds an IP to the failed_ips set, which removes it from future selections and redistributes traffic proportionally among the remaining healthy servers. The simulate_traffic() method tracks request counts per IP and randomly marks servers as failed with a 20% probability, mimicking a readiness check that detects unhealthy nodes.
Common mistakes
- Forgetting to recompute total weight after filtering failed IPs, which skews the distribution
- Using the original total_weight instead of the available sum when selecting the random threshold
- Not handling the edge case where all IPs are marked failed and resolve() returns None
- Marking an IP as failed repeatedly without a health-check mechanism to restore it
Variations
- Use a health-check thread that periodically unmarks IPs after they recover
- Replace random.uniform with a deterministic round-robin to guarantee exact traffic ratios
Real-world use cases
- Load balancing HTTP requests across backend servers in a microservice architecture.
- Simulating DNS failover behavior for infrastructure testing before a production deployment.
- Weighting traffic toward newly deployed canary servers while monitoring their error rates.
Sponsored
More from Automation & scripting
- Aggregate Log Errors Count by Hour in Python easy
- Automate Tweeting New Blog Posts in Python easy
- Automatically Clean Temporary Files from Applications Using Python medium
- Automatically Download the Latest Software Release from GitHub with Python medium
- Automatically Generate Charts from CSV Files with One Command medium
- Automatically Generate Hardware Inventory Reports in Python easy
Keep learning
Related tutorials and quizzes for this topic.