redis 3.3.5 → 4.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.travis/Gemfile +8 -1
- data/.travis.yml +34 -62
- data/CHANGELOG.md +45 -2
- data/Gemfile +5 -1
- data/README.md +32 -76
- data/benchmarking/logging.rb +1 -1
- data/bin/build +71 -0
- data/bors.toml +14 -0
- data/lib/redis/client.rb +38 -20
- data/lib/redis/cluster/command.rb +81 -0
- data/lib/redis/cluster/command_loader.rb +32 -0
- data/lib/redis/cluster/key_slot_converter.rb +72 -0
- data/lib/redis/cluster/node.rb +104 -0
- data/lib/redis/cluster/node_key.rb +35 -0
- data/lib/redis/cluster/node_loader.rb +35 -0
- data/lib/redis/cluster/option.rb +76 -0
- data/lib/redis/cluster/slot.rb +69 -0
- data/lib/redis/cluster/slot_loader.rb +47 -0
- data/lib/redis/cluster.rb +285 -0
- data/lib/redis/connection/command_helper.rb +2 -8
- data/lib/redis/connection/hiredis.rb +2 -2
- data/lib/redis/connection/ruby.rb +13 -30
- data/lib/redis/connection/synchrony.rb +12 -4
- data/lib/redis/connection.rb +2 -2
- data/lib/redis/distributed.rb +29 -8
- data/lib/redis/errors.rb +46 -0
- data/lib/redis/hash_ring.rb +20 -64
- data/lib/redis/pipeline.rb +9 -7
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +287 -52
- data/makefile +74 -0
- data/redis.gemspec +9 -10
- data/test/bitpos_test.rb +13 -19
- data/test/blocking_commands_test.rb +3 -5
- data/test/client_test.rb +18 -1
- data/test/cluster_abnormal_state_test.rb +38 -0
- data/test/cluster_blocking_commands_test.rb +15 -0
- data/test/cluster_client_internals_test.rb +77 -0
- data/test/cluster_client_key_hash_tags_test.rb +88 -0
- data/test/cluster_client_options_test.rb +147 -0
- data/test/cluster_client_pipelining_test.rb +59 -0
- data/test/cluster_client_replicas_test.rb +36 -0
- data/test/cluster_client_slots_test.rb +94 -0
- data/test/cluster_client_transactions_test.rb +71 -0
- data/test/cluster_commands_on_cluster_test.rb +165 -0
- data/test/cluster_commands_on_connection_test.rb +40 -0
- data/test/cluster_commands_on_geo_test.rb +74 -0
- data/test/cluster_commands_on_hashes_test.rb +11 -0
- data/test/cluster_commands_on_hyper_log_log_test.rb +17 -0
- data/test/cluster_commands_on_keys_test.rb +134 -0
- data/test/cluster_commands_on_lists_test.rb +15 -0
- data/test/cluster_commands_on_pub_sub_test.rb +101 -0
- data/test/cluster_commands_on_scripting_test.rb +56 -0
- data/test/cluster_commands_on_server_test.rb +221 -0
- data/test/cluster_commands_on_sets_test.rb +39 -0
- data/test/cluster_commands_on_sorted_sets_test.rb +35 -0
- data/test/cluster_commands_on_streams_test.rb +196 -0
- data/test/cluster_commands_on_strings_test.rb +15 -0
- data/test/cluster_commands_on_transactions_test.rb +41 -0
- data/test/cluster_commands_on_value_types_test.rb +14 -0
- data/test/command_map_test.rb +3 -5
- data/test/commands_on_geo_test.rb +116 -0
- data/test/commands_on_hashes_test.rb +2 -16
- data/test/commands_on_hyper_log_log_test.rb +3 -17
- data/test/commands_on_lists_test.rb +2 -15
- data/test/commands_on_sets_test.rb +2 -72
- data/test/commands_on_sorted_sets_test.rb +2 -132
- data/test/commands_on_strings_test.rb +2 -96
- data/test/commands_on_value_types_test.rb +80 -6
- data/test/connection_handling_test.rb +5 -7
- data/test/distributed_blocking_commands_test.rb +10 -4
- data/test/distributed_commands_on_hashes_test.rb +16 -5
- data/test/distributed_commands_on_hyper_log_log_test.rb +8 -15
- data/test/distributed_commands_on_lists_test.rb +4 -7
- data/test/distributed_commands_on_sets_test.rb +58 -36
- data/test/distributed_commands_on_sorted_sets_test.rb +51 -10
- data/test/distributed_commands_on_strings_test.rb +30 -10
- data/test/distributed_commands_on_value_types_test.rb +38 -4
- data/test/distributed_commands_requiring_clustering_test.rb +1 -3
- data/test/distributed_connection_handling_test.rb +1 -3
- data/test/distributed_internals_test.rb +8 -19
- data/test/distributed_key_tags_test.rb +4 -6
- data/test/distributed_persistence_control_commands_test.rb +1 -3
- data/test/distributed_publish_subscribe_test.rb +1 -3
- data/test/distributed_remote_server_control_commands_test.rb +1 -3
- data/test/distributed_scripting_test.rb +1 -3
- data/test/distributed_sorting_test.rb +1 -3
- data/test/distributed_test.rb +12 -14
- data/test/distributed_transactions_test.rb +1 -3
- data/test/encoding_test.rb +4 -8
- data/test/error_replies_test.rb +2 -4
- data/test/fork_safety_test.rb +1 -6
- data/test/helper.rb +179 -66
- data/test/helper_test.rb +1 -3
- data/test/internals_test.rb +47 -56
- data/test/lint/blocking_commands.rb +40 -16
- data/test/lint/hashes.rb +41 -0
- data/test/lint/hyper_log_log.rb +15 -1
- data/test/lint/lists.rb +16 -0
- data/test/lint/sets.rb +142 -0
- data/test/lint/sorted_sets.rb +183 -2
- data/test/lint/strings.rb +108 -20
- data/test/lint/value_types.rb +8 -0
- data/test/persistence_control_commands_test.rb +1 -3
- data/test/pipelining_commands_test.rb +12 -8
- data/test/publish_subscribe_test.rb +1 -3
- data/test/remote_server_control_commands_test.rb +60 -3
- data/test/scanning_test.rb +1 -7
- data/test/scripting_test.rb +1 -3
- data/test/sentinel_command_test.rb +1 -3
- data/test/sentinel_test.rb +1 -3
- data/test/sorting_test.rb +1 -3
- data/test/ssl_test.rb +45 -49
- data/test/support/cluster/orchestrator.rb +199 -0
- data/test/support/connection/hiredis.rb +1 -1
- data/test/support/connection/ruby.rb +1 -1
- data/test/support/connection/synchrony.rb +1 -1
- data/test/support/redis_mock.rb +1 -1
- data/test/synchrony_driver.rb +6 -9
- data/test/thread_safety_test.rb +1 -3
- data/test/transactions_test.rb +11 -3
- data/test/unknown_commands_test.rb +1 -3
- data/test/url_param_test.rb +44 -46
- metadata +109 -16
- data/Rakefile +0 -87
data/lib/redis/pipeline.rb
CHANGED
@@ -1,10 +1,4 @@
|
|
1
1
|
class Redis
|
2
|
-
unless defined?(::BasicObject)
|
3
|
-
class BasicObject
|
4
|
-
instance_methods.each { |meth| undef_method(meth) unless meth =~ /\A(__|instance_eval)/ }
|
5
|
-
end
|
6
|
-
end
|
7
|
-
|
8
2
|
class Pipeline
|
9
3
|
attr_accessor :db
|
10
4
|
|
@@ -28,6 +22,10 @@ class Redis
|
|
28
22
|
@shutdown
|
29
23
|
end
|
30
24
|
|
25
|
+
def empty?
|
26
|
+
@futures.empty?
|
27
|
+
end
|
28
|
+
|
31
29
|
def call(command, &block)
|
32
30
|
# A pipeline that contains a shutdown should not raise ECONNRESET when
|
33
31
|
# the connection is gone.
|
@@ -92,7 +90,11 @@ class Redis
|
|
92
90
|
end
|
93
91
|
|
94
92
|
def commands
|
95
|
-
|
93
|
+
if empty?
|
94
|
+
[]
|
95
|
+
else
|
96
|
+
[[:multi]] + super + [[:exec]]
|
97
|
+
end
|
96
98
|
end
|
97
99
|
end
|
98
100
|
end
|
data/lib/redis/version.rb
CHANGED
data/lib/redis.rb
CHANGED
@@ -1,21 +1,8 @@
|
|
1
1
|
require "monitor"
|
2
|
-
|
2
|
+
require_relative "redis/errors"
|
3
3
|
|
4
4
|
class Redis
|
5
5
|
|
6
|
-
def self.deprecate(message, trace = caller[0])
|
7
|
-
$stderr.puts "\n#{message} (in #{trace})"
|
8
|
-
end
|
9
|
-
|
10
|
-
attr :client
|
11
|
-
|
12
|
-
# @deprecated The preferred way to create a new client object is using `#new`.
|
13
|
-
# This method does not actually establish a connection to Redis,
|
14
|
-
# in contrary to what you might expect.
|
15
|
-
def self.connect(options = {})
|
16
|
-
new(options)
|
17
|
-
end
|
18
|
-
|
19
6
|
def self.current
|
20
7
|
@current ||= Redis.new
|
21
8
|
end
|
@@ -44,11 +31,16 @@ class Redis
|
|
44
31
|
# @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not
|
45
32
|
# @option options [Array] :sentinels List of sentinels to contact
|
46
33
|
# @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave`
|
34
|
+
# @option options [Array<String, Hash{Symbol => String, Integer}>] :cluster List of cluster nodes to contact
|
35
|
+
# @option options [Boolean] :replica Whether to use readonly replica nodes in Redis Cluster or not
|
36
|
+
# @option options [Class] :connector Class of custom connector
|
47
37
|
#
|
48
38
|
# @return [Redis] a new client instance
|
49
39
|
def initialize(options = {})
|
50
40
|
@options = options.dup
|
51
|
-
@
|
41
|
+
@cluster_mode = options.key?(:cluster)
|
42
|
+
client = @cluster_mode ? Cluster : Client
|
43
|
+
@original_client = @client = client.new(options)
|
52
44
|
@queue = Hash.new { |h, k| h[k] = [] }
|
53
45
|
|
54
46
|
super() # Monitor#initialize
|
@@ -119,6 +111,10 @@ class Redis
|
|
119
111
|
end
|
120
112
|
end
|
121
113
|
|
114
|
+
def _client
|
115
|
+
@client
|
116
|
+
end
|
117
|
+
|
122
118
|
# Authenticate to the server.
|
123
119
|
#
|
124
120
|
# @param [String] password must match the password specified in the
|
@@ -143,10 +139,11 @@ class Redis
|
|
143
139
|
|
144
140
|
# Ping the server.
|
145
141
|
#
|
142
|
+
# @param [optional, String] message
|
146
143
|
# @return [String] `PONG`
|
147
|
-
def ping
|
144
|
+
def ping(message = nil)
|
148
145
|
synchronize do |client|
|
149
|
-
client.call([:ping])
|
146
|
+
client.call([:ping, message].compact)
|
150
147
|
end
|
151
148
|
end
|
152
149
|
|
@@ -209,6 +206,25 @@ class Redis
|
|
209
206
|
end
|
210
207
|
end
|
211
208
|
|
209
|
+
# Manage client connections.
|
210
|
+
#
|
211
|
+
# @param [String, Symbol] subcommand e.g. `kill`, `list`, `getname`, `setname`
|
212
|
+
# @return [String, Hash] depends on subcommand
|
213
|
+
def client(subcommand = nil, *args)
|
214
|
+
synchronize do |client|
|
215
|
+
client.call([:client, subcommand] + args) do |reply|
|
216
|
+
if subcommand.to_s == "list"
|
217
|
+
reply.lines.map do |line|
|
218
|
+
entries = line.chomp.split(/[ =]/)
|
219
|
+
Hash[entries.each_slice(2).to_a]
|
220
|
+
end
|
221
|
+
else
|
222
|
+
reply
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
212
228
|
# Return the number of keys in the selected database.
|
213
229
|
#
|
214
230
|
# @return [Fixnum]
|
@@ -226,19 +242,31 @@ class Redis
|
|
226
242
|
|
227
243
|
# Remove all keys from all databases.
|
228
244
|
#
|
245
|
+
# @param [Hash] options
|
246
|
+
# - `:async => Boolean`: async flush (default: false)
|
229
247
|
# @return [String] `OK`
|
230
|
-
def flushall
|
248
|
+
def flushall(options = nil)
|
231
249
|
synchronize do |client|
|
232
|
-
|
250
|
+
if options && options[:async]
|
251
|
+
client.call([:flushall, :async])
|
252
|
+
else
|
253
|
+
client.call([:flushall])
|
254
|
+
end
|
233
255
|
end
|
234
256
|
end
|
235
257
|
|
236
258
|
# Remove all keys from the current database.
|
237
259
|
#
|
260
|
+
# @param [Hash] options
|
261
|
+
# - `:async => Boolean`: async flush (default: false)
|
238
262
|
# @return [String] `OK`
|
239
|
-
def flushdb
|
263
|
+
def flushdb(options = nil)
|
240
264
|
synchronize do |client|
|
241
|
-
|
265
|
+
if options && options[:async]
|
266
|
+
client.call([:flushdb, :async])
|
267
|
+
else
|
268
|
+
client.call([:flushdb])
|
269
|
+
end
|
242
270
|
end
|
243
271
|
end
|
244
272
|
|
@@ -250,9 +278,7 @@ class Redis
|
|
250
278
|
synchronize do |client|
|
251
279
|
client.call([:info, cmd].compact) do |reply|
|
252
280
|
if reply.kind_of?(String)
|
253
|
-
reply =
|
254
|
-
line.split(":", 2) unless line =~ /^(#|$)/
|
255
|
-
end.compact]
|
281
|
+
reply = HashifyInfo.call(reply)
|
256
282
|
|
257
283
|
if cmd && cmd.to_s == "commandstats"
|
258
284
|
# Extract nested hashes for INFO COMMANDSTATS
|
@@ -458,10 +484,16 @@ class Redis
|
|
458
484
|
# @param [String] key
|
459
485
|
# @param [String] ttl
|
460
486
|
# @param [String] serialized_value
|
487
|
+
# @param [Hash] options
|
488
|
+
# - `:replace => Boolean`: if false, raises an error if key already exists
|
489
|
+
# @raise [Redis::CommandError]
|
461
490
|
# @return [String] `"OK"`
|
462
|
-
def restore(key, ttl, serialized_value)
|
491
|
+
def restore(key, ttl, serialized_value, options = {})
|
492
|
+
args = [:restore, key, ttl, serialized_value]
|
493
|
+
args << 'REPLACE' if options[:replace]
|
494
|
+
|
463
495
|
synchronize do |client|
|
464
|
-
client.call(
|
496
|
+
client.call(args)
|
465
497
|
end
|
466
498
|
end
|
467
499
|
|
@@ -477,8 +509,8 @@ class Redis
|
|
477
509
|
def migrate(key, options)
|
478
510
|
host = options[:host] || raise(RuntimeError, ":host not specified")
|
479
511
|
port = options[:port] || raise(RuntimeError, ":port not specified")
|
480
|
-
db = (options[:db] || client.db).to_i
|
481
|
-
timeout = (options[:timeout] || client.timeout).to_i
|
512
|
+
db = (options[:db] || @client.db).to_i
|
513
|
+
timeout = (options[:timeout] || @client.timeout).to_i
|
482
514
|
|
483
515
|
synchronize do |client|
|
484
516
|
client.call([:migrate, host, port, key, db, timeout])
|
@@ -495,6 +527,16 @@ class Redis
|
|
495
527
|
end
|
496
528
|
end
|
497
529
|
|
530
|
+
# Unlink one or more keys.
|
531
|
+
#
|
532
|
+
# @param [String, Array<String>] keys
|
533
|
+
# @return [Fixnum] number of keys that were unlinked
|
534
|
+
def unlink(*keys)
|
535
|
+
synchronize do |client|
|
536
|
+
client.call([:unlink] + keys)
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
498
540
|
# Determine if a key exists.
|
499
541
|
#
|
500
542
|
# @param [String] key
|
@@ -756,8 +798,6 @@ class Redis
|
|
756
798
|
end
|
757
799
|
end
|
758
800
|
|
759
|
-
alias :[]= :set
|
760
|
-
|
761
801
|
# Set the time to live in seconds of a key.
|
762
802
|
#
|
763
803
|
# @param [String] key
|
@@ -863,8 +903,6 @@ class Redis
|
|
863
903
|
end
|
864
904
|
end
|
865
905
|
|
866
|
-
alias :[] :get
|
867
|
-
|
868
906
|
# Get the values of all the given keys.
|
869
907
|
#
|
870
908
|
# @example
|
@@ -1041,7 +1079,7 @@ class Redis
|
|
1041
1079
|
# Prepend one or more values to a list, creating the list if it doesn't exist
|
1042
1080
|
#
|
1043
1081
|
# @param [String] key
|
1044
|
-
# @param [String, Array] value string value, or array of string values to push
|
1082
|
+
# @param [String, Array<String>] value string value, or array of string values to push
|
1045
1083
|
# @return [Fixnum] the length of the list after the push operation
|
1046
1084
|
def lpush(key, value)
|
1047
1085
|
synchronize do |client|
|
@@ -1063,7 +1101,7 @@ class Redis
|
|
1063
1101
|
# Append one or more values to a list, creating the list if it doesn't exist
|
1064
1102
|
#
|
1065
1103
|
# @param [String] key
|
1066
|
-
# @param [String] value
|
1104
|
+
# @param [String, Array<String>] value string value, or array of string values to push
|
1067
1105
|
# @return [Fixnum] the length of the list after the push operation
|
1068
1106
|
def rpush(key, value)
|
1069
1107
|
synchronize do |client|
|
@@ -1701,6 +1739,30 @@ class Redis
|
|
1701
1739
|
end
|
1702
1740
|
end
|
1703
1741
|
|
1742
|
+
# Count the members, with the same score in a sorted set, within the given lexicographical range.
|
1743
|
+
#
|
1744
|
+
# @example Count members matching a
|
1745
|
+
# redis.zlexcount("zset", "[a", "[a\xff")
|
1746
|
+
# # => 1
|
1747
|
+
# @example Count members matching a-z
|
1748
|
+
# redis.zlexcount("zset", "[a", "[z\xff")
|
1749
|
+
# # => 26
|
1750
|
+
#
|
1751
|
+
# @param [String] key
|
1752
|
+
# @param [String] min
|
1753
|
+
# - inclusive minimum is specified by prefixing `(`
|
1754
|
+
# - exclusive minimum is specified by prefixing `[`
|
1755
|
+
# @param [String] max
|
1756
|
+
# - inclusive maximum is specified by prefixing `(`
|
1757
|
+
# - exclusive maximum is specified by prefixing `[`
|
1758
|
+
#
|
1759
|
+
# @return [Fixnum] number of members within the specified lexicographical range
|
1760
|
+
def zlexcount(key, min, max)
|
1761
|
+
synchronize do |client|
|
1762
|
+
client.call([:zlexcount, key, min, max])
|
1763
|
+
end
|
1764
|
+
end
|
1765
|
+
|
1704
1766
|
# Return a range of members with the same score in a sorted set, by lexicographical ordering
|
1705
1767
|
#
|
1706
1768
|
# @example Retrieve members matching a
|
@@ -2053,9 +2115,9 @@ class Redis
|
|
2053
2115
|
# @param [String] key
|
2054
2116
|
# @param [String, Array<String>] field
|
2055
2117
|
# @return [Fixnum] the number of fields that were removed from the hash
|
2056
|
-
def hdel(key,
|
2118
|
+
def hdel(key, *fields)
|
2057
2119
|
synchronize do |client|
|
2058
|
-
client.call([:hdel, key,
|
2120
|
+
client.call([:hdel, key, *fields])
|
2059
2121
|
end
|
2060
2122
|
end
|
2061
2123
|
|
@@ -2661,6 +2723,86 @@ class Redis
|
|
2661
2723
|
end
|
2662
2724
|
end
|
2663
2725
|
|
2726
|
+
# Adds the specified geospatial items (latitude, longitude, name) to the specified key
|
2727
|
+
#
|
2728
|
+
# @param [String] key
|
2729
|
+
# @param [Array] member arguemnts for member or members: longitude, latitude, name
|
2730
|
+
# @return [Intger] number of elements added to the sorted set
|
2731
|
+
def geoadd(key, *member)
|
2732
|
+
synchronize do |client|
|
2733
|
+
client.call([:geoadd, key, member])
|
2734
|
+
end
|
2735
|
+
end
|
2736
|
+
|
2737
|
+
# Returns geohash string representing position for specified members of the specified key.
|
2738
|
+
#
|
2739
|
+
# @param [String] key
|
2740
|
+
# @param [String, Array<String>] member one member or array of members
|
2741
|
+
# @return [Array<String, nil>] returns array containg geohash string if member is present, nil otherwise
|
2742
|
+
def geohash(key, member)
|
2743
|
+
synchronize do |client|
|
2744
|
+
client.call([:geohash, key, member])
|
2745
|
+
end
|
2746
|
+
end
|
2747
|
+
|
2748
|
+
|
2749
|
+
# Query a sorted set representing a geospatial index to fetch members matching a
|
2750
|
+
# given maximum distance from a point
|
2751
|
+
#
|
2752
|
+
# @param [Array] args key, longitude, latitude, radius, unit(m|km|ft|mi)
|
2753
|
+
# @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest to the nearest relative to the center
|
2754
|
+
# @param [Integer] count limit the results to the first N matching items
|
2755
|
+
# @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
|
2756
|
+
# @return [Array<String>] may be changed with `options`
|
2757
|
+
|
2758
|
+
def georadius(*args, **geoptions)
|
2759
|
+
geoarguments = _geoarguments(*args, **geoptions)
|
2760
|
+
|
2761
|
+
synchronize do |client|
|
2762
|
+
client.call([:georadius, *geoarguments])
|
2763
|
+
end
|
2764
|
+
end
|
2765
|
+
|
2766
|
+
# Query a sorted set representing a geospatial index to fetch members matching a
|
2767
|
+
# given maximum distance from an already existing member
|
2768
|
+
#
|
2769
|
+
# @param [Array] args key, member, radius, unit(m|km|ft|mi)
|
2770
|
+
# @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest to the nearest relative to the center
|
2771
|
+
# @param [Integer] count limit the results to the first N matching items
|
2772
|
+
# @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
|
2773
|
+
# @return [Array<String>] may be changed with `options`
|
2774
|
+
|
2775
|
+
def georadiusbymember(*args, **geoptions)
|
2776
|
+
geoarguments = _geoarguments(*args, **geoptions)
|
2777
|
+
|
2778
|
+
synchronize do |client|
|
2779
|
+
client.call([:georadiusbymember, *geoarguments])
|
2780
|
+
end
|
2781
|
+
end
|
2782
|
+
|
2783
|
+
# Returns longitude and latitude of members of a geospatial index
|
2784
|
+
#
|
2785
|
+
# @param [String] key
|
2786
|
+
# @param [String, Array<String>] member one member or array of members
|
2787
|
+
# @return [Array<Array<String>, nil>] returns array of elements, where each element is either array of longitude and latitude or nil
|
2788
|
+
def geopos(key, member)
|
2789
|
+
synchronize do |client|
|
2790
|
+
client.call([:geopos, key, member])
|
2791
|
+
end
|
2792
|
+
end
|
2793
|
+
|
2794
|
+
# Returns the distance between two members of a geospatial index
|
2795
|
+
#
|
2796
|
+
# @param [String ]key
|
2797
|
+
# @param [Array<String>] members
|
2798
|
+
# @param ['m', 'km', 'mi', 'ft'] unit
|
2799
|
+
# @return [String, nil] returns distance in spefied unit if both members present, nil otherwise.
|
2800
|
+
def geodist(key, member1, member2, unit = 'm')
|
2801
|
+
synchronize do |client|
|
2802
|
+
client.call([:geodist, key, member1, member2, unit])
|
2803
|
+
end
|
2804
|
+
end
|
2805
|
+
|
2664
2806
|
# Interact with the sentinel command (masters, master, slaves, failover)
|
2665
2807
|
#
|
2666
2808
|
# @param [String] subcommand e.g. `masters`, `master`, `slaves`
|
@@ -2688,6 +2830,41 @@ class Redis
|
|
2688
2830
|
end
|
2689
2831
|
end
|
2690
2832
|
|
2833
|
+
# Sends `CLUSTER *` command to random node and returns its reply.
|
2834
|
+
#
|
2835
|
+
# @see https://redis.io/commands#cluster Reference of cluster command
|
2836
|
+
#
|
2837
|
+
# @param subcommand [String, Symbol] the subcommand of cluster command
|
2838
|
+
# e.g. `:slots`, `:nodes`, `:slaves`, `:info`
|
2839
|
+
#
|
2840
|
+
# @return [Object] depends on the subcommand
|
2841
|
+
def cluster(subcommand, *args)
|
2842
|
+
subcommand = subcommand.to_s.downcase
|
2843
|
+
block = case subcommand
|
2844
|
+
when 'slots' then HashifyClusterSlots
|
2845
|
+
when 'nodes' then HashifyClusterNodes
|
2846
|
+
when 'slaves' then HashifyClusterSlaves
|
2847
|
+
when 'info' then HashifyInfo
|
2848
|
+
else Noop
|
2849
|
+
end
|
2850
|
+
|
2851
|
+
# @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
|
2852
|
+
block = Noop unless @cluster_mode
|
2853
|
+
|
2854
|
+
synchronize do |client|
|
2855
|
+
client.call([:cluster, subcommand] + args, &block)
|
2856
|
+
end
|
2857
|
+
end
|
2858
|
+
|
2859
|
+
# Sends `ASKING` command to random node and returns its reply.
|
2860
|
+
#
|
2861
|
+
# @see https://redis.io/topics/cluster-spec#ask-redirection ASK redirection
|
2862
|
+
#
|
2863
|
+
# @return [String] `'OK'`
|
2864
|
+
def asking
|
2865
|
+
synchronize { |client| client.call(%i[asking]) }
|
2866
|
+
end
|
2867
|
+
|
2691
2868
|
def id
|
2692
2869
|
@original_client.id
|
2693
2870
|
end
|
@@ -2701,12 +2878,14 @@ class Redis
|
|
2701
2878
|
end
|
2702
2879
|
|
2703
2880
|
def connection
|
2881
|
+
return @original_client.connection_info if @cluster_mode
|
2882
|
+
|
2704
2883
|
{
|
2705
|
-
:
|
2706
|
-
:
|
2707
|
-
:
|
2708
|
-
:
|
2709
|
-
:
|
2884
|
+
host: @original_client.host,
|
2885
|
+
port: @original_client.port,
|
2886
|
+
db: @original_client.db,
|
2887
|
+
id: @original_client.id,
|
2888
|
+
location: @original_client.location
|
2710
2889
|
}
|
2711
2890
|
end
|
2712
2891
|
|
@@ -2756,14 +2935,70 @@ private
|
|
2756
2935
|
}
|
2757
2936
|
|
2758
2937
|
FloatifyPairs =
|
2759
|
-
lambda { |
|
2760
|
-
|
2761
|
-
|
2762
|
-
|
2763
|
-
|
2938
|
+
lambda { |result|
|
2939
|
+
result.each_slice(2).map do |member, score|
|
2940
|
+
[member, Floatify.call(score)]
|
2941
|
+
end
|
2942
|
+
}
|
2943
|
+
|
2944
|
+
HashifyInfo =
|
2945
|
+
lambda { |reply|
|
2946
|
+
Hash[reply.split("\r\n").map do |line|
|
2947
|
+
line.split(':', 2) unless line =~ /^(#|$)/
|
2948
|
+
end.compact]
|
2949
|
+
}
|
2950
|
+
|
2951
|
+
HashifyClusterNodeInfo =
|
2952
|
+
lambda { |str|
|
2953
|
+
arr = str.split(' ')
|
2954
|
+
{
|
2955
|
+
'node_id' => arr[0],
|
2956
|
+
'ip_port' => arr[1],
|
2957
|
+
'flags' => arr[2].split(','),
|
2958
|
+
'master_node_id' => arr[3],
|
2959
|
+
'ping_sent' => arr[4],
|
2960
|
+
'pong_recv' => arr[5],
|
2961
|
+
'config_epoch' => arr[6],
|
2962
|
+
'link_state' => arr[7],
|
2963
|
+
'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
|
2964
|
+
}
|
2965
|
+
}
|
2966
|
+
|
2967
|
+
HashifyClusterSlots =
|
2968
|
+
lambda { |reply|
|
2969
|
+
reply.map do |arr|
|
2970
|
+
first_slot, last_slot = arr[0..1]
|
2971
|
+
master = { 'ip' => arr[2][0], 'port' => arr[2][1], 'node_id' => arr[2][2] }
|
2972
|
+
replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } }
|
2973
|
+
{
|
2974
|
+
'start_slot' => first_slot,
|
2975
|
+
'end_slot' => last_slot,
|
2976
|
+
'master' => master,
|
2977
|
+
'replicas' => replicas
|
2978
|
+
}
|
2764
2979
|
end
|
2765
2980
|
}
|
2766
2981
|
|
2982
|
+
HashifyClusterNodes =
|
2983
|
+
lambda { |reply|
|
2984
|
+
reply.split(/[\r\n]+/).map { |str| HashifyClusterNodeInfo.call(str) }
|
2985
|
+
}
|
2986
|
+
|
2987
|
+
HashifyClusterSlaves =
|
2988
|
+
lambda { |reply|
|
2989
|
+
reply.map { |str| HashifyClusterNodeInfo.call(str) }
|
2990
|
+
}
|
2991
|
+
|
2992
|
+
Noop = ->(reply) { reply }
|
2993
|
+
|
2994
|
+
def _geoarguments(*args, options: nil, sort: nil, count: nil)
|
2995
|
+
args.push sort if sort
|
2996
|
+
args.push 'count', count if count
|
2997
|
+
args.push options if options
|
2998
|
+
|
2999
|
+
args.uniq
|
3000
|
+
end
|
3001
|
+
|
2767
3002
|
def _subscription(method, timeout, channels, block)
|
2768
3003
|
return @client.call([method] + channels) if subscribed?
|
2769
3004
|
|
@@ -2778,11 +3013,11 @@ private
|
|
2778
3013
|
@client = original
|
2779
3014
|
end
|
2780
3015
|
end
|
2781
|
-
|
2782
3016
|
end
|
2783
3017
|
|
2784
|
-
|
2785
|
-
|
2786
|
-
|
2787
|
-
|
2788
|
-
|
3018
|
+
require_relative "redis/version"
|
3019
|
+
require_relative "redis/connection"
|
3020
|
+
require_relative "redis/client"
|
3021
|
+
require_relative "redis/cluster"
|
3022
|
+
require_relative "redis/pipeline"
|
3023
|
+
require_relative "redis/subscribe"
|
data/makefile
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
TEST_FILES := $(shell find ./test -name *_test.rb -type f)
|
2
|
+
REDIS_BRANCH ?= unstable
|
3
|
+
TMP := tmp
|
4
|
+
BUILD_DIR := ${TMP}/cache/redis-${REDIS_BRANCH}
|
5
|
+
TARBALL := ${TMP}/redis-${REDIS_BRANCH}.tar.gz
|
6
|
+
BINARY := ${BUILD_DIR}/src/redis-server
|
7
|
+
REDIS_CLIENT := ${BUILD_DIR}/src/redis-cli
|
8
|
+
REDIS_TRIB := ${BUILD_DIR}/src/redis-trib.rb
|
9
|
+
PID_PATH := ${BUILD_DIR}/redis.pid
|
10
|
+
SOCKET_PATH := ${BUILD_DIR}/redis.sock
|
11
|
+
PORT := 6381
|
12
|
+
CLUSTER_PORTS := 7000 7001 7002 7003 7004 7005
|
13
|
+
CLUSTER_PID_PATHS := $(addprefix ${TMP}/redis,$(addsuffix .pid,${CLUSTER_PORTS}))
|
14
|
+
CLUSTER_CONF_PATHS := $(addprefix ${TMP}/nodes,$(addsuffix .conf,${CLUSTER_PORTS}))
|
15
|
+
CLUSTER_ADDRS := $(addprefix 127.0.0.1:,${CLUSTER_PORTS})
|
16
|
+
|
17
|
+
define kill-redis
|
18
|
+
(ls $1 2> /dev/null && kill $$(cat $1) && rm -f $1) || true
|
19
|
+
endef
|
20
|
+
|
21
|
+
all:
|
22
|
+
make start
|
23
|
+
make start_cluster
|
24
|
+
make create_cluster
|
25
|
+
make test
|
26
|
+
make stop
|
27
|
+
make stop_cluster
|
28
|
+
|
29
|
+
${TMP}:
|
30
|
+
mkdir -p $@
|
31
|
+
|
32
|
+
${BINARY}: ${TMP}
|
33
|
+
bin/build ${REDIS_BRANCH} $<
|
34
|
+
|
35
|
+
test: ${TEST_FILES}
|
36
|
+
env SOCKET_PATH=${SOCKET_PATH} \
|
37
|
+
bundle exec ruby -v -e 'ARGV.each { |test_file| require test_file }' ${TEST_FILES}
|
38
|
+
|
39
|
+
stop:
|
40
|
+
$(call kill-redis,${PID_PATH})
|
41
|
+
|
42
|
+
start: ${BINARY}
|
43
|
+
${BINARY} \
|
44
|
+
--daemonize yes \
|
45
|
+
--pidfile ${PID_PATH} \
|
46
|
+
--port ${PORT} \
|
47
|
+
--unixsocket ${SOCKET_PATH}
|
48
|
+
|
49
|
+
stop_cluster:
|
50
|
+
$(call kill-redis,${CLUSTER_PID_PATHS})
|
51
|
+
rm -f appendonly.aof || true
|
52
|
+
rm -f ${CLUSTER_CONF_PATHS} || true
|
53
|
+
|
54
|
+
start_cluster: ${BINARY}
|
55
|
+
for port in ${CLUSTER_PORTS}; do \
|
56
|
+
${BINARY} \
|
57
|
+
--daemonize yes \
|
58
|
+
--appendonly yes \
|
59
|
+
--cluster-enabled yes \
|
60
|
+
--cluster-config-file ${TMP}/nodes$$port.conf \
|
61
|
+
--cluster-node-timeout 5000 \
|
62
|
+
--pidfile ${TMP}/redis$$port.pid \
|
63
|
+
--port $$port \
|
64
|
+
--unixsocket ${TMP}/redis$$port.sock; \
|
65
|
+
done
|
66
|
+
|
67
|
+
create_cluster:
|
68
|
+
yes yes | ((bundle exec ruby ${REDIS_TRIB} create --replicas 1 ${CLUSTER_ADDRS}) || \
|
69
|
+
(${REDIS_CLIENT} --cluster create ${CLUSTER_ADDRS} --cluster-replicas 1))
|
70
|
+
|
71
|
+
clean:
|
72
|
+
(test -d ${BUILD_DIR} && cd ${BUILD_DIR}/src && make clean distclean) || true
|
73
|
+
|
74
|
+
.PHONY: all test stop start stop_cluster start_cluster create_cluster clean
|
data/redis.gemspec
CHANGED
@@ -1,8 +1,4 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
$:.unshift File.expand_path("../lib", __FILE__)
|
4
|
-
|
5
|
-
require "redis/version"
|
1
|
+
require "./lib/redis/version"
|
6
2
|
|
7
3
|
Gem::Specification.new do |s|
|
8
4
|
s.name = "redis"
|
@@ -15,8 +11,7 @@ Gem::Specification.new do |s|
|
|
15
11
|
|
16
12
|
s.description = <<-EOS
|
17
13
|
A Ruby client that tries to match Redis' API one-to-one, while still
|
18
|
-
providing an idiomatic interface.
|
19
|
-
client-side sharding, pipelining, and an obsession for performance.
|
14
|
+
providing an idiomatic interface.
|
20
15
|
EOS
|
21
16
|
|
22
17
|
s.license = "MIT"
|
@@ -37,8 +32,12 @@ Gem::Specification.new do |s|
|
|
37
32
|
|
38
33
|
s.files = `git ls-files`.split("\n")
|
39
34
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
40
|
-
s.executables = `git ls-files --
|
35
|
+
s.executables = `git ls-files -- exe/*`.split("\n").map{ |f| File.basename(f) }
|
36
|
+
|
37
|
+
s.required_ruby_version = '>= 2.2.2'
|
41
38
|
|
42
|
-
s.add_development_dependency("
|
43
|
-
s.add_development_dependency("
|
39
|
+
s.add_development_dependency("test-unit", ">= 3.1.5")
|
40
|
+
s.add_development_dependency("mocha")
|
41
|
+
s.add_development_dependency("hiredis")
|
42
|
+
s.add_development_dependency("em-synchrony")
|
44
43
|
end
|