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.
-
#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(**, &block) @options = Options.new(, &block) # 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.
275 276 277 |
# File 'lib/faulty/storage/redis.rb', line 275 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 |
# 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)) 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
282 283 284 |
# File 'lib/faulty/storage/redis.rb', line 282 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
252 253 254 255 |
# File 'lib/faulty/storage/redis.rb', line 252 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
260 261 262 |
# File 'lib/faulty/storage/redis.rb', line 260 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
189 190 191 |
# File 'lib/faulty/storage/redis.rb', line 189 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 |
#reset(circuit) ⇒ void
This method returns an undefined value.
Reset a circuit
207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/faulty/storage/redis.rb', line 207 def reset(circuit) name = circuit.is_a?(Circuit) ? circuit.name : circuit pipe do |r| r.del( entries_key(name), opened_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
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/faulty/storage/redis.rb', line 225 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[:entries] = r.lrange(entries_key(circuit.name), 0, -1) end state = futures[:state].value&.to_sym || :closed opened_at = futures[:opened_at].value ? Float(futures[:opened_at].value) : nil opened_at = Faulty.current_time - .circuit_ttl if state == :open && opened_at.nil? Faulty::Status.from_entries( map_entries(futures[:entries].value), state: state, lock: futures[:lock].value&.to_sym, opened_at: opened_at, options: circuit. ) end |
#unlock(circuit) ⇒ void
This method returns an undefined value.
Unlock a circuit
198 199 200 |
# File 'lib/faulty/storage/redis.rb', line 198 def unlock(circuit) redis { |r| r.del(lock_key(circuit.name)) } end |