DEV Community

Cover image for Why Your Production Microservices Should Use Circuit Breakers (And How to Implement One in 50 Lines)
Mindinu Ariyawansha
Mindinu Ariyawansha

Posted on

Why Your Production Microservices Should Use Circuit Breakers (And How to Implement One in 50 Lines)

If you are building distributed systems, network calls will fail. It’s not a matter of if, but when.

Whether it's an API rate limit, a transient database spike, or a downstream service deploying buggy code, external network calls are non-deterministic. The real problem isn't the single failed request—it's cascading failures.

When Service A synchronously calls Service B, and Service B begins hanging due to high latency, Service A runs out of available threads or memory waiting for responses. Suddenly, Service A crashes, taking down Service C, and dragging your entire infrastructure into a full outage.

This is where the Circuit Breaker Pattern becomes vital.


What is a Circuit Breaker?

Inspired by the electrical circuit breaker in your home that cuts power during a current overload, a software circuit breaker wraps an expensive or network-bound call and monitors for failures.

A circuit breaker operates in three distinct states:

         +--------------------------------------------+
         |                                            |
         v                                            |
   +-----------+   Failures > Threshold   +----------+ |
---|   CLOSED  |------------------------->|   OPEN   | | Success
   +-----------+                          +----------+ |
         ^                                     |       |
         |         Timeout Expired             |       |
         |      +-------------------+          |       |
         +------|     HALF-OPEN     |<---------+       |
                +-------------------+                  |
                          |                            |
                          +----------------------------+
                                   Failure
Enter fullscreen mode Exit fullscreen mode
  1. CLOSED (Normal Operation): All requests pass through to the downstream service. The breaker records successes and failures.
  2. OPEN (Failing Fast): If the failure rate crosses a specified threshold within a time window, the circuit trips OPEN. All incoming requests immediately return a fallback error or cached data without actually making a network call.
  3. HALF-OPEN (Testing the Waters): After a cooldown period, the breaker allows a limited number of test requests through. If they succeed, the circuit resets to CLOSED. If they fail, it trips back to OPEN.

Implementing a Minimal Circuit Breaker

Here is a lightweight, dependency-free implementation of a Circuit Breaker in Go (the exact same pattern applies in TypeScript, Python, or Java).


go
package main

import (
    "errors"
    "sync"
    "time"
)

type State int

const (
    StateClosed State = iota
    StateOpen
    StateHalfOpen
)

type CircuitBreaker struct {
    mu               sync.Mutex
    state            State
    failureThreshold int
    cooldown         time.Duration
    failures         int
    lastFailureTime  time.Time
}

func NewCircuitBreaker(threshold int, cooldown time.Duration) *CircuitBreaker {
    return &CircuitBreaker{
        state:            StateClosed,
        failureThreshold: threshold,
        cooldown:         cooldown,
    }
}

func (cb *CircuitBreaker) Execute(req func() error) error {
    cb.mu.Lock()

    // Check if OPEN circuit cooldown period has expired
    if cb.state == StateOpen {
        if time.Since(cb.lastFailureTime) > cb.cooldown {
            cb.state = StateHalfOpen
        } else {
            cb.mu.Unlock()
            return errors.New("circuit breaker is OPEN: fast-failing request")
        }
    }

    cb.mu.Unlock()

    // Execute actual network request
    err := req()

    cb.mu.Lock()
    defer cb.mu.Unlock()

    if err != nil {
        cb.failures++
        cb.lastFailureTime = time.Now()

        if cb.failures >= cb.failureThreshold {
            cb.state = StateOpen
        }
        return err
    }

    // Reset state on successful request
    if cb.state == StateHalfOpen || cb.state == StateClosed {
        cb.failures = 0
        cb.state = StateClosed
    }

    return nil
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)