Discovery usually ends with "give me one healthy instance to call." Spreading calls evenly is round-robin: keep a per-service counter and hand out healthy instances in rotation, wrapping around. Only healthy instances are eligible, an expired or deregistered one is never returned, and when none are available the caller gets None so it can back off.
Build the picker:
pick(service, now): from the current healthy list, return the next instance in round-robin order (advancing a per-service counter in self._rr); return None if there are no healthy instances.class ServiceRegistry:
def __init__(self, ttl=10):
self.ttl = ttl
self.instances = {}
self._rr = {}
def register(self, service, instance_id, now):
self.instances.setdefault(service, {})[instance_id] = now
def healthy(self, service, now):
svc = self.instances.get(service, {})
return sorted(i for i, last in svc.items() if now - last <= self.ttl)
def heartbeat(self, service, instance_id, now):
svc = self.instances.get(service, {})
if instance_id in svc:
svc[instance_id] = now
return True
return False
def deregister(self, service, instance_id):
self.instances.get(service, {}).pop(instance_id, None)
def pick(self, service, now):
"""Return the next healthy instance in round-robin order, or None."""
options = self.healthy(service, now)
if not options:
return None
# TODO: options is the sorted healthy list. Take this service's counter
# from self._rr (missing means zero), reduce it modulo the number of
# options to choose one, then bump that stored counter by one so the
# next call lands on the following instance. Hand the chosen id back.
pass
The editor and the test runner need a wide screen. Open this page on a desktop browser and your progress will be waiting.
Discovery usually ends with "give me one healthy instance to call." Spreading calls evenly is round-robin: keep a per-service counter and hand out healthy instances in rotation, wrapping around. Only healthy instances are eligible, an expired or deregistered one is never returned, and when none are available the caller gets None so it can back off.
Build the picker:
pick(service, now): from the current healthy list, return the next instance in round-robin order (advancing a per-service counter in self._rr); return None if there are no healthy instances.Press Run Tests or ββ© to check your solution.