Let's implement basic service discovery using Go 🚀

Abdulsamet Ä°LERÄ°
ITNEXT
Published in
3 min readMay 22, 2022

--

As we already know, to request a service instance (server), we must know its network location (IP address and port).

With the breakthrough of microservices in today's world, the increasing number of users, requests, and demands have made this job very difficult. Moreover, our services are constantly changing due to various situations such as autoscaling, failures, and upgrades in the cloud-based microservices era. As a result of these changes, they continuously get their new IPs.

This is where service discovery enters the microservices scene. We need a system that will keep an eye on all services and track which service is deployed on which IP/port combination at any given time so that the clients of microservices can be seamlessly routed accordingly. [1]

Service discovery is conceptually quite simple: its vital component is a service registry, which is a database of the network locations of an application's service instances. [2] This mechanism updates the service registry when service instances start and stop.

There are two main ways to implement service discovery:

We will use 3rd party registration pattern to implement for our service discovery. Thanks to this pattern, instead of a service registering with the service registry, a third party called the registrar (in our case very basic go function runs docker ps -a with a specific time interval).

Figure: Project Architecture Overview

Let's review our application in more detail.

Reverse Proxy

To implement reverse proxy, I used the httputil package—my main purpose in implementing this providing load balancing.

To implement client request routing in a round-robin fashion, I do basic math, which counts the number of getting requests, and does a modular operation with the length of the service registry list so that I easily find the backend and proxy the request.

Registrar

I used time.Tick for implementing polling between specific time intervals (default 3sec). At every tick, the code runs docker ps -a using the official docker go SDK. (I used -a because I need to know which containers are downstate; if so, I remove the unhealthy container IP and port from our service registry list). If a new container is added and running state, check if it already exists in the service registry; if not so, add its address to the service registry.

Service Registry

It's a very basic struct slice with the safe of concurrent access thanks to sync.RWMutex And as mentioned above, it keeps all healthy backend address lists. This list updates every 3 seconds by the registrar.

Feedbacks:

1- Using Docker Events API instead of time pooling to observe containers:

I used basic time pooling (every 3 sec) to keep track of newly started or dead containers. In doing so, I keep my service registry up to date.

According to incoming feedback, It's better to use docker events api for this purpose. Thank to this API, I can get real-time updates from the docker server.

I implemented it as follows. (Commit)

Init() error method for getting initial running containers just one time.

In Observe() method, I watch events using special filters like an image name, etc.

I listen to events with a channel returned from the docker API client; when a new event comes, I can update the service registry according to its payload.

2- Using atomic.Value instead of sync.Mutex

I used sync.Mutex to control the service registry's read-write. But of course, there is a downside to using it. It blocks new proxy connections during the service registry mutation.

According to the other incoming feedback, "one advanced trick is to use sync/atomic.Value, construct a new lookup table (copy-on-write is okay), and then consumers can Load that value at any time for an immutable lookup snapshot as of the moment they did the load."

I refactored my service registry as follows. (Commit)

Source Code

https://github.com/Abdulsametileri/simple-service-discovery

References

[1] Microservice Architecture Aligning Principles, Practices, and Culture

[2] Microservices Patterns with Examples In Java

--

--