Some tools to aid in resiliency in Ruby. For now, just a circuit breaker (stolen from based on hystrix). Soon much more...
Nothing asynchronous or thread safe yet either, but open to it and would like to see more around it in the future. See more here: jnunemaker/resilient#18.
Add this line to your application's Gemfile:
gem "resilient"
And then execute:
$ bundle
Or install it yourself as:
$ gem install resilient
require "resilient/circuit_breaker"
# default properties for circuit, CircuitBreaker.get is used instead of
# CircuitBreaker.new as `get` keeps a registry of circuits by key to prevent
# creating multiple instances of the same circuit breaker for a key; not using
# `get` means you would have multiple instances of the circuit breaker and thus
# separate state and metrics; you can read more in examples/get_vs_new.rb
circuit_breaker = Resilient::CircuitBreaker.get("example")
if circuit_breaker.allow_request?
begin
# do something expensive
circuit_breaker.success
rescue => boom
circuit_breaker.failure
# do fallback
end
else
# do fallback
end
customize properties of circuit:
circuit_breaker = Resilient::CircuitBreaker.get("example", {
# at what percentage of errors should we open the circuit
error_threshold_percentage: 50,
# do not try request again for 5 seconds
sleep_window_seconds: 5,
# do not open circuit until at least 5 requests have happened
request_volume_threshold: 5,
})
# etc etc etc
force the circuit to be always open:
circuit_breaker = Resilient::CircuitBreaker.get("example", force_open: true)
# etc etc etc
force the circuit to be always closed (great way to test in production with no impact, all instrumentation still runs which means you can measure in production with config and gain confidence while never actually opening a circuit incorrectly):
circuit_breaker = Resilient::CircuitBreaker.get("example", force_closed: true)
# etc etc etc
customize rolling window to be 10 buckets of 1 second each (10 seconds in all):
circuit_breaker = Resilient::CircuitBreaker.get("example", {
window_size_in_seconds: 10,
bucket_size_in_seconds: 1,
})
# etc etc etc
Property | Default | Notes |
---|---|---|
:force_open | false |
allows forcing the circuit open (stopping all requests) |
:force_closed | false |
allows ignoring errors and therefore never trip "open" (e.g. allow all traffic through); normal instrumentation will still happen, thus allowing you to "test" configuration live without impact |
:instrumenter | Instrumenters::Noop |
what to use to instrument all events that happen (e.g. ActiveSupport::Notifications ) |
:sleep_window_seconds | 5 |
seconds after tripping circuit before allowing retry |
:request_volume_threshold | 20 |
number of requests that must be made within a statistical window before open/close decisions are made using stats |
:error_threshold_percentage | 50 |
% of "marks" that must be failed to trip the circuit |
:window_size_in_seconds | 60 |
number of seconds in the statistical window |
:bucket_size_in_seconds | 10 |
size of buckets in statistical window |
:metrics | Resilient::Metrics.new |
metrics instance used to keep track of success and failure |
To ensure that you have clean circuit breakers for each test case, be sure to run the following in the setup for your tests (which resets the default registry and thus clears all the registered circuits) either before every test case or at a minimum each test case that uses circuit breakers.
Resilient::CircuitBreaker::Registry.reset
Note: If you use a non-default registry, you'll need to reset that on your own. If you don't know what I'm talking about, you are fine.
# install dependencies
script/bootstrap
# run tests
script/test
# ...or to auto run tests with guard
script/watch
# to get a shell to play in
script/console
Bug reports and pull requests are welcome on GitHub at https://github.com/jnunemaker/resilient.
The gem is available as open source under the terms of the MIT License.
- increment version based on semver
- git commit version change
- script/release (releases to rubygems and git tags)