๐ Service Discovery in Microservices
Technical Documentation for Principal Engineers
1. Overview and Problem Statement ๐ฏโ
Definitionโ
Service Discovery is a mechanism that enables microservices to locate and communicate with each other dynamically in a distributed system. It provides automatic detection of services and their instances as they become available or unavailable.
Problems Solvedโ
- Dynamic service location
- Load balancing
- Service health monitoring
- Instance registration/deregistration
- Network location abstraction
- Service redundancy
Business Valueโ
- Improved system reliability
- Automatic scaling support
- Reduced operational overhead
- Enhanced system maintainability
- Better fault tolerance
- Dynamic service management
2. Detailed Solution/Architecture ๐โ
Core Componentsโ
Implementation Patternsโ
-
Client-Side Discovery
- Client queries registry
- Client performs load balancing
- Direct service communication
-
Server-Side Discovery
- Load balancer queries registry
- Centralized request routing
- Client unaware of service locations
3. Technical Implementation ๐ปโ
3.1 Service Registration (Spring Cloud Netflix)โ
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
@Configuration
public class ServiceConfig {
@Bean
public ServiceRegistry serviceRegistry() {
return new EurekaServiceRegistry();
}
}
3.2 Service Discovery Clientโ
@Service
@LoadBalanced
public class OrderServiceClient {
private final RestTemplate restTemplate;
private final DiscoveryClient discoveryClient;
public Order getOrder(String orderId) {
// Using service ID instead of direct URL
return restTemplate.getForObject(
"http://order-service/orders/{id}",
Order.class,
orderId
);
}
public List<ServiceInstance> getServiceInstances() {
return discoveryClient.getInstances("order-service");
}
}
3.3 Custom Service Registry Implementationโ
public class CustomServiceRegistry implements ServiceRegistry<Registration> {
private final ConcurrentHashMap<String, List<ServiceInstance>> services =
new ConcurrentHashMap<>();
private final ScheduledExecutorService healthChecker =
Executors.newScheduledThreadPool(1);
@Override
public void register(Registration registration) {
String serviceId = registration.getServiceId();
ServiceInstance instance = createServiceInstance(registration);
services.computeIfAbsent(serviceId, k -> new CopyOnWriteArrayList<>())
.add(instance);
startHealthCheck(instance);
}
@Override
public void deregister(Registration registration) {
String serviceId = registration.getServiceId();
services.getOrDefault(serviceId, Collections.emptyList())
.removeIf(instance ->
instance.getInstanceId().equals(registration.getInstanceId())
);
}
private void startHealthCheck(ServiceInstance instance) {
healthChecker.scheduleAtFixedRate(() -> {
if (!isHealthy(instance)) {
deregister(instance.getRegistration());
}
}, 0, 30, TimeUnit.SECONDS);
}
}
3.4 Load Balancer Integrationโ
@Configuration
public class LoadBalancerConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public LoadBalancerClient loadBalancerClient(
DiscoveryClient discoveryClient
) {
return new RoundRobinLoadBalancerClient(discoveryClient);
}
}
public class RoundRobinLoadBalancerClient implements LoadBalancerClient {
private final DiscoveryClient discoveryClient;
private final AtomicInteger counter = new AtomicInteger(0);
@Override
public ServiceInstance choose(String serviceId) {
List<ServiceInstance> instances =
discoveryClient.getInstances(serviceId);
if (instances.isEmpty()) {
return null;
}
int index = counter.getAndIncrement() % instances.size();
return instances.get(index);
}
}
4. Implementation Patterns ๐โ
4.1 Client-Side Discovery Patternโ
@Service
public class ClientSideDiscoveryService {
private final DiscoveryClient discoveryClient;
private final LoadBalancerClient loadBalancer;
public <T> T executeRequest(String serviceId, Function<URI, T> request) {
ServiceInstance instance = loadBalancer.choose(serviceId);
if (instance == null) {
throw new ServiceNotFoundException(serviceId);
}
URI uri = instance.getUri();
return request.apply(uri);
}
}