Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
229: Circuit Breaker Pattern in Go
I remember a Friday afternoon a few years back when I was on call for a logistics platform. One of our third-party address validation APIs started lagging—not crashing, but taking nearly 29 seconds to respond. Our Go services had a 30-second timeout. Suddenly, every single request to our checkout endpoint was hanging. Our goroutine count spiked from a few hundred to over 15,000 in minutes because each request was just sitting there, waiting. We didn't just lose address validation; we took down the entire checkout process. We were essentially DDoS-ing ourselves because we were too polite to stop calling a service that was clearly drowning.
That's where the Circuit Breaker pattern comes in. In a distributed system, you have to assume that things will fail. The goal isn't just to handle the error, but to prevent a failing dependency from dragging your entire application into the abyss. It works exactly like an electrical circuit breaker in your house: when it detects a fault (too many errors), it "trips," cutting off the flow of requests to the failing service to give it space to recover and to protect your own resources.
Managing the Three States of Failure
To implement this in Go, you need to track the state of the connection to the external service. I usually think of it as a state machine with three distinct phases: Closed, Open, and Half-Open.
- Closed: Everything is normal. Requests flow through to the dependency. We keep a count of recent failures, but as long as they stay below a certain threshold, we stay Closed.
- Open: The threshold was hit. The circuit "trips." Now, any request that attempts to call the dependency fails immediately without even trying to hit the network. This is the "fail-fast" mechanism that saves your goroutines from piling up.
- Half-Open: After a "sleep window" (say, 60 seconds), we move to Half-Open. We allow a single "probe" request through. If it succeeds, we assume the service is healthy and flip back to Closed. If it fails, we immediately go back to Open and reset the timer.
Implementing a Breaker in Go
While you can write your own state machine, the Go community generally leans on proven libraries like sony/gobreaker because handling the concurrency of state transitions (especially moving from Open to Half-Open) can be tricky. Here is how you'd typically wrap a flaky API call using a circuit breaker.
package main
import (
"fmt"
"time"
"github.com/sony/gobreaker"
)
func main() {
// Configure the breaker
settings := gobreaker.Settings{
Name: "HTTP GET",
MaxRequests: 3, // Max requests allowed through when Half-Open
Interval: 5 * time.Second, // Clear counts every 5 seconds when Closed
Timeout: 10 * time.Second, // How long to stay Open before switching to Half-Open
ReadyToTrip: func(counts gobreaker.Counts) bool {
// Trip if more than 3 requests failed
return counts.ConsecutiveFailures > 3
},
}
cb := gobreaker.NewCircuitBreaker(settings)
// We wrap our flaky call inside the Execute method
for i := 0; i < 10; i++ {
result, err := cb.Execute(func() (interface{}, error) {
return callFlakyAPI()
})
if err != nil {
fmt.Printf("Request %d: Error: %v\n", i, err)
} else {
fmt.Printf("Request %d: Success: %v\n", i, result)
}
time.Sleep(1 * time.Second)
}
}
func callFlakyAPI() (interface{}, error) {
// Simulating a failure
return nil, fmt.Errorf("service unavailable")
}
Notice that the Execute method handles the state transitions for you. If the circuit is Open, Execute returns an error immediately without even running the inner function. This is the magic that prevents your service from hanging. I've found that the ReadyToTrip logic is the most important part to tune; sometimes you want it to trip based on a percentage of failures rather than consecutive ones, depending on how volatile the dependency is.
When to Use a Breaker vs. a Timeout
I often see developers confuse timeouts with circuit breakers. A timeout is your last line of defense for a single request—it stops a request from waiting forever. A circuit breaker is a strategic defense for your entire system—it stops you from making requests you already know are likely to fail.
If you only use timeouts, you're still hitting the failing server thousands of times per second, which might actually prevent that server from ever recovering. By using a circuit breaker, you're giving the downstream service breathing room. It's the difference between shouting at someone who is having a panic attack and giving them five minutes of silence to calm down before trying to talk again.
📋 Practical Task
Exercise: Implementing a Resilient Payment Gateway Wrapper
You are integrating a third-party payment processor that is known to be unstable. Your goal is to create a wrapper that prevents your application from hanging when the processor goes down.
Requirements:
- Create a mock function
ProcessPayment()that randomly returns an error 70% of the time to simulate instability. - Implement a circuit breaker (using
sony/gobreakeror a custom state machine) with the following rules:- The circuit should trip (Open) after 3 consecutive failures.
- The circuit should remain Open for 5 seconds before transitioning to Half-Open.
- In the Half-Open state, only 1 request should be allowed through to test the service.
- Write a
mainfunction that attempts to process 20 payments in a loop with a small delay (e.g., 500ms) between them. - Print the result of each attempt, clearly indicating if the failure was caused by the
ProcessPaymentfunction itself or if the Circuit Breaker blocked the request (Fail-Fast).
There are no comments for now.