faulty 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +6 -0
- data/.travis.yml +4 -2
- data/CHANGELOG.md +34 -0
- data/Gemfile +17 -0
- data/README.md +177 -47
- data/bin/console +1 -1
- data/faulty.gemspec +3 -10
- data/lib/faulty.rb +155 -43
- data/lib/faulty/cache.rb +1 -1
- data/lib/faulty/cache/default.rb +1 -1
- data/lib/faulty/cache/fault_tolerant_proxy.rb +2 -2
- data/lib/faulty/cache/interface.rb +1 -1
- data/lib/faulty/cache/mock.rb +1 -1
- data/lib/faulty/cache/null.rb +1 -1
- data/lib/faulty/cache/rails.rb +1 -1
- data/lib/faulty/circuit.rb +1 -1
- data/lib/faulty/error.rb +4 -4
- data/lib/faulty/events.rb +3 -2
- data/lib/faulty/events/callback_listener.rb +1 -1
- data/lib/faulty/events/honeybadger_listener.rb +53 -0
- data/lib/faulty/events/listener_interface.rb +1 -1
- data/lib/faulty/events/log_listener.rb +1 -1
- data/lib/faulty/events/notifier.rb +11 -2
- data/lib/faulty/immutable_options.rb +1 -1
- data/lib/faulty/result.rb +2 -2
- data/lib/faulty/status.rb +1 -1
- data/lib/faulty/storage.rb +1 -1
- data/lib/faulty/storage/fault_tolerant_proxy.rb +8 -10
- data/lib/faulty/storage/interface.rb +1 -1
- data/lib/faulty/storage/memory.rb +2 -2
- data/lib/faulty/storage/redis.rb +9 -9
- data/lib/faulty/version.rb +2 -2
- metadata +14 -123
- data/lib/faulty/scope.rb +0 -117
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 55bf688c3615a59f51103633db0902abf3458324945ab855894e5975981868f5
|
4
|
+
data.tar.gz: dab412aad317a79364782a85c2a6a184e55fa2214cd966655d74eeab69e248c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7527a23df0c042ea317e99b8fafdf8c8f96ac5f9f68323ba4c4a2901f8e32dc7ca2b7c62b6b7197e446985eab6901f85dd41c6463407ae3632d86e022626d733
|
7
|
+
data.tar.gz: 7e3d48b82905b0ad2303449a532e85736a0681fa4dcb8a5f9ee64e6ffd10c76fdc760ac97aa9dd882f7a019d335440f9d91f0ba1efbb92f978c5e9e43c5b977a
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -29,6 +29,9 @@ Layout/LineLength:
|
|
29
29
|
Layout/MultilineMethodCallIndentation:
|
30
30
|
EnforcedStyle: indented
|
31
31
|
|
32
|
+
Layout/RescueEnsureAlignment:
|
33
|
+
Enabled: false
|
34
|
+
|
32
35
|
Lint/RaiseException:
|
33
36
|
Enabled: true
|
34
37
|
|
@@ -59,6 +62,9 @@ Metrics/BlockLength:
|
|
59
62
|
Metrics/MethodLength:
|
60
63
|
Max: 30
|
61
64
|
|
65
|
+
Naming/MethodParameterName:
|
66
|
+
MinNameLength: 1
|
67
|
+
|
62
68
|
Style/Documentation:
|
63
69
|
Enabled: false
|
64
70
|
|
data/.travis.yml
CHANGED
@@ -6,6 +6,8 @@ rvm:
|
|
6
6
|
- 2.5
|
7
7
|
- 2.6
|
8
8
|
- 2.7
|
9
|
+
- jruby
|
10
|
+
- truffleruby
|
9
11
|
|
10
12
|
env:
|
11
13
|
global:
|
@@ -22,8 +24,8 @@ before_script:
|
|
22
24
|
- ./cc-test-reporter before-build
|
23
25
|
|
24
26
|
script:
|
25
|
-
-
|
26
|
-
-
|
27
|
+
- bundle exec rubocop
|
28
|
+
- bundle exec rspec --format doc
|
27
29
|
- bin/check-version
|
28
30
|
|
29
31
|
after_script:
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
## Release v0.2.0
|
2
|
+
|
3
|
+
* Remove Scopes and replace them with Faulty instances #9
|
4
|
+
|
5
|
+
### Breaking Changes
|
6
|
+
|
7
|
+
* `Faulty::Scope` has been removed. Use `Faulty.new` instead.
|
8
|
+
* `Faulty` is now a class, not a module
|
9
|
+
|
10
|
+
## Release v0.1.5
|
11
|
+
|
12
|
+
* Fix redis storage to expire state key when using CAS #8
|
13
|
+
|
14
|
+
## Release v0.1.4
|
15
|
+
|
16
|
+
* Improve spec coverage for supporting classes #6
|
17
|
+
* Fix redis bug where concurrent CAS requests could crash #7
|
18
|
+
|
19
|
+
## Release v0.1.3
|
20
|
+
|
21
|
+
* Fix bug where memory storage would delete the newest entries #5
|
22
|
+
* Add HoneybadgerListener for error reporting #4
|
23
|
+
|
24
|
+
## Release v0.1.2
|
25
|
+
|
26
|
+
* Fix Storage::FaultTolerantProxy open and reopen methods #2
|
27
|
+
|
28
|
+
## Release v0.1.1
|
29
|
+
|
30
|
+
* Fix a crash when Storage::FaultTolerantProxy created a status stub #1
|
31
|
+
|
32
|
+
## Release v0.1.0
|
33
|
+
|
34
|
+
Initial public release
|
data/Gemfile
CHANGED
@@ -3,3 +3,20 @@
|
|
3
3
|
source 'https://rubygems.org'
|
4
4
|
|
5
5
|
gemspec
|
6
|
+
|
7
|
+
# We add non-essential gems like debugging tools and CI dependencies
|
8
|
+
# here. This also allows us to use conditional dependencies that depend on the
|
9
|
+
# platform
|
10
|
+
|
11
|
+
not_jruby = %i[ruby mingw x64_mingw].freeze
|
12
|
+
|
13
|
+
gem 'activesupport', '>= 4.2'
|
14
|
+
gem 'bundler', '>= 1.17', '< 3'
|
15
|
+
gem 'byebug', platforms: not_jruby
|
16
|
+
gem 'irb', '~> 1.0'
|
17
|
+
gem 'redcarpet', '~> 3.5', platforms: not_jruby
|
18
|
+
gem 'rspec_junit_formatter', '~> 0.4'
|
19
|
+
# For now, code climate doesn't support simplecov 0.18
|
20
|
+
# https://github.com/codeclimate/test-reporter/issues/413
|
21
|
+
gem 'simplecov', '>= 0.17.1', '< 0.18'
|
22
|
+
gem 'yard', '~> 0.9.25', platforms: not_jruby
|
data/README.md
CHANGED
@@ -67,6 +67,51 @@ end
|
|
67
67
|
For a full list of configuration options, see the
|
68
68
|
[Global Configuration](#global-configuration) section.
|
69
69
|
|
70
|
+
## What is this for?
|
71
|
+
|
72
|
+
Circuit breakers are a fault-tolerance tool for creating separation between your
|
73
|
+
application and external dependencies. For example, your application may call an
|
74
|
+
external API to send a text message:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
TextApi.send(message)
|
78
|
+
```
|
79
|
+
|
80
|
+
In normal operation, this API call is very fast. However what if the texting
|
81
|
+
service started hanging? Your application would quickly use up a lot of
|
82
|
+
resources waiting for requests to return from the service. You could consider
|
83
|
+
adding a timeout to your request:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
TextApi.send(message, timeout: 5)
|
87
|
+
```
|
88
|
+
|
89
|
+
Now your application will terminate requests after 5 seconds, but that could
|
90
|
+
still add up to a lot of resources if you call this thousands of times. Circuit
|
91
|
+
breakers solve this problem.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
Faulty.circuit(:text_api).run do
|
95
|
+
TextApi.send(message, timeout: 5)
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
Now, when the text API hangs, the first few will run and start timing out. This
|
100
|
+
will trip the circuit. After the circuit trips
|
101
|
+
(see [How it Works](#how-it-works)), calls to the text API will be paused for
|
102
|
+
the configured cool down period. Your application resources are not overwhelmed.
|
103
|
+
|
104
|
+
You are free to implement a fallback or error handling however you wish, for
|
105
|
+
example, in this case, you might add the text message to a failure queue:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
Faulty.circuit(:text_api).run do
|
109
|
+
TextApi.send(message, timeout: 5)
|
110
|
+
rescue Faulty::CircuitError => e
|
111
|
+
FailureQueue.enqueue(message)
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
70
115
|
## Basic Usage
|
71
116
|
|
72
117
|
To create a circuit, call `Faulty.circuit`. This can be done as you use the
|
@@ -137,9 +182,9 @@ Faulty's implementation are:
|
|
137
182
|
- Event-based monitoring
|
138
183
|
|
139
184
|
Following the principals of the circuit-breaker pattern, the block given to
|
140
|
-
`run` or `try_run` will always be executed as long as
|
141
|
-
|
142
|
-
|
185
|
+
`run` or `try_run` will always be executed as long as it never raises an error.
|
186
|
+
If the block _does_ raise an error, then the circuit keeps track of the number
|
187
|
+
of runs and the failure rate.
|
143
188
|
|
144
189
|
Once both thresholds are breached, the circuit is opened. Once open, the
|
145
190
|
circuit starts the cool-down period. Any executions within that cool-down are
|
@@ -158,11 +203,12 @@ In addition to the classic circuit breaker design, Faulty implements caching
|
|
158
203
|
that is integrated with the circuit state. See [Caching](#caching) for more
|
159
204
|
detail.
|
160
205
|
|
161
|
-
##
|
206
|
+
## Configuration
|
162
207
|
|
163
|
-
|
164
|
-
illustrates the default values.
|
165
|
-
|
208
|
+
Faulty can be configured with the following configuration options. This example
|
209
|
+
illustrates the default values. In the first example, we configure Faulty
|
210
|
+
globally. The second example shows the same configuration using an instance of
|
211
|
+
Faulty instead of global configuration.
|
166
212
|
|
167
213
|
```ruby
|
168
214
|
Faulty.init do |config|
|
@@ -188,6 +234,25 @@ Faulty.init do |config|
|
|
188
234
|
end
|
189
235
|
```
|
190
236
|
|
237
|
+
Here is the same configuration using an instance of `Faulty`. This is a more
|
238
|
+
object-oriented approach.
|
239
|
+
|
240
|
+
```ruby
|
241
|
+
faulty = Faulty.new do |config|
|
242
|
+
config.cache = Faulty::Cache::Default.new
|
243
|
+
config.storage = Faulty::Storage::Memory.new
|
244
|
+
config.listeners = [Faulty::Events::LogListener.new]
|
245
|
+
config.notifier = Faulty::Events::Notifier.new(config.listeners)
|
246
|
+
end
|
247
|
+
```
|
248
|
+
|
249
|
+
Most of the examples in this README use the global Faulty class methods, but
|
250
|
+
they work the same way when using an instance. Just substitute your instance
|
251
|
+
instead of `Faulty`. There is no preferred way to use Faulty. Choose whichever
|
252
|
+
configuration mechanism works best for your application. Also see
|
253
|
+
[Multiple Configurations](#multiple-configurations) if your application needs
|
254
|
+
to set different options in different scenarios.
|
255
|
+
|
191
256
|
For all Faulty APIs that have configuration, you can also pass in an options
|
192
257
|
hash. For example, `Faulty.init` could be called like this:
|
193
258
|
|
@@ -199,9 +264,9 @@ Faulty.init(cache: Faulty::Cache::Null.new)
|
|
199
264
|
|
200
265
|
A circuit can be created with the following configuration options. Those options
|
201
266
|
are only set once, synchronized across threads, and will persist in-memory until
|
202
|
-
the process exits. If you're using [
|
203
|
-
within the context of each
|
204
|
-
`Faulty.circuit` (or `
|
267
|
+
the process exits. If you're using [multiple configurations](#multiple-configurations),
|
268
|
+
the options are retained within the context of each instance. All options given
|
269
|
+
after the first call to `Faulty.circuit` (or `Faulty#circuit`) are ignored.
|
205
270
|
|
206
271
|
This is because the circuit objects themselves are internally memoized, and are
|
207
272
|
read-only once created.
|
@@ -262,8 +327,8 @@ Faulty.circuit(:api, cache_expires_in: 1800)
|
|
262
327
|
|
263
328
|
Faulty integrates caching into it's circuits in a way that is particularly
|
264
329
|
suited to fault-tolerance. To make use of caching, you must specify the `cache`
|
265
|
-
configuration option when initializing Faulty or creating a
|
266
|
-
using Rails, this is automatically set to the Rails cache.
|
330
|
+
configuration option when initializing Faulty or creating a new Faulty instance.
|
331
|
+
If you're using Rails, this is automatically set to the Rails cache.
|
267
332
|
|
268
333
|
Once your cache is configured, you can use the `cache` parameter when running
|
269
334
|
a circuit to specify a cache key:
|
@@ -338,12 +403,17 @@ events. The full list of events is available from `Faulty::Events::EVENTS`.
|
|
338
403
|
closed. Payload: `circuit`
|
339
404
|
- `circuit_success` - A circuit execution was successful. Payload: `circuit`,
|
340
405
|
`status`
|
341
|
-
- `storage_failure` - A storage backend raised an error. Payload `circuit
|
342
|
-
`action`, `error`
|
406
|
+
- `storage_failure` - A storage backend raised an error. Payload `circuit` (can
|
407
|
+
be nil), `action`, `error`
|
343
408
|
|
344
409
|
By default events are logged using `Faulty::Events::LogListener`, but that can
|
345
410
|
be replaced, or additional listeners can be added.
|
346
411
|
|
412
|
+
### CallbackListener
|
413
|
+
|
414
|
+
The callback listener is useful for ad-hoc handling of events. You can specify
|
415
|
+
an event handler by calling a method on the callback handler by the same name.
|
416
|
+
|
347
417
|
```ruby
|
348
418
|
Faulty.init do |config|
|
349
419
|
# Replace the default listener with a custom callback listener
|
@@ -356,6 +426,17 @@ Faulty.init do |config|
|
|
356
426
|
end
|
357
427
|
```
|
358
428
|
|
429
|
+
### Other Built-in Listeners
|
430
|
+
|
431
|
+
In addition to the log and callback listeners, Faulty intends to implement
|
432
|
+
built-in service-specific handlers to make it easy to integrate with monitoring
|
433
|
+
and reporting software.
|
434
|
+
|
435
|
+
- `Faulty::Events::HoneybadgerListener`: Reports circuit and backend errors to
|
436
|
+
the Honeybadger error reporting service.
|
437
|
+
|
438
|
+
### Custom Listeners
|
439
|
+
|
359
440
|
You can implement your own listener by following the documentation in
|
360
441
|
`Faulty::Events::ListenerInterface`. For example:
|
361
442
|
|
@@ -405,7 +486,7 @@ Faulty.init do |config|
|
|
405
486
|
storage.key_prefix = 'faulty'
|
406
487
|
|
407
488
|
# A string to separate the parts of the redis key
|
408
|
-
storage.key_separator
|
489
|
+
storage.key_separator = ':'
|
409
490
|
|
410
491
|
# The maximum number of circuit runs that will be stored
|
411
492
|
storage.max_sample_size = 100
|
@@ -416,11 +497,11 @@ Faulty.init do |config|
|
|
416
497
|
end
|
417
498
|
```
|
418
499
|
|
419
|
-
|
500
|
+
## Listing Circuits
|
420
501
|
|
421
502
|
For monitoring or debugging, you may need to retrieve a list of all circuit
|
422
|
-
names. This is possible with `Faulty.list_circuits` (or
|
423
|
-
|
503
|
+
names. This is possible with `Faulty.list_circuits` (or `Faulty#list_circuits`
|
504
|
+
if you're using an instance).
|
424
505
|
|
425
506
|
You can get a list of all circuit statuses by mapping those names to their
|
426
507
|
status objects. Be careful though, since this could cause performance issues for
|
@@ -432,73 +513,103 @@ statuses = Faulty.list_circuits.map do |name|
|
|
432
513
|
end
|
433
514
|
```
|
434
515
|
|
435
|
-
##
|
516
|
+
## Locking Circuits
|
517
|
+
|
518
|
+
It is possible to lock a circuit open or closed. A circuit that is locked open
|
519
|
+
will never execute its block, and always raise an `Faulty::OpenCircuitError`.
|
520
|
+
This is useful in cases where you need to manually disable a dependency
|
521
|
+
entirely. If a cached value is available, that will be returned from the circuit
|
522
|
+
until it expires, even outside its refresh period.
|
523
|
+
|
524
|
+
```ruby
|
525
|
+
Faulty.circuit(:broken_api).lock_open!
|
526
|
+
```
|
527
|
+
|
528
|
+
A circuit that is locked closed will never trip. This is useful in cases where a
|
529
|
+
circuit is continuously tripping incorrectly. If a cached value is available, it
|
530
|
+
will have the same behavior as an unlocked circuit.
|
531
|
+
|
532
|
+
```ruby
|
533
|
+
Faulty.circuit(:false_positive).lock_closed!
|
534
|
+
```
|
535
|
+
|
536
|
+
To remove a lock of either type:
|
537
|
+
|
538
|
+
```ruby
|
539
|
+
Faulty.circuit(:fixed).unlock!
|
540
|
+
```
|
541
|
+
|
542
|
+
Locking or unlocking a circuit has no concurrency guarantees, so it's not
|
543
|
+
recommended to lock or unlock circuits from production code. Instead, locks are
|
544
|
+
intended as an emergency tool for troubleshooting and debugging.
|
545
|
+
|
546
|
+
## Multiple Configurations
|
436
547
|
|
437
548
|
It is possible to have multiple configurations of Faulty running within the same
|
438
|
-
process. The most common
|
549
|
+
process. The most common setup is to simply use `Faulty.init` to
|
439
550
|
configure Faulty globally, however it is possible to have additional
|
440
|
-
configurations
|
551
|
+
configurations.
|
441
552
|
|
442
|
-
### The default
|
553
|
+
### The default instance
|
443
554
|
|
444
|
-
When you call `Faulty.init`, you are actually creating the default
|
445
|
-
can access this
|
555
|
+
When you call `Faulty.init`, you are actually creating the default instance of
|
556
|
+
`Faulty`. You can access this instance directly by calling `Faulty.default`.
|
446
557
|
|
447
558
|
```ruby
|
448
|
-
# We create the default
|
559
|
+
# We create the default instance
|
449
560
|
Faulty.init
|
450
561
|
|
451
|
-
# Access the default
|
452
|
-
|
562
|
+
# Access the default instance
|
563
|
+
faulty = Faulty.default
|
453
564
|
|
454
|
-
# Alternatively, access the
|
455
|
-
|
565
|
+
# Alternatively, access the instance by name
|
566
|
+
faulty = Faulty[:default]
|
456
567
|
```
|
457
568
|
|
458
|
-
You can rename the default
|
569
|
+
You can rename the default instance if desired:
|
459
570
|
|
460
571
|
```ruby
|
461
572
|
Faulty.init(:custom_default)
|
462
573
|
|
463
|
-
|
464
|
-
|
574
|
+
instance = Faulty.default
|
575
|
+
instance = Faulty[:custom_default]
|
465
576
|
```
|
466
577
|
|
467
|
-
### Multiple
|
578
|
+
### Multiple Instances
|
468
579
|
|
469
|
-
If you want multiple
|
580
|
+
If you want multiple instance, but want global, thread-safe access to
|
470
581
|
them, you can use `Faulty.register`:
|
471
582
|
|
472
583
|
```ruby
|
473
|
-
|
584
|
+
api_faulty = Faulty.new do |config|
|
474
585
|
# This accepts the same options as Faulty.init
|
475
586
|
end
|
476
587
|
|
477
|
-
Faulty.register(:api,
|
588
|
+
Faulty.register(:api, api_faulty)
|
478
589
|
|
479
|
-
# Now access the
|
590
|
+
# Now access the instance globally
|
480
591
|
Faulty[:api]
|
481
592
|
```
|
482
593
|
|
483
594
|
When you call `Faulty.circuit`, that's the same as calling
|
484
|
-
`Faulty.default.circuit`, so you can apply the same
|
485
|
-
|
595
|
+
`Faulty.default.circuit`, so you can apply the same principal to any other
|
596
|
+
registered Faulty instance:
|
486
597
|
|
487
598
|
```ruby
|
488
599
|
Faulty[:api].circuit(:api_circuit).run { 'ok' }
|
489
600
|
```
|
490
601
|
|
491
|
-
### Standalone
|
602
|
+
### Standalone Instances
|
492
603
|
|
493
|
-
If you choose, you can use Faulty
|
494
|
-
|
604
|
+
If you choose, you can use Faulty instances without registering them globally.
|
605
|
+
This is more object-oriented and is necessary if you use dependency injection.
|
495
606
|
|
496
607
|
```ruby
|
497
|
-
faulty = Faulty
|
608
|
+
faulty = Faulty.new
|
498
609
|
faulty.circuit(:standalone_circuit)
|
499
610
|
```
|
500
611
|
|
501
|
-
Calling
|
612
|
+
Calling `#circuit` on the instance still has the same memoization behavior that
|
502
613
|
`Faulty.circuit` has, so subsequent calls to the same circuit will return a
|
503
614
|
memoized circuit object.
|
504
615
|
|
@@ -545,15 +656,34 @@ would be useful for other users.
|
|
545
656
|
Faulty has its own opinions about how to implement a circuit breaker in Ruby,
|
546
657
|
but there are and have been many other options:
|
547
658
|
|
548
|
-
|
549
|
-
|
550
|
-
- [
|
659
|
+
### Currently Active
|
660
|
+
|
661
|
+
- [semian](https://github.com/Shopify/semian): A resiliency toolkit that
|
662
|
+
includes circuit breakers. It uses adapters to auto-wire circuits, and it has
|
663
|
+
only host-local storage by design.
|
664
|
+
- [circuitbox](https://github.com/yammer/circuitbox): Similar in function to
|
665
|
+
Faulty, but with a different API. It uses Moneta to abstract circuit storage
|
666
|
+
to allow any key-value store.
|
667
|
+
|
668
|
+
### Previous Work
|
669
|
+
|
670
|
+
- [circuit_breaker-ruby](https://github.com/scripbox/circuit_breaker-ruby) (no
|
671
|
+
recent activity)
|
672
|
+
- [stoplight](https://github.com/orgsync/stoplight) (unmaintained)
|
551
673
|
- [circuit_breaker](https://github.com/wooga/circuit_breaker) (archived)
|
552
674
|
- [simple_circuit_breaker](https://github.com/soundcloud/simple_circuit_breaker)
|
553
675
|
(unmaintained)
|
554
676
|
- [breaker](https://github.com/ahawkins/breaker) (unmaintained)
|
555
677
|
- [circuit_b](https://github.com/alg/circuit_b) (unmaintained)
|
556
678
|
|
679
|
+
### Faulty's Unique Features
|
680
|
+
|
681
|
+
- Simple API but configurable for advanced users
|
682
|
+
- Pluggable storage backends (circuitbox also has this)
|
683
|
+
- Global, or object-oriented configuration with multiple instances
|
684
|
+
- Integrated caching support tailored for fault-tolerance
|
685
|
+
- Manually lock circuits open or closed
|
686
|
+
|
557
687
|
[api docs]: https://www.rubydoc.info/github/ParentSquare/faulty/master
|
558
688
|
[martin fowler]: https://www.martinfowler.com/bliki/CircuitBreaker.html
|
559
689
|
[hystrix]: https://github.com/Netflix/Hystrix/wiki/How-it-Works
|