redis 3.0.0 → 4.5.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.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +315 -0
  3. data/README.md +301 -58
  4. data/lib/redis/client.rb +383 -88
  5. data/lib/redis/cluster/command.rb +81 -0
  6. data/lib/redis/cluster/command_loader.rb +33 -0
  7. data/lib/redis/cluster/key_slot_converter.rb +72 -0
  8. data/lib/redis/cluster/node.rb +108 -0
  9. data/lib/redis/cluster/node_key.rb +31 -0
  10. data/lib/redis/cluster/node_loader.rb +37 -0
  11. data/lib/redis/cluster/option.rb +93 -0
  12. data/lib/redis/cluster/slot.rb +86 -0
  13. data/lib/redis/cluster/slot_loader.rb +49 -0
  14. data/lib/redis/cluster.rb +291 -0
  15. data/lib/redis/connection/command_helper.rb +7 -10
  16. data/lib/redis/connection/hiredis.rb +12 -8
  17. data/lib/redis/connection/registry.rb +2 -1
  18. data/lib/redis/connection/ruby.rb +266 -74
  19. data/lib/redis/connection/synchrony.rb +41 -14
  20. data/lib/redis/connection.rb +4 -2
  21. data/lib/redis/distributed.rb +258 -76
  22. data/lib/redis/errors.rb +48 -0
  23. data/lib/redis/hash_ring.rb +31 -73
  24. data/lib/redis/pipeline.rb +74 -18
  25. data/lib/redis/subscribe.rb +24 -13
  26. data/lib/redis/version.rb +3 -1
  27. data/lib/redis.rb +2068 -464
  28. metadata +63 -160
  29. data/.gitignore +0 -10
  30. data/.order +0 -169
  31. data/.travis/Gemfile +0 -11
  32. data/.travis.yml +0 -50
  33. data/.yardopts +0 -3
  34. data/Rakefile +0 -392
  35. data/benchmarking/logging.rb +0 -62
  36. data/benchmarking/pipeline.rb +0 -51
  37. data/benchmarking/speed.rb +0 -21
  38. data/benchmarking/suite.rb +0 -24
  39. data/benchmarking/worker.rb +0 -71
  40. data/examples/basic.rb +0 -15
  41. data/examples/dist_redis.rb +0 -43
  42. data/examples/incr-decr.rb +0 -17
  43. data/examples/list.rb +0 -26
  44. data/examples/pubsub.rb +0 -31
  45. data/examples/sets.rb +0 -36
  46. data/examples/unicorn/config.ru +0 -3
  47. data/examples/unicorn/unicorn.rb +0 -20
  48. data/redis.gemspec +0 -41
  49. data/test/blocking_commands_test.rb +0 -42
  50. data/test/command_map_test.rb +0 -30
  51. data/test/commands_on_hashes_test.rb +0 -21
  52. data/test/commands_on_lists_test.rb +0 -20
  53. data/test/commands_on_sets_test.rb +0 -77
  54. data/test/commands_on_sorted_sets_test.rb +0 -109
  55. data/test/commands_on_strings_test.rb +0 -83
  56. data/test/commands_on_value_types_test.rb +0 -99
  57. data/test/connection_handling_test.rb +0 -189
  58. data/test/db/.gitignore +0 -1
  59. data/test/distributed_blocking_commands_test.rb +0 -46
  60. data/test/distributed_commands_on_hashes_test.rb +0 -10
  61. data/test/distributed_commands_on_lists_test.rb +0 -22
  62. data/test/distributed_commands_on_sets_test.rb +0 -83
  63. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  64. data/test/distributed_commands_on_strings_test.rb +0 -48
  65. data/test/distributed_commands_on_value_types_test.rb +0 -87
  66. data/test/distributed_commands_requiring_clustering_test.rb +0 -148
  67. data/test/distributed_connection_handling_test.rb +0 -23
  68. data/test/distributed_internals_test.rb +0 -15
  69. data/test/distributed_key_tags_test.rb +0 -52
  70. data/test/distributed_persistence_control_commands_test.rb +0 -26
  71. data/test/distributed_publish_subscribe_test.rb +0 -92
  72. data/test/distributed_remote_server_control_commands_test.rb +0 -53
  73. data/test/distributed_scripting_test.rb +0 -102
  74. data/test/distributed_sorting_test.rb +0 -20
  75. data/test/distributed_test.rb +0 -58
  76. data/test/distributed_transactions_test.rb +0 -32
  77. data/test/encoding_test.rb +0 -18
  78. data/test/error_replies_test.rb +0 -59
  79. data/test/helper.rb +0 -188
  80. data/test/helper_test.rb +0 -22
  81. data/test/internals_test.rb +0 -214
  82. data/test/lint/blocking_commands.rb +0 -124
  83. data/test/lint/hashes.rb +0 -162
  84. data/test/lint/lists.rb +0 -143
  85. data/test/lint/sets.rb +0 -96
  86. data/test/lint/sorted_sets.rb +0 -201
  87. data/test/lint/strings.rb +0 -157
  88. data/test/lint/value_types.rb +0 -106
  89. data/test/persistence_control_commands_test.rb +0 -26
  90. data/test/pipelining_commands_test.rb +0 -195
  91. data/test/publish_subscribe_test.rb +0 -153
  92. data/test/remote_server_control_commands_test.rb +0 -104
  93. data/test/scripting_test.rb +0 -78
  94. data/test/sorting_test.rb +0 -45
  95. data/test/support/connection/hiredis.rb +0 -1
  96. data/test/support/connection/ruby.rb +0 -1
  97. data/test/support/connection/synchrony.rb +0 -17
  98. data/test/support/redis_mock.rb +0 -92
  99. data/test/support/wire/synchrony.rb +0 -24
  100. data/test/support/wire/thread.rb +0 -5
  101. data/test/synchrony_driver.rb +0 -57
  102. data/test/test.conf +0 -9
  103. data/test/thread_safety_test.rb +0 -32
  104. data/test/transactions_test.rb +0 -244
  105. data/test/unknown_commands_test.rb +0 -14
  106. data/test/url_param_test.rb +0 -64
data/README.md CHANGED
@@ -1,29 +1,17 @@
1
- # redis-rb [![Build Status][travis-image]][travis-link]
1
+ # redis-rb [![Build Status][gh-actions-image]][gh-actions-link] [![Inline docs][inchpages-image]][inchpages-link]
2
2
 
3
- [travis-image]: https://secure.travis-ci.org/redis/redis-rb.png?branch=master
4
- [travis-link]: http://travis-ci.org/redis/redis-rb
5
- [travis-home]: http://travis-ci.org/
3
+ A Ruby client that tries to match [Redis][redis-home]' API one-to-one, while still
4
+ providing an idiomatic interface.
6
5
 
7
- A Ruby client library for [Redis][redis-home].
8
-
9
- [redis-home]: http://redis.io
10
-
11
- A Ruby client that tries to match Redis' API one-to-one, while still
12
- providing an idiomatic interface. It features thread-safety, client-side
13
- sharding, pipelining, and an obsession for performance.
14
-
15
- ## Upgrading from 2.x to 3.0
16
-
17
- Please refer to the [CHANGELOG][changelog-3.0.0] for a summary of the
18
- most important changes, as well as a full list of changes.
19
-
20
- [changelog-3.0.0]: https://github.com/redis/redis-rb/blob/master/CHANGELOG.md#300
6
+ See [RubyDoc.info][rubydoc] for the API docs of the latest published gem.
21
7
 
22
8
  ## Getting started
23
9
 
24
- As of version 2.0 this client only targets Redis version 2.0 and higher.
25
- You can use an older version of this client if you need to interface
26
- with a Redis instance older than 2.0, but this is no longer supported.
10
+ Install with:
11
+
12
+ ```
13
+ $ gem install redis
14
+ ```
27
15
 
28
16
  You can connect to Redis by instantiating the `Redis` class:
29
17
 
@@ -33,18 +21,43 @@ require "redis"
33
21
  redis = Redis.new
34
22
  ```
35
23
 
36
- This assumes Redis was started with a default configuration, and it
24
+ This assumes Redis was started with a default configuration, and is
37
25
  listening on `localhost`, port 6379. If you need to connect to a remote
38
26
  server or a different port, try:
39
27
 
40
28
  ```ruby
41
- redis = Redis.new(:host => "10.0.1.1", :port => 6380)
29
+ redis = Redis.new(host: "10.0.1.1", port: 6380, db: 15)
42
30
  ```
43
31
 
32
+ You can also specify connection options as a [`redis://` URL][redis-url]:
33
+
34
+ ```ruby
35
+ redis = Redis.new(url: "redis://:p4ssw0rd@10.0.1.1:6380/15")
36
+ ```
37
+
38
+ The client expects passwords with special chracters to be URL-encoded (i.e.
39
+ `CGI.escape(password)`).
40
+
41
+ By default, the client will try to read the `REDIS_URL` environment variable
42
+ and use that as URL to connect to. The above statement is therefore equivalent
43
+ to setting this environment variable and calling `Redis.new` without arguments.
44
+
44
45
  To connect to Redis listening on a Unix socket, try:
45
46
 
46
47
  ```ruby
47
- redis = Redis.new(:path => "/tmp/redis.sock")
48
+ redis = Redis.new(path: "/tmp/redis.sock")
49
+ ```
50
+
51
+ To connect to a password protected Redis instance, use:
52
+
53
+ ```ruby
54
+ redis = Redis.new(password: "mysecret")
55
+ ```
56
+
57
+ To connect a Redis instance using [ACL](https://redis.io/topics/acl), use:
58
+
59
+ ```ruby
60
+ redis = Redis.new(username: 'myname', password: 'mysecret')
48
61
  ```
49
62
 
50
63
  The Redis class exports methods that are named identical to the commands
@@ -52,8 +65,6 @@ they execute. The arguments these methods accept are often identical to
52
65
  the arguments specified on the [Redis website][redis-commands]. For
53
66
  instance, the `SET` and `GET` commands can be called like this:
54
67
 
55
- [redis-commands]: http://redis.io/commands
56
-
57
68
  ```ruby
58
69
  redis.set("mykey", "hello world")
59
70
  # => "OK"
@@ -62,15 +73,92 @@ redis.get("mykey")
62
73
  # => "hello world"
63
74
  ```
64
75
 
65
- All commands, their arguments and return values are documented, and
66
- available on [rdoc.info][rdoc].
76
+ All commands, their arguments, and return values are documented and
77
+ available on [RubyDoc.info][rubydoc].
78
+
79
+ ## Sentinel support
80
+
81
+ The client is able to perform automatic failover by using [Redis
82
+ Sentinel](http://redis.io/topics/sentinel). Make sure to run Redis 2.8+
83
+ if you want to use this feature.
84
+
85
+ To connect using Sentinel, use:
86
+
87
+ ```ruby
88
+ SENTINELS = [{ host: "127.0.0.1", port: 26380 },
89
+ { host: "127.0.0.1", port: 26381 }]
90
+
91
+ redis = Redis.new(url: "redis://mymaster", sentinels: SENTINELS, role: :master)
92
+ ```
93
+
94
+ * The master name identifies a group of Redis instances composed of a master
95
+ and one or more slaves (`mymaster` in the example).
96
+
97
+ * It is possible to optionally provide a role. The allowed roles are `master`
98
+ and `slave`. When the role is `slave`, the client will try to connect to a
99
+ random slave of the specified master. If a role is not specified, the client
100
+ will connect to the master.
101
+
102
+ * When using the Sentinel support you need to specify a list of sentinels to
103
+ connect to. The list does not need to enumerate all your Sentinel instances,
104
+ but a few so that if one is down the client will try the next one. The client
105
+ is able to remember the last Sentinel that was able to reply correctly and will
106
+ use it for the next requests.
107
+
108
+ If you want to [authenticate](https://redis.io/topics/sentinel#configuring-sentinel-instances-with-authentication) Sentinel itself, you must specify the `password` option per instance.
109
+
110
+ ```ruby
111
+ SENTINELS = [{ host: '127.0.0.1', port: 26380, password: 'mysecret' },
112
+ { host: '127.0.0.1', port: 26381, password: 'mysecret' }]
113
+
114
+ redis = Redis.new(host: 'mymaster', sentinels: SENTINELS, role: :master)
115
+ ```
116
+
117
+ ## Cluster support
118
+
119
+ `redis-rb` supports [clustering](https://redis.io/topics/cluster-spec).
120
+
121
+ ```ruby
122
+ # Nodes can be passed to the client as an array of connection URLs.
123
+ nodes = (7000..7005).map { |port| "redis://127.0.0.1:#{port}" }
124
+ redis = Redis.new(cluster: nodes)
125
+
126
+ # You can also specify the options as a Hash. The options are the same as for a single server connection.
127
+ (7000..7005).map { |port| { host: '127.0.0.1', port: port } }
128
+ ```
129
+
130
+ You can also specify only a subset of the nodes, and the client will discover the missing ones using the [CLUSTER NODES](https://redis.io/commands/cluster-nodes) command.
131
+
132
+ ```ruby
133
+ Redis.new(cluster: %w[redis://127.0.0.1:7000])
134
+ ```
135
+
136
+ If you want [the connection to be able to read from any replica](https://redis.io/commands/readonly), you must pass the `replica: true`. Note that this connection won't be usable to write keys.
137
+
138
+ ```ruby
139
+ Redis.new(cluster: nodes, replica: true)
140
+ ```
141
+
142
+ The calling code is responsible for [avoiding cross slot commands](https://redis.io/topics/cluster-spec#keys-distribution-model).
143
+
144
+ ```ruby
145
+ redis = Redis.new(cluster: %w[redis://127.0.0.1:7000])
146
+
147
+ redis.mget('key1', 'key2')
148
+ #=> Redis::CommandError (CROSSSLOT Keys in request don't hash to the same slot)
149
+
150
+ redis.mget('{key}1', '{key}2')
151
+ #=> [nil, nil]
152
+ ```
67
153
 
68
- [rdoc]: http://rdoc.info/github/redis/redis-rb/
154
+ * The client automatically reconnects after a failover occurred, but the caller is responsible for handling errors while it is happening.
155
+ * The client support permanent node failures, and will reroute requests to promoted slaves.
156
+ * The client supports `MOVED` and `ASK` redirections transparently.
69
157
 
70
158
  ## Storing objects
71
159
 
72
- Redis only stores strings as values. If you want to store an object, you
73
- can use a serialization mechanism such as JSON:
160
+ Redis "string" types can be used to store serialized Ruby objects, for
161
+ example with JSON:
74
162
 
75
163
  ```ruby
76
164
  require "json"
@@ -124,7 +212,7 @@ end
124
212
  Replies to commands in a pipeline can be accessed via the *futures* they
125
213
  emit (since redis-rb 3.0). All calls inside a pipeline block return a
126
214
  `Future` object, which responds to the `#value` method. When the
127
- pipeline has succesfully executed, all futures are assigned their
215
+ pipeline has successfully executed, all futures are assigned their
128
216
  respective replies and can be used.
129
217
 
130
218
  ```ruby
@@ -140,6 +228,164 @@ end
140
228
  # => 1
141
229
  ```
142
230
 
231
+ ## Error Handling
232
+
233
+ In general, if something goes wrong you'll get an exception. For example, if
234
+ it can't connect to the server a `Redis::CannotConnectError` error will be raised.
235
+
236
+ ```ruby
237
+ begin
238
+ redis.ping
239
+ rescue StandardError => e
240
+ e.inspect
241
+ # => #<Redis::CannotConnectError: Timed out connecting to Redis on 10.0.1.1:6380>
242
+
243
+ e.message
244
+ # => Timed out connecting to Redis on 10.0.1.1:6380
245
+ end
246
+ ```
247
+
248
+ See lib/redis/errors.rb for information about what exceptions are possible.
249
+
250
+ ## Timeouts
251
+
252
+ The client allows you to configure connect, read, and write timeouts.
253
+ Passing a single `timeout` option will set all three values:
254
+
255
+ ```ruby
256
+ Redis.new(:timeout => 1)
257
+ ```
258
+
259
+ But you can use specific values for each of them:
260
+
261
+ ```ruby
262
+ Redis.new(
263
+ :connect_timeout => 0.2,
264
+ :read_timeout => 1.0,
265
+ :write_timeout => 0.5
266
+ )
267
+ ```
268
+
269
+ All timeout values are specified in seconds.
270
+
271
+ When using pub/sub, you can subscribe to a channel using a timeout as well:
272
+
273
+ ```ruby
274
+ redis = Redis.new(reconnect_attempts: 0)
275
+ redis.subscribe_with_timeout(5, "news") do |on|
276
+ on.message do |channel, message|
277
+ # ...
278
+ end
279
+ end
280
+ ```
281
+
282
+ If no message is received after 5 seconds, the client will unsubscribe.
283
+
284
+ ## Reconnections
285
+
286
+ The client allows you to configure how many `reconnect_attempts` it should
287
+ complete before declaring a connection as failed. Furthermore, you may want
288
+ to control the maximum duration between reconnection attempts with
289
+ `reconnect_delay` and `reconnect_delay_max`.
290
+
291
+ ```ruby
292
+ Redis.new(
293
+ :reconnect_attempts => 10,
294
+ :reconnect_delay => 1.5,
295
+ :reconnect_delay_max => 10.0,
296
+ )
297
+ ```
298
+
299
+ The delay values are specified in seconds. With the above configuration, the
300
+ client would attempt 10 reconnections, exponentially increasing the duration
301
+ between each attempt but it never waits longer than `reconnect_delay_max`.
302
+
303
+ This is the retry algorithm:
304
+
305
+ ```ruby
306
+ attempt_wait_time = [(reconnect_delay * 2**(attempt-1)), reconnect_delay_max].min
307
+ ```
308
+
309
+ **By default**, this gem will only **retry a connection once** and then fail, but with the
310
+ above configuration the reconnection attempt would look like this:
311
+
312
+ #|Attempt wait time|Total wait time
313
+ :-:|:-:|:-:
314
+ 1|1.5s|1.5s
315
+ 2|3.0s|4.5s
316
+ 3|6.0s|10.5s
317
+ 4|10.0s|20.5s
318
+ 5|10.0s|30.5s
319
+ 6|10.0s|40.5s
320
+ 7|10.0s|50.5s
321
+ 8|10.0s|60.5s
322
+ 9|10.0s|70.5s
323
+ 10|10.0s|80.5s
324
+
325
+ So if the reconnection attempt #10 succeeds 70 seconds have elapsed trying
326
+ to reconnect, this is likely fine in long-running background processes, but if
327
+ you use Redis to drive your website you might want to have a lower
328
+ `reconnect_delay_max` or have less `reconnect_attempts`.
329
+
330
+ ## SSL/TLS Support
331
+
332
+ This library supports natively terminating client side SSL/TLS connections
333
+ when talking to Redis via a server-side proxy such as [stunnel], [hitch],
334
+ or [ghostunnel].
335
+
336
+ To enable SSL support, pass the `:ssl => true` option when configuring the
337
+ Redis client, or pass in `:url => "rediss://..."` (like HTTPS for Redis).
338
+ You will also need to pass in an `:ssl_params => { ... }` hash used to
339
+ configure the `OpenSSL::SSL::SSLContext` object used for the connection:
340
+
341
+ ```ruby
342
+ redis = Redis.new(
343
+ :url => "rediss://:p4ssw0rd@10.0.1.1:6381/15",
344
+ :ssl_params => {
345
+ :ca_file => "/path/to/ca.crt"
346
+ }
347
+ )
348
+ ```
349
+
350
+ The options given to `:ssl_params` are passed directly to the
351
+ `OpenSSL::SSL::SSLContext#set_params` method and can be any valid attribute
352
+ of the SSL context. Please see the [OpenSSL::SSL::SSLContext documentation]
353
+ for all of the available attributes.
354
+
355
+ Here is an example of passing in params that can be used for SSL client
356
+ certificate authentication (a.k.a. mutual TLS):
357
+
358
+ ```ruby
359
+ redis = Redis.new(
360
+ :url => "rediss://:p4ssw0rd@10.0.1.1:6381/15",
361
+ :ssl_params => {
362
+ :ca_file => "/path/to/ca.crt",
363
+ :cert => OpenSSL::X509::Certificate.new(File.read("client.crt")),
364
+ :key => OpenSSL::PKey::RSA.new(File.read("client.key"))
365
+ }
366
+ )
367
+ ```
368
+
369
+ [stunnel]: https://www.stunnel.org/
370
+ [hitch]: https://hitch-tls.org/
371
+ [ghostunnel]: https://github.com/square/ghostunnel
372
+ [OpenSSL::SSL::SSLContext documentation]: http://ruby-doc.org/stdlib-2.3.0/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html
373
+
374
+ *NOTE:* SSL is only supported by the default "Ruby" driver
375
+
376
+
377
+ ## Expert-Mode Options
378
+
379
+ - `inherit_socket: true`: disable safety check that prevents a forked child
380
+ from sharing a socket with its parent; this is potentially useful in order to mitigate connection churn when:
381
+ - many short-lived forked children of one process need to talk
382
+ to redis, AND
383
+ - your own code prevents the parent process from using the redis
384
+ connection while a child is alive
385
+
386
+ Improper use of `inherit_socket` will result in corrupted and/or incorrect
387
+ responses.
388
+
143
389
  ## Alternate drivers
144
390
 
145
391
  By default, redis-rb uses Ruby's socket library to talk with Redis.
@@ -163,7 +409,7 @@ It is best to use hiredis when you have large replies (for example:
163
409
  In your Gemfile, include hiredis:
164
410
 
165
411
  ```ruby
166
- gem "redis", "~> 3.0.0.rc2"
412
+ gem "redis", "~> 3.0.1"
167
413
  gem "hiredis", "~> 0.4.5"
168
414
  ```
169
415
 
@@ -186,12 +432,12 @@ protocol.
186
432
  In your Gemfile, include em-synchrony and hiredis:
187
433
 
188
434
  ```ruby
189
- gem "redis", "~> 3.0.0.rc2"
435
+ gem "redis", "~> 3.0.1"
190
436
  gem "hiredis", "~> 0.4.5"
191
437
  gem "em-synchrony"
192
438
  ```
193
439
 
194
- When instantiating the client object, specify hiredis:
440
+ When instantiating the client object, specify synchrony:
195
441
 
196
442
  ```ruby
197
443
  redis = Redis.new(:driver => :synchrony)
@@ -199,34 +445,31 @@ redis = Redis.new(:driver => :synchrony)
199
445
 
200
446
  ## Testing
201
447
 
202
- This library is tested using [Travis][travis-home], where it is tested
203
- against the following interpreters and drivers:
448
+ This library is tested against recent Ruby and Redis versions.
449
+ Check [Github Actions][gh-actions-link] for the exact versions supported.
204
450
 
205
- * MRI 1.8.7 (drivers: ruby, hiredis)
206
- * MRI 1.9.2 (drivers: ruby, hiredis, synchrony)
207
- * MRI 1.9.3 (drivers: ruby, hiredis, synchrony)
208
- * JRuby 1.6 (1.8 mode) (drivers: ruby)
209
- * JRuby 1.6 (1.9 mode) (drivers: ruby)
451
+ ## See Also
452
+
453
+ - [async-redis](https://github.com/socketry/async-redis) — An [async](https://github.com/socketry/async) compatible Redis client.
210
454
 
211
455
  ## Contributors
212
456
 
213
- (ordered chronologically with more than 5 commits, see `git shortlog -sn` for
214
- all contributors)
215
-
216
- * Ezra Zygmuntowicz
217
- * Taylor Weibley
218
- * Matthew Clark
219
- * Brian McKinney
220
- * Luca Guidi
221
- * Salvatore Sanfillipo
222
- * Chris Wanstrath
223
- * Damian Janowski
224
- * Michel Martens
225
- * Nick Quaranto
226
- * Pieter Noordhuis
227
- * Ilya Grigorik
457
+ Several people contributed to redis-rb, but we would like to especially
458
+ mention Ezra Zygmuntowicz. Ezra introduced the Ruby community to many
459
+ new cool technologies, like Redis. He wrote the first version of this
460
+ client and evangelized Redis in Rubyland. Thank you, Ezra.
228
461
 
229
462
  ## Contributing
230
463
 
231
464
  [Fork the project](https://github.com/redis/redis-rb) and send pull
232
- requests. You can also ask for help at `#redis-rb` on Freenode.
465
+ requests.
466
+
467
+
468
+ [inchpages-image]: https://inch-ci.org/github/redis/redis-rb.svg
469
+ [inchpages-link]: https://inch-ci.org/github/redis/redis-rb
470
+ [redis-commands]: https://redis.io/commands
471
+ [redis-home]: https://redis.io
472
+ [redis-url]: http://www.iana.org/assignments/uri-schemes/prov/redis
473
+ [gh-actions-image]: https://github.com/redis/redis-rb/workflows/Test/badge.svg
474
+ [gh-actions-link]: https://github.com/redis/redis-rb/actions
475
+ [rubydoc]: http://www.rubydoc.info/gems/redis