Class: Faulty::Storage::Redis
- Inherits:
-
Object
- Object
- Faulty::Storage::Redis
- Defined in:
- lib/faulty/storage/redis.rb
Overview
A storage backend for storing circuit state in Redis.
When using this or any networked backend, be sure to evaluate the risk, and set conservative timeouts so that the circuit storage does not cause cascading failures in your application when evaluating circuits. Always wrap this backend with a FaultTolerantProxy to limit the effect of these types of events.
Defined Under Namespace
Classes: Options
Constant Summary collapse
- ENTRY_SEPARATOR =
Separates the time/status for history entry strings
':'
Instance Attribute Summary collapse
-
#options ⇒ Object
readonly
Returns the value of attribute options.
Instance Method Summary collapse
-
#clear ⇒ void
Reset all circuits.
-
#close(circuit) ⇒ Boolean
Mark a circuit as closed.
-
#entry(circuit, time, success, status) ⇒ Status?
Add an entry to storage.
-
#fault_tolerant? ⇒ true
Redis storage is not fault-tolerant.
-
#get_options(circuit) ⇒ Hash
Get the options stored for circuit.
-
#history(circuit) ⇒ Array<Array>
Get the circuit history up to
max_sample_size. -
#initialize(**options) {|Options| ... } ⇒ Redis
constructor
A new instance of Redis.
-
#list ⇒ Array<String>
List all unexpired circuits.
-
#lock(circuit, state) ⇒ void
Lock a circuit open or closed.
-
#open(circuit, opened_at) ⇒ Boolean
Mark a circuit as open.
-
#reopen(circuit, opened_at, previous_opened_at) ⇒ Boolean
Mark a circuit as reopened.
-
#reserve(circuit, reserved_at, previous_reserved_at) ⇒ Boolean
Reserve an exclusive run for this circuit.
-
#reset(circuit) ⇒ void
Reset a circuit.
-
#set_options(circuit, stored_options) ⇒ void
Store the options for a circuit.
-
#status(circuit) ⇒ Status
Get the status of a circuit.
-
#unlock(circuit) ⇒ void
Unlock a circuit.
Constructor Details
#initialize(**options) {|Options| ... } ⇒ Redis
Returns a new instance of Redis.
83 84 85 86 87 88 89 90 |
# File 'lib/faulty/storage/redis.rb', line 83 def initialize(**, &) @options = Options.new(, &) # Ensure JSON is available since we don't explicitly require it JSON # rubocop:disable Lint/Void end |
Instance Attribute Details
#options ⇒ Object (readonly)
Returns the value of attribute options.
16 17 18 |
# File 'lib/faulty/storage/redis.rb', line 16 def @options end |
Instance Method Details
#clear ⇒ void
This method returns an undefined value.
Reset all circuits
This does not empty the list of circuits as returned by #list. This is because that would be a thread-usafe operation that could result in circuits not being in the list.
This implmenentation resets circuits individually, and will be very slow for large numbers of circuits. It should not be used in production code.
281 282 283 |
# File 'lib/faulty/storage/redis.rb', line 281 def clear list.each { |c| reset(c) } end |
#close(circuit) ⇒ Boolean
Mark a circuit as closed
171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/faulty/storage/redis.rb', line 171 def close(circuit) key = state_key(circuit.name) ex = .circuit_ttl result = watch_exec(key, ['open']) do |m| m.set(key, 'closed', ex: ex) m.del(entries_key(circuit.name)) m.del(reserved_at_key(circuit.name)) end result && result[0] == 'OK' end |
#entry(circuit, time, success, status) ⇒ Status?
Add an entry to storage
122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/faulty/storage/redis.rb', line 122 def entry(circuit, time, success, status) key = entries_key(circuit.name) result = pipe do |r| r.call([:sadd, list_key, circuit.name]) r.expire(list_key, .circuit_ttl + .list_granularity) if .circuit_ttl r.lpush(key, "#{time}#{ENTRY_SEPARATOR}#{success ? 1 : 0}") r.ltrim(key, 0, .max_sample_size - 1) r.expire(key, .sample_ttl) if .sample_ttl r.lrange(key, 0, -1) if status end Status.from_entries(map_entries(result.last), **status.to_h) if status end |
#fault_tolerant? ⇒ true
Redis storage is not fault-tolerant
288 289 290 |
# File 'lib/faulty/storage/redis.rb', line 288 def fault_tolerant? false end |
#get_options(circuit) ⇒ Hash
Get the options stored for circuit
97 98 99 100 101 102 |
# File 'lib/faulty/storage/redis.rb', line 97 def (circuit) json = redis { |r| r.get((circuit.name)) } return if json.nil? JSON.parse(json, symbolize_names: true) end |
#history(circuit) ⇒ Array<Array>
Get the circuit history up to max_sample_size
258 259 260 261 |
# File 'lib/faulty/storage/redis.rb', line 258 def history(circuit) entries = redis { |r| r.lrange(entries_key(circuit.name), 0, -1) } map_entries(entries).reverse end |
#list ⇒ Array<String>
List all unexpired circuits
266 267 268 |
# File 'lib/faulty/storage/redis.rb', line 266 def list redis { |r| r.sunion(*all_list_keys) } end |
#lock(circuit, state) ⇒ void
This method returns an undefined value.
Lock a circuit open or closed
The circuit_ttl does not apply to locks
203 204 205 |
# File 'lib/faulty/storage/redis.rb', line 203 def lock(circuit, state) redis { |r| r.set(lock_key(circuit.name), state) } end |
#open(circuit, opened_at) ⇒ Boolean
Mark a circuit as open
141 142 143 144 145 146 147 148 149 150 |
# File 'lib/faulty/storage/redis.rb', line 141 def open(circuit, opened_at) key = state_key(circuit.name) ex = .circuit_ttl result = watch_exec(key, ['closed', nil]) do |m| m.set(key, 'open', ex: ex) m.set(opened_at_key(circuit.name), opened_at, ex: ex) end result && result[0] == 'OK' end |
#reopen(circuit, opened_at, previous_opened_at) ⇒ Boolean
Mark a circuit as reopened
157 158 159 160 161 162 163 164 |
# File 'lib/faulty/storage/redis.rb', line 157 def reopen(circuit, opened_at, previous_opened_at) key = opened_at_key(circuit.name) result = watch_exec(key, [previous_opened_at.to_s]) do |m| m.set(key, opened_at, ex: .circuit_ttl) end result && result[0] == 'OK' end |
#reserve(circuit, reserved_at, previous_reserved_at) ⇒ Boolean
Reserve an exclusive run for this circuit
188 189 190 191 192 193 194 |
# File 'lib/faulty/storage/redis.rb', line 188 def reserve(circuit, reserved_at, previous_reserved_at) key = reserved_at_key(circuit.name) result = watch_exec(key, [previous_reserved_at&.to_s]) do |m| m.set(key, reserved_at, ex: .circuit_ttl) end result && result[0] == 'OK' end |
#reset(circuit) ⇒ void
This method returns an undefined value.
Reset a circuit
221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/faulty/storage/redis.rb', line 221 def reset(circuit) name = circuit.is_a?(Circuit) ? circuit.name : circuit pipe do |r| r.del( entries_key(name), opened_at_key(name), reserved_at_key(name), lock_key(name), (name) ) r.set(state_key(name), 'closed', ex: .circuit_ttl) end end |
#set_options(circuit, stored_options) ⇒ void
This method returns an undefined value.
Store the options for a circuit
These will be serialized as JSON
111 112 113 114 115 |
# File 'lib/faulty/storage/redis.rb', line 111 def (circuit, ) redis do |r| r.set((circuit.name), JSON.dump(), ex: .circuit_ttl) end end |
#status(circuit) ⇒ Status
Get the status of a circuit
240 241 242 243 244 245 246 247 248 249 250 251 |
# File 'lib/faulty/storage/redis.rb', line 240 def status(circuit) futures = {} pipe do |r| futures[:state] = r.get(state_key(circuit.name)) futures[:lock] = r.get(lock_key(circuit.name)) futures[:opened_at] = r.get(opened_at_key(circuit.name)) futures[:reserved_at] = r.get(reserved_at_key(circuit.name)) futures[:entries] = r.lrange(entries_key(circuit.name), 0, -1) end build_status(circuit, futures) end |
#unlock(circuit) ⇒ void
This method returns an undefined value.
Unlock a circuit
212 213 214 |
# File 'lib/faulty/storage/redis.rb', line 212 def unlock(circuit) redis { |r| r.del(lock_key(circuit.name)) } end |