5.0.0
Opossum is a Node.js circuit breaker that executes asynchronous functions
and monitors their execution status. When things start failing, opossum
plays dead and fails fast. If you want, you can provide a fallback function
to be executed when in the failure state.
For more about the circuit breaker pattern, there are lots of resources on the web - search it! Fowler's blog post is one place to start reading.
Let's say you've got an API that depends on something that might fail -
a network operation, or disk read, for example. Wrap those functions up in a
CircuitBreaker
and you have control over your destiny.
const CircuitBreaker = require('@redhat/opossum');
function asyncFunctionThatCouldFail (x, y) {
return new Promise((resolve, reject) => {
// Do something, maybe on the network or a disk
});
}
const options = {
timeout: 3000, // If our function takes longer than 3 seconds, trigger a failure
errorThresholdPercentage: 50, // When 50% of requests fail, trip the circuit
resetTimeout: 30000 // After 30 seconds, try again.
};
const breaker = new CircuitBreaker(asyncFunctionThatCouldFail, options);
breaker.fire(params)
.then(console.log)
.catch(console.error);
You can also provide a fallback function that will be executed in the
event of failure. To take some action when the fallback is performed,
listen for the fallback
event.
const breaker = new CircuitBreaker(asyncFunctionThatCouldFail, options);
// if asyncFunctionThatCouldFail starts to fail, firing the breaker
// will trigger our fallback function
breaker.fallback(() => 'Sorry, out of service right now');
breaker.on('fallback', (result) => reportFallbackEvent(result));
Once the circuit has opened, a timeout is set based on options.resetTimeout
.
When the resetTimeout
expires, opossum
will enter the halfOpen
state.
Once in the halfOpen
state, the next time the circuit is fired, the circuit's
action will be executed again. If successful, the circuit will close and emit
the close
event. If the action fails or times out, it immediately re-enters
the open
state.
When a fallback function is triggered, it's considered a failure, and the fallback function will continue to be executed until the breaker is closed.
The fallback function accepts the same parameters as the fire function:
const delay = (delay, a, b, c) =>
new Promise((resolve) => {
setTimeout(() => {
resolve();
}, delay);
});
const breaker = new CircuitBreaker(delay);
breaker.fire(20000, 1, 2, 3);
breaker.fallback((delay, a, b, c) => `Sorry, out of service right now. But your parameters are: ${delay}, ${a}, ${b} and ${c}`);
A CircuitBreaker
will emit events for important things that occur.
Here are the events you can listen for.
fire
- emitted when the breaker is fired.reject
- emitted when the breaker is open (or halfOpen).timeout
- emitted when the breaker action times out.success
- emitted when the breaker action completes successfullyfailure
- emitted when the breaker action fails, called with the erroropen
- emitted when the breaker state changes to open
close
- emitted when the breaker state changes to closed
halfOpen
- emitted when the breaker state changes to halfOpen
fallback
- emitted when the breaker has a fallback function and executes itsemaphoreLocked
- emitted when the breaker is at capacity and cannot execute the requesthealthCheckFailed
- emitted when a user-supplied health check function returns a rejected promiseHandling events gives a greater level of control over your application behavior.
The opossum
API returns a Promise
from CircuitBreaker.fire()
.
But your circuit action - the async function that might fail -
doesn't have to return a promise. You can easily turn Node.js style
callback functions into something opossum
understands by using the built in
Node core utility function util.promisify()
.
const fs = require('fs');
const { promisify } = require('util');
const CircuitBreaker = require('opossum');
const readFile = promisify(fs.readFile);
const breaker = new CircuitBreaker(readFile, options);
breaker.fire('./package.json', 'utf-8')
.then(console.log)
.catch(console.error);
And just for fun, your circuit doesn't even really have to be a function. Not sure when you'd use this - but you could if you wanted to.
const breaker = new CircuitBreaker('foo', options);
breaker.fire()
.then(console.log) // logs 'foo'
.catch(console.error);
The opossum-prometheus
module
can be used to produce metrics that are consumable by Prometheus.
These metrics include information about the circuit itself, for example how many
times it has opened, as well as general Node.js statistics, for example event loop lag.
The opossum-hystrix
module can
be used to produce metrics that are consumable by the Hystrix Dashboard.
You may run into issues related to too many listeners on an EventEmitter
like this.
(node:25619) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 10 unpipe listeners added. Use emitter.setMaxListeners() to increase limit
(node:25619) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 drain listeners added. Use emitter.setMaxListeners() to increase limit
(node:25619) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 error listeners added. Use emitter.setMaxListeners() to increase limit
(node:25619) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 close listeners added. Use emitter.setMaxListeners() to increase limit
(node:25619) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 finish listeners added. Use emitter.setMaxListeners() to increase limit
In some cases, seeing this error might indicate a bug in client code, where many CircuitBreaker
s are inadvertently being created. But there are legitimate scenarios where this may not be the case. For example, it could just be that you need more than 10 CircuitBreaker
s in your app. That's ok.
To get around the error, you can set the number of listeners on the stream.
circuit.stats.getHystrixStream().setMaxListeners(100);
Or it could be that you have a large test suite which exercises some code that creates CircuitBreaker
s and does so repeatedly. If the CircuitBreaker
being created is only needed for the duration of the test, use circuit.shutdown()
when the circuit is no longer in use to clean up all listeners.
Constructs a CircuitBreaker.
Extends EventEmitter
Name | Description |
---|---|
options.timeout Number
|
The time in milliseconds that action should
be allowed to execute before timing out. Timeout can be disabled by setting
this to
false
. Default 10000 (10 seconds)
|
options.maxFailures Number
|
(Deprecated) The number of times the circuit can fail before opening. Default 10. |
options.resetTimeout Number
|
The time in milliseconds to wait before
setting the breaker to
halfOpen
state, and trying the action again.
Default: 30000 (30 seconds)
|
options.rollingCountTimeout Number
|
Sets the duration of the statistical rolling window, in milliseconds. This is how long Opossum keeps metrics for the circuit breaker to use and for publishing. Default: 10000 |
options.rollingCountBuckets Number
|
Sets the number of buckets the rolling statistical window is divided into. So, if options.rollingCountTimeout is 10000, and options.rollingCountBuckets is 10, then the statistical window will be 1000 1 second snapshots in the statistical window. Default: 10 |
options.name String
|
the circuit name to use when reporting stats. Default: the name of the function this circuit controls. |
options.rollingPercentilesEnabled boolean
|
This property indicates whether execution latencies should be tracked and calculated as percentiles. If they are disabled, all summary statistics (mean, percentiles) are returned as -1. Default: false |
options.capacity Number
|
the number of concurrent requests allowed.
If the number currently executing function calls is equal to
options.capacity, further calls to
fire()
are rejected until at least one
of the current requests completes. Default:
Number.MAX_SAFE_INTEGER
.
|
options.errorThresholdPercentage Number
|
the error percentage at which to open the circuit and start short-circuiting requests to fallback. Default: 50 |
options.enabled boolean
|
whether this circuit is enabled upon construction. Default: true |
options.allowWarmUp boolean
|
determines whether to allow failures
without opening the circuit during a brief warmup period (this is the
rollingCountTimeout
property). Default: false
allow before enabling the circuit. This can help in situations where no
matter what your
errorThresholdPercentage
is, if the first execution
times out or fails, the circuit immediately opens.
|
options.volumeThreshold Number
|
the minimum number of requests within
the rolling statistical window that must exist before the circuit breaker
can open. This is similar to
options.allowWarmUp
in that no matter how many
failures there are, if the number of requests within the statistical window
does not exceed this threshold, the circuit will remain closed. Default: 0
|
options.errorFilter Function
|
an optional function that will be called when the circuit's function fails (returns a rejected Promise). If this function returns truthy, the circuit's failPure statistics will not be incremented. This is useful, for example, when you don't want HTTP 404 to trip the circuit, but still want to handle it as a failure case. |
options.cache boolean
|
whether the return value of the first
successful execution of the circuit's function will be cached. Once a value
has been cached that value will be returned for every subsequent execution:
the cache can be cleared using
clearCache
. (The metrics
cacheHit
and
cacheMiss
reflect cache activity.) Default: false
|
Closes the breaker, allowing the action to execute again
void
:
Opens the breaker. Each time the breaker is fired while the circuit is opened, a failed Promise is returned, or if any fallback function has been provided, it is invoked.
If the breaker is already open this call does nothing.
void
:
Shuts down this circuit breaker. All subsequent calls to the circuit will fail, returning a rejected promise.
void
:
The current Status of this CircuitBreaker
Type: Status
Provide a fallback function for this CircuitBreaker. This
function will be executed when the circuit is fire
d and fails.
It will always be preceded by a failure
event, and breaker.fire
returns
a rejected Promise.
((Function | CircuitBreaker))
the fallback function to execute
when the breaker has opened or when a timeout or error occurs.
CircuitBreaker
:
this
Execute the action for this circuit. If the action fails or times out, the returned promise will be rejected. If the action succeeds, the promise will resolve with the resolved value from action. If a fallback function was provided, it will be invoked in the event of any failure or timeout.
Any parameters passed to this function will be proxied to the circuit function.
(...any)
Promise<any>
:
promise resolves with the circuit function's return
value on success or is rejected on failure of the action. Use isOurError()
to determine if a rejection was a result of the circuit breaker or the
action.
Execute the action for this circuit using context
as this
.
If the action fails or times out, the
returned promise will be rejected. If the action succeeds, the promise will
resolve with the resolved value from action. If a fallback function was
provided, it will be invoked in the event of any failure or timeout.
Any parameters in addition to `context will be passed to the circuit function.
(any)
the
this
context used for function execution
(any)
the arguments passed to the action
Promise<any>
:
promise resolves with the circuit function's return
value on success or is rejected on failure of the action.
Clears the cache of this CircuitBreaker
void
:
Provide a health check function to be called periodically. The function
should return a Promise. If the promise is rejected the circuit will open.
This is in addition to the existing circuit behavior as defined by
options.errorThresholdPercentage
in the constructor. For example, if the
health check function provided here always returns a resolved promise, the
circuit can still trip and open if there are failures exceeding the
configured threshold. The health check function is executed within the
circuit breaker's execution context, so this
within the function is the
circuit breaker itself.
(Function)
a health check function which returns a promise.
(Number?)
the amount of time between calls to the health
check function. Default: 5000 (5 seconds)
void
:
interval
is supplied but not a number
Enables this circuit. If the circuit is the disabled state, it will be re-enabled. If not, this is essentially a noop.
void
:
Disables this circuit, causing all calls to the circuit's function to be executed without circuit or fallback protection.
void
:
Emitted after options.resetTimeout
has elapsed, allowing for
a single attempt to call the service again. If that attempt is
successful, the circuit will be closed. Otherwise it remains open.
Type: Number
Emitted when the breaker is reset allowing the action to execute again
Emitted when the breaker opens because the action has
failed more than options.maxFailures
number of times.
Emitted when the circuit breaker has been shut down.
Emitted when the circuit breaker action is executed
Type: any
Emitted when the circuit breaker is using the cache and finds a value.
Emitted when the circuit breaker does not find a value in the cache, but the cache option is enabled.
Emitted when the circuit breaker is open and failing fast
Type: Error
Emitted when the circuit breaker action takes longer than
options.timeout
Type: Error
Emitted when the circuit breaker action succeeds
Type: any
Emitted when the rate limit has been reached and there are no more locks to be obtained.
Type: Error
Emitted with the user-supplied health check function returns a rejected promise.
Type: Error
Emitted when the circuit breaker executes a fallback function
Type: any
Emitted when the circuit breaker action fails
Type: Error
Tracks execution status for a given CircuitBreaker. A Status instance is created for every CircuitBreaker and does not typically need to be created by a user.
A Status instance will listen for all events on the CircuitBreaker
and track them in a rolling statistical window. The window duration is
determined by the rollingCountTimeout
option provided to the
CircuitBreaker. The window consists of an array of Objects,
each representing the counts for a CircuitBreaker's events.
The array's length is determined by the CircuitBreaker's
rollingCountBuckets
option. The duration of each slice of the window
is determined by dividing the rollingCountTimeout
by
rollingCountBuckets
.
Extends EventEmitter
// Creates a 1 second window consisting of ten time slices,
// each 100ms long.
const circuit = circuitBreaker(fs.readFile,
{ rollingCountBuckets: 10, rollingCountTimeout: 1000});
// get the cumulative statistics for the last second
circuit.status.stats;
// get the array of 10, 1 second time slices for the last second
circuit.status.window;
Emitted at each time-slice. Listeners for this event will receive a cumulative snapshot of the current status window.
Type: Object