Each instance of a microservice exposes a remote API with its own host and port. However, these endpoint URLs can be dynamic and may change frequently. How do other microservices or clients discover and invoke these services without hardcoding the details?
When a microservice instance fails, new instances are spun up to ensure constant availability. These new instances may have different IP addresses or ports. How can these new service instances seamlessly join the network and start serving requests without manual intervention?
With multiple instances of a microservice running, how do we ensure proper load balancing between them? Additionally, how is information about specific service instances shared across the network to enable efficient communication and invocation between microservices?
The challenges of locating services, integrating new service instances, and ensuring load balancing in microservices can be addressed using the following concepts:
For a service or application to communicate with another service or application within a web network, it must have the necessary details to locate it, such as its IP address or DNS name.
Consider an example with two services: Accounts and Loans. If only one instance of the Loans microservice exists, the communication between these two services can be visualized as follows:
Traditional load balancers distribute incoming client requests across a pool of backend servers to optimize resource utilization and enhance application performance. They operate on the basis of algorithms and rules to determine which server receives the next request.
Traditional load balancers use a primary-secondary setup, with the primary load balancer handling all incoming traffic. The secondary load balancer acts as a failover mechanism in case the primary fails. The load balancer sits between the client and the backend servers, directing requests based on its load balancing algorithms.
The traditional load balancers use different algorithms to distribute the incoming requests:
Traditional load balancers handle server failures through redundancy. If a server fails, the load balancer will simply bypass it and continue to distribute traffic to the remaining servers. The secondary load balancer will take over in the event of the primary failure, ensuring high availability of the application.
Failover Mechanism: The secondary load balancer checks the health of the primary periodically. If the primary fails, the secondary assumes control, allowing the application to remain available to users without interruption.
Cloud-native applications utilize modern tools like Service Discovery and Load Balancing as part of their architecture. These tools offer dynamic, automated methods for managing services and load distribution.
Service Discovery enables applications to discover and communicate with other services in a dynamic environment. It reduces the need for hard-coded IP addresses and simplifies the process of adding new services.
How it Works: Services register themselves with the service registry upon startup, and the registry keeps track of all available services. Other services can query the registry to find the available instances of a specific service.
public class EurekaServiceRegistry implements ServiceRegistry {
private final EurekaClient eurekaClient;
public EurekaServiceRegistry(EurekaClient eurekaClient) {
this.eurekaClient = eurekaClient;
}
@Override
public List getAllServices() {
return eurekaClient.getInstancesById("my-service-id");
}
}
Configuration: In Spring Cloud, enable service discovery with @EnableEurekaClient
for Eureka, or @EnableDiscoveryClient
for Consul.
Service Registry keeps a catalog of services available in the system. This ensures services can register and deregister automatically, making it easier to handle failures and scale services dynamically.
How it Works: Services register themselves with the service registry, and the registry provides a central view of all service instances. This setup allows services to be self-healing, where a failed instance can be automatically removed from the registry and replaced by a new instance.
public class EurekaServiceRegistry implements ServiceRegistry {
private final EurekaClient eurekaClient;
public EurekaServiceRegistry(EurekaClient eurekaClient) {
this.eurekaClient = eurekaClient;
}
@Override
public void register(ServiceInstance serviceInstance) {
eurekaClient.registerInstance(serviceInstance);
}
@Override
public void deregister(ServiceInstance serviceInstance) {
eurekaClient.deregisterInstance(serviceInstance.getServiceId(), serviceInstance.getInstanceId());
}
}
Load balancing in cloud-native applications can be done either at the client-side or server-side.
Client-side load balancing involves the client application making decisions about which service instance to call. The client consults the service registry to discover the available instances of a service, chooses one, and then sends requests directly to that service.
Start by creating a new Spring Boot project using your preferred IDE or by using Spring Initializr (https://start.spring.io/). Include the spring-cloud-starter-netflix-eureka-server
Maven dependency in your project.
server:
port: 8070
eureka:
instance:
hostname: localhost
client:
fetchRegistry: false
registerWithEureka: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
In the main class of your project, annotate it with @EnableEurekaServer
. This annotation configures the application to act as a Eureka Server.
Build your project and run it as a Spring Boot application. Open a web browser and navigate to http://localhost:8070
. You should see the Eureka Server dashboard, which displays information about registered service instances.
Start by creating a new Spring Boot project using your preferred IDE or by using Spring Initializr (https://start.spring.io/). Include the spring-cloud-starter-netflix-eureka-client
Maven dependency.
eureka:
instance:
preferIpAddress: true
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: "http://localhost:8070/eureka/"
Build your project and run it as a Spring Boot application. Open a web browser and navigate to http://localhost:8070
. You should see the microservice registered itself as an application, which can be confirmed inside the Eureka Server dashboard.
In server-side load balancing, a central component (like a load balancer) decides which service instance to forward a request to. The client application does not need to be aware of the specific instances or their details; it simply sends requests to a load balancer.
How it Works: The load balancer chooses an appropriate instance to forward the request based on factors like health, load, or latency.
Answer: Client-side service discovery involves the client making requests directly to service instances based on dynamic information from the service registry. Server-side discovery involves a central load balancer making decisions about which instance to forward the request to.
Answer: Cloud-native applications use service registries that automatically remove failed instances, ensuring that new requests are not directed to them. Additionally, health checks (liveness and readiness probes) can be used to detect and respond to failures.
Answer: A service registry provides a central point of truth for service instances, simplifying service discovery and making the system more resilient to changes and failures. It allows for automatic registration and deregistration of services, reducing manual management efforts.