redis 3.3.5 → 4.0.3
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 +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
|