How to Use and Configure the Circuit Breaker Middleware in Kratos
The circuit breaker middleware in Kratos protects services from cascading failures by intercepting client requests through a per-operation SRE-style breaker that automatically rejects traffic when error thresholds are exceeded, configurable via WithGroup() and WithCircuitBreaker() options.
Kratos provides a client-side circuit breaker middleware located in middleware/circuitbreaker that prevents cascading failures by short-circuiting calls to unhealthy resources. Built on top of the aegis library, this middleware automatically isolates failing operations and returns structured errors without hitting the downstream service.
How the Circuit Breaker Middleware Functions in Kratos
Per-Operation Breaker Initialization
In middleware/circuitbreaker/circuitbreaker.go, the middleware constructs a group (group.Group) that lazily instantiates a breaker for each distinct operation using info.Operation(). The default generator returns an SRE-style breaker (sre.NewBreaker()) with sensible defaults: failure-rate threshold of 0.5, request-volume threshold of 20, and a 5-second sleep window.
This design ensures that each RPC method or HTTP endpoint receives an independent circuit breaker, preventing a failure in one operation from affecting unrelated traffic.
Request Flow and Allow Checks
When processing requests, the middleware extracts client transport info via transport.FromClientContext and retrieves the operation-specific breaker using opt.group.Get(info.Operation()). The breaker.Allow() method determines if the request proceeds.
If the breaker rejects the call, the middleware increments the failure counter via breaker.MarkFailed() and returns ErrNotAllowed with HTTP status 503 and error code "CIRCUITBREAKER". This rejection occurs immediately without invoking the downstream handler.
Error Classification and Outcome Recording
For permitted requests, the middleware invokes the downstream handler and records outcomes based on error type. Server-side errors (InternalServer, ServiceUnavailable, GatewayTimeout) trigger breaker.MarkFailed(), while all other outcomes including success trigger breaker.MarkSuccess(). This distinction ensures that client-side errors (4xx) do not artificially open the circuit against healthy downstream services.
Configuring the Circuit Breaker Middleware in Kratos
Using Default SRE Breakers
Without configuration, cb.Client() creates a fresh group generating SRE breakers per operation. This zero-config approach provides immediate protection with industry-standard defaults suitable for most microservices.
import (
cb "github.com/go-kratos/kratos/v2/middleware/circuitbreaker"
"github.com/go-kratos/kratos/v2/transport/http"
)
func NewClient() *http.Client {
return http.NewClient(
http.WithMiddleware(cb.Client()),
)
}
Customizing Breaker Thresholds with WithCircuitBreaker
Use WithCircuitBreaker(gen func() circuitbreaker.CircuitBreaker) to supply a custom factory function. This enables tuning thresholds, timeout windows, or swapping the SRE implementation for another algorithm entirely.
import (
cb "github.com/go-kratos/kratos/v2/middleware/circuitbreaker"
"github.com/go-kratos/aegis/circuitbreaker"
"github.com/go-kratos/aegis/circuitbreaker/sre"
"time"
)
func customBreakerFactory() circuitbreaker.CircuitBreaker {
return sre.NewBreaker(
sre.WithFailureRateThreshold(0.30),
sre.WithSleepWindow(10 * time.Second),
)
}
func NewClientWithCustomCB() *http.Client {
return http.NewClient(
http.WithMiddleware(
cb.Client(cb.WithCircuitBreaker(customBreakerFactory)),
),
)
}
Sharing Breaker Groups Across Clients with WithGroup
WithGroup(g *group.Group[circuitbreaker.CircuitBreaker]) replaces the internal group with a user-provided instance, useful for sharing circuit state across multiple client instances or pre-populating method-specific configurations. Passing nil disables the group and will cause a panic if used.
import (
"github.com/go-kratos/kratos/v2/internal/group"
cb "github.com/go-kratos/kratos/v2/middleware/circuitbreaker"
"github.com/go-kratos/aegis/circuitbreaker"
"github.com/go-kratos/aegis/circuitbreaker/sre"
)
var sharedGroup = group.NewGroup(func() circuitbreaker.CircuitBreaker {
return sre.NewBreaker()
})
func NewClientUsingGroup() *http.Client {
return http.NewClient(
http.WithMiddleware(
cb.Client(cb.WithGroup(sharedGroup)),
),
)
}
Summary
- The circuit breaker middleware in Kratos uses a per-operation group pattern in
middleware/circuitbreaker/circuitbreaker.goto isolate failures between different RPC methods. - Default SRE breakers provide immediate protection with 50% failure-rate thresholds, 20-request volume minimums, and 5-second sleep windows.
- WithCircuitBreaker() enables custom breaker factories for fine-tuned reliability policies per service.
- WithGroup() allows sharing breaker state across multiple client instances for consistent circuit state and global metrics.
- Server-side errors (5xx) automatically mark failures via
MarkFailed(), while client errors (4xx) and successes mark healthy states viaMarkSuccess().
Frequently Asked Questions
What error does the circuit breaker return when rejecting requests?
When the breaker is open, the middleware returns ErrNotAllowed with HTTP status 503 and error code "CIRCUITBREAKER". This happens immediately after breaker.Allow() rejects the request in middleware/circuitbreaker/circuitbreaker.go, preventing the call from reaching the downstream service and protecting the client from waiting on a failing resource.
How does Kratos determine which requests count as failures?
The middleware inspects returned errors in middleware/circuitbreaker/circuitbreaker.go and treats server-side errors (InternalServer, ServiceUnavailable, GatewayTimeout) as failures by calling breaker.MarkFailed(). All other outcomes, including success and client-side errors (4xx), trigger breaker.MarkSuccess(), ensuring that client mistakes don't artificially open the circuit against healthy downstream services.
Can I use different breaker configurations for different API endpoints?
Yes. Because the middleware uses a group.Group that keys breakers by info.Operation(), each RPC method or HTTP endpoint automatically receives an independent breaker instance with its own failure counters. You can further customize this by providing a custom group via WithGroup() that returns different breaker configurations based on the operation name, or by using a custom factory function that inspects context metadata.
What happens if I pass nil to WithGroup?
Passing nil to WithGroup() disables the group mechanism entirely. If the middleware attempts to retrieve a breaker from a nil group via opt.group.Get(), it will panic. Always provide a valid *group.Group[circuitbreaker.CircuitBreaker] instance when using this configuration option.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →