When an instance starts, it registers itself with the registry, sending its name, IP, port, and metadata like version or region. The registry tracks health using heartbeats or health checks. If an instance stops sending heartbeats, or a periodic check on /health fails, the registry marks it unhealthy and stops handing out its address. This is what keeps callers from being routed to a dead instance.
There are two patterns for the lookup itself. In client-side discovery, the caller queries the registry directly, gets the full list of healthy instances, and picks one using its own load-balancing logic. Netflix Eureka with the Ribbon client worked this way. The caller does more work but there is no extra network hop.
In server-side discovery, the caller just sends the request to a fixed address like payments.internal, and a load balancer or proxy in front of the service does the registry lookup and forwards the request. Kubernetes Services and AWS load balancers work this way. The client stays dumb, which is simpler, at the cost of an extra hop through the proxy.
Many platforms also expose discovery over DNS. Kubernetes gives every service a DNS name like payments.default.svc.cluster.local, and resolving that name returns a cluster IP or the set of pod IPs. DNS is convenient because almost every language already knows how to resolve a hostname, but plain DNS caching can hand out stale results, so platforms tune TTLs down hard.