In a monolithic architecture, we deployed a few application servers and statically registered them with load balancers. But in Microservices based architectures, inherently the services are mobile and can come and go because of auto-scaling, self-healing etc.
So the clients need a way for looking up the services they want to talk to. Essentially we need a way for mapping logical service names to one or more network paths.
To accomplish this, we need to implement a service discovery mechanism. This usually has 3 parts:
- Service registry: This is a database of all the services and their network locations.
- Strategy for building up this database. i.e., registering the services.
- Strategy for service lookup.
There are several great options for service registry these days. Netflix Eureka, Consul, ZooKeeper to name a few.
Registering the services
Whenever a service starts up or shuts down, the service registry needs to know about this. This can be implemented in 2 ways.
Every service when it starts up, registers with the service registry. If the registry requires meta-data, this also should be provided by the service itself. Netflix OSS Eureka client is an example of this where each service registers with Eureka on startup and also sends a heartbeat every 30 seconds. If the heartbeat fails, Eureka removes the service from its registry.
This is a bit of a design smell as the individual services have the additional responsibility of knowing about the service registry. But it’s a simple and easy to get started with.
Third party registration:
In this pattern, individual Microservices are blissfully unaware of service registry concerns. There is a third party component which discovers and registers Microservices by polling specific files in the deployment environment or by subscribing to events. This component is also responsible for de-registering services that are no longer available. This is more prevalent in more mature implementations.
Registrator is a good example of this pattern. Here is a good article on this: https://www.airpair.com/scalable-architecture-with-docker-consul-and-nginx.
A big advantage of this pattern is that services are decoupled from service registries. A disadvantage is that the third party component has to be installed and configured, but many Microservices environment like Kubernetes comes with one.
Once the service registry database is built up, clients should be able to lookup services. This can be implemented in 2 ways:
Client side discovery:
The client is responsible for connecting to the service registry and looking up the service. Similar to self-registration, a client is coupled with service registry concerns. Netflix OSS Ribbon is an example of this. Netflix Ribbon is used to lookup and communicate with services. Ribbon comes with the basic round robin based load balancing and fault tolerance.
Though client’s ability to implement custom load balancing strategies is touted as an advantage of this pattern, in our experience we have hardly seen the need for this.
Server side discovery:
Here clients make the request for any service to a router/load balancer which is responsible for talking to service registry returning a suitable service instance. The router can additionally help with metrics, AB testing, Canary releases etc.
Another good option if you are in AWS and especially if you deploy your Microservices as Docker containers, is the newly launched AWS application load balancer.
Here is a good article on this: Amazon ECS Reference Architecture