Circuit

repository·master·Indexed 21 days ago

https://github.com/cep21/circuit

An efficient, feature-complete Go implementation of the Hystrix circuit breaker pattern. It protects services from downstream failures by preventing hangs, providing monitoring boundaries, handling fallback logic, and preventing overload. It includes support for Hystrix-style open/close logic, dashboard metrics via MetricEventStream, expvar health exposure, and SLO tracking via the responsetimeslo package.

Tokens
10.3K
Snippets
37
Records
50
Agent score
73%

What's inside cep21/circuit

  1. What is Circuit and why use it?

    master

    Circuit is an efficient Go implementation of the circuit breaker pattern, similar to Netflix's Hystrix. It protects your service from downstream failures by:

    • Preventing hangs: Detects downstream failures quickly and returns errors immediately instead of waiting for timeouts.
    • Providing monitoring boundaries: Creates common metric names for downstream failure types and supports SLO tracking.
    • Handling fallbacks: Provides a centralized place for fallback logic when a circuit is open or a call fails.
    • Preventing overload: Allows downstream services to recover by limiting traffic during degraded states.
    • Protecting dependencies: Shields dependencies from abnormal traffic rushes.
  2. Update stats interfaces to include context

    master

    In v4, all metric and circuit interfaces have been updated to accept a context.Context as the first parameter. If you are using or implementing custom metric interfaces, you must update your method signatures to include ctx context.Context.

    Example changes:

    • Success(now time.Time, duration time.Duration) $\rightarrow$ Success(ctx context.Context, now time.Time, duration time.Duration)
    • Closed(now time.Time) $\rightarrow$ Closed(ctx context.Context, now time.Time)
  3. Implement Service Level Objective (SLO) tracking

    master

    To track service health based on response time (e.g., "X% of requests return faster than Y ms"), use the responsetimeslo package. You can create an sloTrackerFactory and register its CommandProperties in the circuit.Manager.DefaultCircuitProperties. This allows the circuit to report a "healthy" percentage based on whether requests meet a MaximumHealthyTime threshold. Note that you must provide your own CollectorConstructors (e.g., for StatsD).

    sloTrackerFactory := responsetimeslo.Factory{
      Config: responsetimeslo.Config{
        // Consider requests faster than 20 ms as passing
        MaximumHealthyTime: time.Millisecond * 20,
      },
      // Pass in your collector here: for example, statsd
      CollectorConstructors: nil,
    }
    h := circuit.Manager{
      DefaultCircuitProperties: []circuit.CommandPropertiesConstructor{sloTrackerFactory.CommandProperties},
    }
    h.CreateCircuit("circuit-with-slo")
  4. Migrate from v3 to v4

    master

    When upgrading from version 3 to version 4, several breaking changes and architectural shifts must be addressed:

    • Dependency Management: Gopkg.toml and dep support have been removed. Use go.mod for dependency management.
    • Directory Structure: The /v3 root directory has been removed; all core logic is now in the root directory.
    • Statsd Metrics: The built-in statsd implementation has been moved to a separate repository. If you require statsd metrics, use cep21/circuit-statsd.
    • Atomics: The library now uses Go 1.19's built-in atomic package instead of a manual implementation.
    • Benchmarks: Benchmarks are no longer part of this repository and have moved to cep21/circuit-benchmarks.
  5. Development commands

    master

    The project uses a Makefile for common development tasks:

    • make: Fast development loop (build, test, and lint).
    • make ci: Runs the full CI suite locally (build, go test -race -count 10, and golangci-lint run).
    • make fuzz: Runs active fuzzing.
    • make help: Displays the full target list.
    make
    make ci
    make fuzz
    make help
  6. Use configuration factories for dynamic circuit settings

    master

    You can provide a configuration factory to the circuit.Manager via the DefaultCircuitProperties field. This allows you to define dynamic configurations (like timeouts) based on the specific name of the circuit being created. The factory must satisfy the circuit.CommandPropertiesConstructor type, which is a function taking a string (the circuit name) and returning a circuit.Config.

    myFactory := func(circuitName string) circuit.Config {
      timeoutsByName := map[string]time.Duration{
        "v1": time.Second,
        "v2": time.Second * 2,
      }
      customTimeout := timeoutsByName[circuitName]
      if customTimeout == 0 {
        return circuit.Config{}
      }
      return circuit.Config{
        Execution: circuit.ExecutionConfig{
          Timeout: customTimeout,
        },
      }
    }
    
    h := circuit.Manager{
      DefaultCircuitProperties: []circuit.CommandPropertiesConstructor{myFactory},
    }
    h.MustCreateCircuit("v1")
    fmt.Println("The timeout of v1 is", h.GetCircuit("v1").Config().Execution.Timeout)
    // Output: The timeout of v1 is 1s
  7. Use RunMetricsCollection to aggregate multiple RunMetrics collectors

    master

    If you need to send metrics to multiple collectors simultaneously, use RunMetricsCollection. It implements the RunMetrics interface by broadcasting every call to all underlying collectors.

    It also implements the expvar.Var interface via the Var() method, allowing you to expose the aggregated metrics of all collectors through Go's expvar package.

    // Example of creating a collection
    collection := RunMetricsCollection{
        &MyCustomRunMetrics{},
        &AnotherRunMetrics{},
    }
    
    // The collection can be used directly where RunMetrics is expected
    collection.Success(ctx, time.Now(), duration)
  8. Manage multiple circuits with Manager

    master

    The Manager struct is used to create, retrieve, and list multiple circuits identified by unique names. It provides thread-safe access to the underlying circuit map.

    Key capabilities:

    • Creating Circuits: Use CreateCircuit to instantiate a new circuit with specific configurations, or MustCreateCircuit if you want the application to panic if a circuit with that name already exists.
    • Retrieving Circuits: Use GetCircuit(name string) to find an existing circuit. Note: The documentation recommends storing the returned circuit instance for direct use in live code rather than calling GetCircuit repeatedly.
    • Listing Circuits: Use AllCircuits() to get a slice of all tracked *Circuit instances.
    • Observability: Use Var() to expose all circuit metrics via expvar.
    import "github.com/cep21/circuit"
    
    manager := &circuit.Manager{}
    
    // Create a circuit
    c, err := manager.CreateCircuit("my-service", circuit.Config{ /* options */ })
    if err != nil {
        // handle error
    }
    
    // Or use MustCreateCircuit if you want to panic on name collision
    c := manager.MustCreateCircuit("my-service")
  9. Use MetricsCollection to aggregate multiple Metrics collectors

    master

    Use MetricsCollection to broadcast circuit state change events (Opened and Closed) to multiple Metrics implementations simultaneously.

    collection := MetricsCollection{
        &MyStateMetrics{},
    }
    
    collection.Opened(ctx, time.Now())
  10. Configure default circuit properties using CommandPropertiesConstructor

    master

    The Manager.DefaultCircuitProperties field accepts a slice of CommandPropertiesConstructor functions. These functions are used to apply global configuration defaults to every circuit created by the manager.

    When CreateCircuit is called, these constructors are executed in reverse order. This allows you to define a base configuration and then append constructors that override specific parts of that base for all circuits.

    type CommandPropertiesConstructor func(circuitName string) Config
  11. Use FallbackMetricsCollection to aggregate multiple FallbackMetrics collectors

    master

    Similar to RunMetricsCollection, FallbackMetricsCollection allows you to broadcast fallback telemetry to multiple collectors. It also implements expvar.Var to expose the metrics of all collectors in the collection via expvar.

    collection := FallbackMetricsCollection{
        &MyCustomFallbackMetrics{},
    }
    
    collection.ErrFailure(ctx, time.Now(), duration)