redis 3.1.0 → 3.2.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.
- checksums.yaml +5 -13
- data/.gitignore +2 -0
- data/.travis.yml +15 -17
- data/CHANGELOG.md +9 -1
- data/README.md +29 -1
- data/examples/consistency.rb +114 -0
- data/examples/sentinel.rb +41 -0
- data/examples/sentinel/start +49 -0
- data/lib/redis.rb +38 -3
- data/lib/redis/client.rb +109 -2
- data/lib/redis/distributed.rb +4 -2
- data/lib/redis/version.rb +1 -1
- data/test/commands_on_sorted_sets_test.rb +14 -0
- data/test/distributed_commands_on_hyper_log_log_test.rb +12 -1
- data/test/helper.rb +2 -0
- data/test/internals_test.rb +10 -2
- data/test/lint/hyper_log_log.rb +18 -6
- data/test/publish_subscribe_test.rb +1 -1
- metadata +18 -15
- data/.order +0 -170
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
MTAxMTMzZmVjY2E2YjI1NzQ3Y2M1YTI5NDk3ZWQ5ZjVmZmExYTMwYg==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e25c90cb532eea0215afe63b0156fa102446c5f2
|
4
|
+
data.tar.gz: b2acddebb3e293807a9b6bb6a6f986432859afb0
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
NzljZTdhZTg1ZjEyYTYxYTJjYmJkYmIwZjYzZTdkNmFlOTNmNDZlY2YzMTY1
|
11
|
-
MTgyZmRmZjNlNTEwNzZlYWJmM2Q3YTkxZTEyZDc1MjZhNWNlODc=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
NDQ0NDIyZmVmMWVmMmE5ZGU4ZDQyYzAxODljYTEzODM2YjY5YzAyMWZjNzdh
|
14
|
-
NTEwMjcwZDM2OWRhYWY4ZjUzMTRmOWU5MTNjMDM3ZDM2YjM0YTRhMDc3NGY0
|
15
|
-
MjQ5ZTFhYjg5ODM5NDlmZjMyMDkxMTg1NmI3YjgyZWQwNmQzN2Y=
|
6
|
+
metadata.gz: f599db91c12cf1a0e6a86c114e6ea687f9027d1a58e982f4f68bded6e38409cf4aab46fb742ec2d9fd80e85214a41a7f341399348645bb8765099c8f059b3efe
|
7
|
+
data.tar.gz: 12fb14500e54c7eeb46a79a9b112eb5b85a457c50ae388f505db18b9a2ab826d3c206bd4d5bc778b487a94f7af48d8b5df79f1f01396087c2b856ea2e0b589d8
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,52 +1,50 @@
|
|
1
1
|
language: ruby
|
2
2
|
|
3
|
-
branches:
|
4
|
-
only:
|
5
|
-
- master
|
6
|
-
|
7
3
|
rvm:
|
8
4
|
- 1.8.7
|
9
|
-
- 1.9.2
|
10
5
|
- 1.9.3
|
11
|
-
- 2.
|
12
|
-
- 2.1.0
|
6
|
+
- 2.1
|
13
7
|
- jruby-18mode
|
14
8
|
- jruby-19mode
|
15
9
|
|
16
|
-
gemfile:
|
17
|
-
|
10
|
+
gemfile: ".travis/Gemfile"
|
11
|
+
|
12
|
+
sudo: false
|
18
13
|
|
19
14
|
env:
|
20
15
|
global:
|
21
16
|
- VERBOSE=true
|
22
17
|
- TIMEOUT=1
|
23
18
|
matrix:
|
24
|
-
- conn=ruby REDIS_BRANCH=2.6
|
25
|
-
- conn=hiredis REDIS_BRANCH=2.6
|
26
|
-
- conn=synchrony REDIS_BRANCH=2.6
|
27
19
|
- conn=ruby REDIS_BRANCH=2.8
|
20
|
+
- conn=hiredis REDIS_BRANCH=2.8
|
21
|
+
- conn=synchrony REDIS_BRANCH=2.8
|
28
22
|
- conn=ruby REDIS_BRANCH=unstable
|
29
23
|
|
24
|
+
branches:
|
25
|
+
only:
|
26
|
+
- master
|
27
|
+
|
30
28
|
matrix:
|
31
29
|
exclude:
|
32
30
|
# hiredis
|
33
31
|
- rvm: jruby-18mode
|
34
32
|
gemfile: .travis/Gemfile
|
35
|
-
env: conn=hiredis REDIS_BRANCH=2.
|
33
|
+
env: conn=hiredis REDIS_BRANCH=2.8
|
36
34
|
- rvm: jruby-19mode
|
37
35
|
gemfile: .travis/Gemfile
|
38
|
-
env: conn=hiredis REDIS_BRANCH=2.
|
36
|
+
env: conn=hiredis REDIS_BRANCH=2.8
|
39
37
|
|
40
38
|
# synchrony
|
41
39
|
- rvm: 1.8.7
|
42
40
|
gemfile: .travis/Gemfile
|
43
|
-
env: conn=synchrony REDIS_BRANCH=2.
|
41
|
+
env: conn=synchrony REDIS_BRANCH=2.8
|
44
42
|
- rvm: jruby-18mode
|
45
43
|
gemfile: .travis/Gemfile
|
46
|
-
env: conn=synchrony REDIS_BRANCH=2.
|
44
|
+
env: conn=synchrony REDIS_BRANCH=2.8
|
47
45
|
- rvm: jruby-19mode
|
48
46
|
gemfile: .travis/Gemfile
|
49
|
-
env: conn=synchrony REDIS_BRANCH=2.
|
47
|
+
env: conn=synchrony REDIS_BRANCH=2.8
|
50
48
|
|
51
49
|
notifications:
|
52
50
|
irc:
|
data/CHANGELOG.md
CHANGED
@@ -12,7 +12,15 @@
|
|
12
12
|
security updates in June of 2013; continuing to support it would prevent
|
13
13
|
the use of newer features of Ruby.
|
14
14
|
|
15
|
-
# 3.
|
15
|
+
# 3.2.0
|
16
|
+
|
17
|
+
* Redis Sentinel support.
|
18
|
+
|
19
|
+
# 3.1.1 (unreleased)
|
20
|
+
|
21
|
+
* Added support for variadic `PFCOUNT`.
|
22
|
+
|
23
|
+
# 3.1.0
|
16
24
|
|
17
25
|
* Added debug log sanitization (#428).
|
18
26
|
|
data/README.md
CHANGED
@@ -85,6 +85,34 @@ available on [rdoc.info][rdoc].
|
|
85
85
|
|
86
86
|
[rdoc]: http://rdoc.info/github/redis/redis-rb/
|
87
87
|
|
88
|
+
## Sentinel support
|
89
|
+
|
90
|
+
The client is able to perform automatic failovers by using [Redis
|
91
|
+
Sentinel](http://redis.io/topics/sentinel). Make sure to run Redis 2.8+
|
92
|
+
if you want to use this feature.
|
93
|
+
|
94
|
+
To connect using Sentinel, use:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
SENTINELS = [{:host => "127.0.0.1", :port => 26380},
|
98
|
+
{:host => "127.0.0.1", :port => 26381}]
|
99
|
+
|
100
|
+
redis = Redis.new(:url => "redis://mymaster", :sentinels => SENTINELS, :role => :master)
|
101
|
+
```
|
102
|
+
|
103
|
+
* The master name identifies a group of Redis instances composed of a master
|
104
|
+
and one or more slaves (`mymaster` in the example).
|
105
|
+
|
106
|
+
* It is possible to optionally provide a role. The allowed roles are `master`
|
107
|
+
and `slave`. When the role is `slave`, the client will try to connect to a
|
108
|
+
random slave of the specified master.
|
109
|
+
|
110
|
+
* When using the Sentinel support you need to specify a list of sentinels to
|
111
|
+
connect to. The list does not need to enumerate all your Sentinel instances,
|
112
|
+
but a few so that if one is down the client will try the next one. The client
|
113
|
+
is able to remember the last Sentinel that was able to reply correctly and will
|
114
|
+
use it for the next requests.
|
115
|
+
|
88
116
|
## Storing objects
|
89
117
|
|
90
118
|
Redis only stores strings as values. If you want to store an object, you
|
@@ -166,7 +194,7 @@ end
|
|
166
194
|
to redis, AND
|
167
195
|
- your own code prevents the parent process from using the redis
|
168
196
|
connection while a child is alive
|
169
|
-
|
197
|
+
|
170
198
|
Improper use of `inherit_socket` will result in corrupted and/or incorrect
|
171
199
|
responses.
|
172
200
|
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# This file implements a simple consistency test for Redis-rb (or any other
|
2
|
+
# Redis environment if you pass a different client object) where a client
|
3
|
+
# writes to the database using INCR in order to increment keys, but actively
|
4
|
+
# remember the value the key should have. Before every write a read is performed
|
5
|
+
# to check if the value in the database matches the value expected.
|
6
|
+
#
|
7
|
+
# In this way this program can check for lost writes, or acknowledged writes
|
8
|
+
# that were executed.
|
9
|
+
#
|
10
|
+
# Copyright (C) 2013-2014 Salvatore Sanfilippo <antirez@gmail.com>
|
11
|
+
#
|
12
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
13
|
+
# a copy of this software and associated documentation files (the
|
14
|
+
# "Software"), to deal in the Software without restriction, including
|
15
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
16
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
17
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
18
|
+
# the following conditions:
|
19
|
+
#
|
20
|
+
# The above copyright notice and this permission notice shall be
|
21
|
+
# included in all copies or substantial portions of the Software.
|
22
|
+
#
|
23
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
24
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
25
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
26
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
27
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
28
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
29
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
30
|
+
|
31
|
+
require 'redis'
|
32
|
+
|
33
|
+
class ConsistencyTester
|
34
|
+
def initialize(redis)
|
35
|
+
@r = redis
|
36
|
+
@working_set = 10000
|
37
|
+
@keyspace = 100000
|
38
|
+
@writes = 0
|
39
|
+
@reads = 0
|
40
|
+
@failed_writes = 0
|
41
|
+
@failed_reads = 0
|
42
|
+
@lost_writes = 0
|
43
|
+
@not_ack_writes = 0
|
44
|
+
@delay = 0
|
45
|
+
@cached = {} # We take our view of data stored in the DB.
|
46
|
+
@prefix = [Process.pid.to_s,Time.now.usec,@r.object_id,""].join("|")
|
47
|
+
@errtime = {}
|
48
|
+
end
|
49
|
+
|
50
|
+
def genkey
|
51
|
+
# Write more often to a small subset of keys
|
52
|
+
ks = rand() > 0.5 ? @keyspace : @working_set
|
53
|
+
@prefix+"key_"+rand(ks).to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
def check_consistency(key,value)
|
57
|
+
expected = @cached[key]
|
58
|
+
return if !expected # We lack info about previous state.
|
59
|
+
if expected > value
|
60
|
+
@lost_writes += expected-value
|
61
|
+
elsif expected < value
|
62
|
+
@not_ack_writes += value-expected
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def puterr(msg)
|
67
|
+
if !@errtime[msg] || Time.now.to_i != @errtime[msg]
|
68
|
+
puts msg
|
69
|
+
end
|
70
|
+
@errtime[msg] = Time.now.to_i
|
71
|
+
end
|
72
|
+
|
73
|
+
def test
|
74
|
+
last_report = Time.now.to_i
|
75
|
+
while true
|
76
|
+
# Read
|
77
|
+
key = genkey
|
78
|
+
begin
|
79
|
+
val = @r.get(key)
|
80
|
+
check_consistency(key,val.to_i)
|
81
|
+
@reads += 1
|
82
|
+
rescue => e
|
83
|
+
puterr "Reading: #{e.class}: #{e.message} (#{e.backtrace.first})"
|
84
|
+
@failed_reads += 1
|
85
|
+
end
|
86
|
+
|
87
|
+
# Write
|
88
|
+
begin
|
89
|
+
@cached[key] = @r.incr(key).to_i
|
90
|
+
@writes += 1
|
91
|
+
rescue => e
|
92
|
+
puterr "Writing: #{e.class}: #{e.message} (#{e.backtrace.first})"
|
93
|
+
@failed_writes += 1
|
94
|
+
end
|
95
|
+
|
96
|
+
# Report
|
97
|
+
sleep @delay
|
98
|
+
if Time.now.to_i != last_report
|
99
|
+
report = "#{@reads} R (#{@failed_reads} err) | " +
|
100
|
+
"#{@writes} W (#{@failed_writes} err) | "
|
101
|
+
report += "#{@lost_writes} lost | " if @lost_writes > 0
|
102
|
+
report += "#{@not_ack_writes} noack | " if @not_ack_writes > 0
|
103
|
+
last_report = Time.now.to_i
|
104
|
+
puts report
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
Sentinels = [{:host => "127.0.0.1", :port => 26379},
|
111
|
+
{:host => "127.0.0.1", :port => 26380}]
|
112
|
+
r = Redis.new(:url => "redis://master1", :sentinels => Sentinels, :role => :master)
|
113
|
+
tester = ConsistencyTester.new(r)
|
114
|
+
tester.test
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'redis'
|
2
|
+
|
3
|
+
# This example creates a master-slave setup with a sentinel, then connects to
|
4
|
+
# it and sends write commands in a loop.
|
5
|
+
#
|
6
|
+
# After 30 seconds, the master dies. You will be able to see how a new master
|
7
|
+
# is elected and things continue to work as if nothing happened.
|
8
|
+
#
|
9
|
+
# To run this example:
|
10
|
+
#
|
11
|
+
# $ ruby -I./lib examples/sentinel.rb
|
12
|
+
#
|
13
|
+
|
14
|
+
at_exit do
|
15
|
+
begin
|
16
|
+
Process.kill(:INT, $redises)
|
17
|
+
rescue Errno::ESRCH
|
18
|
+
end
|
19
|
+
|
20
|
+
Process.waitall
|
21
|
+
end
|
22
|
+
|
23
|
+
$redises = spawn("examples/sentinel/start")
|
24
|
+
|
25
|
+
Sentinels = [{:host => "127.0.0.1", :port => 26379},
|
26
|
+
{:host => "127.0.0.1", :port => 26380}]
|
27
|
+
r = Redis.new(:url => "redis://master1", :sentinels => Sentinels, :role => :master)
|
28
|
+
|
29
|
+
# Set keys into a loop.
|
30
|
+
#
|
31
|
+
# The example traps errors so that you can actually try to failover while
|
32
|
+
# running the script to see redis-rb reconfiguring.
|
33
|
+
(0..1000000).each{|i|
|
34
|
+
begin
|
35
|
+
r.set(i,i)
|
36
|
+
$stdout.write("SET (#{i} times)\n") if i % 100 == 0
|
37
|
+
rescue => e
|
38
|
+
$stdout.write("E")
|
39
|
+
end
|
40
|
+
sleep(0.01)
|
41
|
+
}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
# This is a helper script used together with examples/sentinel.rb
|
4
|
+
# It runs two Redis masters, two slaves for each of them, and two sentinels.
|
5
|
+
# After 30 seconds, the first master dies.
|
6
|
+
#
|
7
|
+
# You don't need to run this script yourself. Rather, use examples/sentinel.rb.
|
8
|
+
|
9
|
+
require "fileutils"
|
10
|
+
|
11
|
+
$pids = []
|
12
|
+
|
13
|
+
at_exit do
|
14
|
+
$pids.each do |pid|
|
15
|
+
begin
|
16
|
+
Process.kill(:INT, pid)
|
17
|
+
rescue Errno::ESRCH
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Process.waitall
|
22
|
+
end
|
23
|
+
|
24
|
+
base = File.expand_path(File.dirname(__FILE__))
|
25
|
+
|
26
|
+
# Masters
|
27
|
+
$pids << spawn("redis-server --port 6380 --loglevel warning")
|
28
|
+
$pids << spawn("redis-server --port 6381 --loglevel warning")
|
29
|
+
|
30
|
+
# Slaves of Master 1
|
31
|
+
$pids << spawn("redis-server --port 63800 --slaveof 127.0.0.1 6380 --loglevel warning")
|
32
|
+
$pids << spawn("redis-server --port 63801 --slaveof 127.0.0.1 6380 --loglevel warning")
|
33
|
+
|
34
|
+
# Slaves of Master 2
|
35
|
+
$pids << spawn("redis-server --port 63810 --slaveof 127.0.0.1 6381 --loglevel warning")
|
36
|
+
$pids << spawn("redis-server --port 63811 --slaveof 127.0.0.1 6381 --loglevel warning")
|
37
|
+
|
38
|
+
FileUtils.cp(File.join(base, "sentinel.conf"), "tmp/sentinel1.conf")
|
39
|
+
FileUtils.cp(File.join(base, "sentinel.conf"), "tmp/sentinel2.conf")
|
40
|
+
|
41
|
+
# Sentinels
|
42
|
+
$pids << spawn("redis-server tmp/sentinel1.conf --sentinel --port 26379")
|
43
|
+
$pids << spawn("redis-server tmp/sentinel2.conf --sentinel --port 26380")
|
44
|
+
|
45
|
+
sleep 30
|
46
|
+
|
47
|
+
Process.kill(:KILL, $pids[0])
|
48
|
+
|
49
|
+
Process.waitall
|
data/lib/redis.rb
CHANGED
@@ -1588,6 +1588,38 @@ class Redis
|
|
1588
1588
|
end
|
1589
1589
|
end
|
1590
1590
|
|
1591
|
+
# Return a range of members with the same score in a sorted set, by lexicographical ordering
|
1592
|
+
#
|
1593
|
+
# @example Retrieve members matching a
|
1594
|
+
# redis.zrangebylex("zset", "[a", "[a\xff")
|
1595
|
+
# # => ["aaren", "aarika", "abagael", "abby"]
|
1596
|
+
# @example Retrieve the first 2 members matching a
|
1597
|
+
# redis.zrangebylex("zset", "[a", "[a\xff", :limit => [0, 2])
|
1598
|
+
# # => ["aaren", "aarika"]
|
1599
|
+
#
|
1600
|
+
# @param [String] key
|
1601
|
+
# @param [String] min
|
1602
|
+
# - inclusive minimum is specified by prefixing `(`
|
1603
|
+
# - exclusive minimum is specified by prefixing `[`
|
1604
|
+
# @param [String] max
|
1605
|
+
# - inclusive maximum is specified by prefixing `(`
|
1606
|
+
# - exclusive maximum is specified by prefixing `[`
|
1607
|
+
# @param [Hash] options
|
1608
|
+
# - `:limit => [offset, count]`: skip `offset` members, return a maximum of
|
1609
|
+
# `count` members
|
1610
|
+
#
|
1611
|
+
# @return [Array<String>, Array<[String, Float]>]
|
1612
|
+
def zrangebylex(key, min, max, options = {})
|
1613
|
+
args = []
|
1614
|
+
|
1615
|
+
limit = options[:limit]
|
1616
|
+
args.concat(["LIMIT"] + limit) if limit
|
1617
|
+
|
1618
|
+
synchronize do |client|
|
1619
|
+
client.call([:zrangebylex, key, min, max] + args)
|
1620
|
+
end
|
1621
|
+
end
|
1622
|
+
|
1591
1623
|
# Return a range of members in a sorted set, by score.
|
1592
1624
|
#
|
1593
1625
|
# @example Retrieve members with score `>= 5` and `< 100`
|
@@ -2449,11 +2481,14 @@ class Redis
|
|
2449
2481
|
|
2450
2482
|
# Get the approximate cardinality of members added to HyperLogLog structure.
|
2451
2483
|
#
|
2452
|
-
#
|
2484
|
+
# If called with multiple keys, returns the approximate cardinality of the
|
2485
|
+
# union of the HyperLogLogs contained in the keys.
|
2486
|
+
#
|
2487
|
+
# @param [String, Array<String>] keys
|
2453
2488
|
# @return [Fixnum]
|
2454
|
-
def pfcount(
|
2489
|
+
def pfcount(*keys)
|
2455
2490
|
synchronize do |client|
|
2456
|
-
client.call([:pfcount
|
2491
|
+
client.call([:pfcount] + keys)
|
2457
2492
|
end
|
2458
2493
|
end
|
2459
2494
|
|
data/lib/redis/client.rb
CHANGED
@@ -75,6 +75,12 @@ class Redis
|
|
75
75
|
@logger = @options[:logger]
|
76
76
|
@connection = nil
|
77
77
|
@command_map = {}
|
78
|
+
|
79
|
+
if options.include?(:sentinels)
|
80
|
+
@connector = Connector::Sentinel.new(@options)
|
81
|
+
else
|
82
|
+
@connector = Connector.new(@options)
|
83
|
+
end
|
78
84
|
end
|
79
85
|
|
80
86
|
def connect
|
@@ -85,6 +91,7 @@ class Redis
|
|
85
91
|
establish_connection
|
86
92
|
call [:auth, password] if password
|
87
93
|
call [:select, db] if db != 0
|
94
|
+
@connector.check(self)
|
88
95
|
end
|
89
96
|
|
90
97
|
self
|
@@ -301,8 +308,12 @@ class Redis
|
|
301
308
|
end
|
302
309
|
|
303
310
|
def establish_connection
|
304
|
-
|
311
|
+
server = @connector.resolve.dup
|
305
312
|
|
313
|
+
@options[:host] = server[:host]
|
314
|
+
@options[:port] = server[:port]
|
315
|
+
|
316
|
+
@connection = @options[:driver].connect(server)
|
306
317
|
rescue TimeoutError
|
307
318
|
raise CannotConnectError, "Timed out connecting to Redis on #{location}"
|
308
319
|
rescue Errno::ECONNREFUSED
|
@@ -365,7 +376,7 @@ class Redis
|
|
365
376
|
|
366
377
|
if uri.scheme == "unix"
|
367
378
|
defaults[:path] = uri.path
|
368
|
-
|
379
|
+
elsif uri.scheme == "redis"
|
369
380
|
# Require the URL to have at least a host
|
370
381
|
raise ArgumentError, "invalid url" unless uri.host
|
371
382
|
|
@@ -374,6 +385,9 @@ class Redis
|
|
374
385
|
defaults[:port] = uri.port if uri.port
|
375
386
|
defaults[:password] = CGI.unescape(uri.password) if uri.password
|
376
387
|
defaults[:db] = uri.path[1..-1].to_i if uri.path
|
388
|
+
defaults[:role] = :master
|
389
|
+
else
|
390
|
+
raise ArgumentError, "invalid uri scheme '#{uri.scheme}'"
|
377
391
|
end
|
378
392
|
end
|
379
393
|
|
@@ -383,10 +397,12 @@ class Redis
|
|
383
397
|
end
|
384
398
|
|
385
399
|
if options[:path]
|
400
|
+
# Unix socket
|
386
401
|
options[:scheme] = "unix"
|
387
402
|
options.delete(:host)
|
388
403
|
options.delete(:port)
|
389
404
|
else
|
405
|
+
# TCP socket
|
390
406
|
options[:host] = options[:host].to_s
|
391
407
|
options[:port] = options[:port].to_i
|
392
408
|
end
|
@@ -432,5 +448,96 @@ class Redis
|
|
432
448
|
|
433
449
|
driver
|
434
450
|
end
|
451
|
+
|
452
|
+
class Connector
|
453
|
+
def initialize(options)
|
454
|
+
@options = options
|
455
|
+
end
|
456
|
+
|
457
|
+
def resolve
|
458
|
+
@options
|
459
|
+
end
|
460
|
+
|
461
|
+
def check(client)
|
462
|
+
end
|
463
|
+
|
464
|
+
class Sentinel < Connector
|
465
|
+
def initialize(options)
|
466
|
+
super(options)
|
467
|
+
|
468
|
+
@sentinels = options.fetch(:sentinels).dup
|
469
|
+
@role = options[:role].to_s
|
470
|
+
@master = options[:host]
|
471
|
+
end
|
472
|
+
|
473
|
+
def check(client)
|
474
|
+
# Check the instance is really of the role we are looking for.
|
475
|
+
# We can't assume the command is supported since it was introduced
|
476
|
+
# recently and this client should work with old stuff.
|
477
|
+
begin
|
478
|
+
role = client.call([:role])[0]
|
479
|
+
rescue Redis::CommandError
|
480
|
+
# Assume the test is passed if we can't get a reply from ROLE...
|
481
|
+
role = @role
|
482
|
+
end
|
483
|
+
|
484
|
+
if role != @role
|
485
|
+
disconnect
|
486
|
+
raise ConnectionError, "Instance role mismatch. Expected #{@role}, got #{role}."
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
def resolve
|
491
|
+
result = case @role
|
492
|
+
when "master"
|
493
|
+
resolve_master
|
494
|
+
when "slave"
|
495
|
+
resolve_slave
|
496
|
+
else
|
497
|
+
raise ArgumentError, "Unknown instance role #{@role}"
|
498
|
+
end
|
499
|
+
|
500
|
+
result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
|
501
|
+
end
|
502
|
+
|
503
|
+
def sentinel_detect
|
504
|
+
@sentinels.each do |sentinel|
|
505
|
+
client = Client.new(:host => sentinel[:host], :port => sentinel[:port], :timeout => 0.3)
|
506
|
+
|
507
|
+
begin
|
508
|
+
if result = yield(client)
|
509
|
+
# This sentinel responded. Make sure we ask it first next time.
|
510
|
+
@sentinels.delete(sentinel)
|
511
|
+
@sentinels.unshift(sentinel)
|
512
|
+
|
513
|
+
return result
|
514
|
+
end
|
515
|
+
ensure
|
516
|
+
client.disconnect
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
return nil
|
521
|
+
end
|
522
|
+
|
523
|
+
def resolve_master
|
524
|
+
sentinel_detect do |client|
|
525
|
+
if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
|
526
|
+
{:host => reply[0], :port => reply[1]}
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
def resolve_slave
|
532
|
+
sentinel_detect do |client|
|
533
|
+
if reply = client.call(["sentinel", "slaves", @master])
|
534
|
+
slave = Hash[*reply.sample]
|
535
|
+
|
536
|
+
{:host => slave.fetch("ip"), :port => slave.fetch("port")}
|
537
|
+
end
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
end
|
435
542
|
end
|
436
543
|
end
|
data/lib/redis/distributed.rb
CHANGED
@@ -792,8 +792,10 @@ class Redis
|
|
792
792
|
end
|
793
793
|
|
794
794
|
# Get the approximate cardinality of members added to HyperLogLog structure.
|
795
|
-
def pfcount(
|
796
|
-
|
795
|
+
def pfcount(*keys)
|
796
|
+
ensure_same_node(:pfcount, keys.flatten(1)) do |node|
|
797
|
+
node.pfcount(keys)
|
798
|
+
end
|
797
799
|
end
|
798
800
|
|
799
801
|
# Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of
|
data/lib/redis/version.rb
CHANGED
@@ -8,6 +8,20 @@ class TestCommandsOnSortedSets < Test::Unit::TestCase
|
|
8
8
|
include Helper::Client
|
9
9
|
include Lint::SortedSets
|
10
10
|
|
11
|
+
def test_zrangebylex
|
12
|
+
target_version "2.8.9" do
|
13
|
+
r.zadd "foo", 0, "aaren"
|
14
|
+
r.zadd "foo", 0, "abagael"
|
15
|
+
r.zadd "foo", 0, "abby"
|
16
|
+
r.zadd "foo", 0, "abbygail"
|
17
|
+
|
18
|
+
assert_equal ["aaren", "abagael", "abby", "abbygail"], r.zrangebylex("foo", "[a", "[a\xff")
|
19
|
+
assert_equal ["aaren", "abagael"], r.zrangebylex("foo", "[a", "[a\xff", :limit => [0, 2])
|
20
|
+
assert_equal ["abby", "abbygail"], r.zrangebylex("foo", "(abb", "(abb\xff")
|
21
|
+
assert_equal ["abbygail"], r.zrangebylex("foo", "(abby", "(abby\xff")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
11
25
|
def test_zcount
|
12
26
|
r.zadd "foo", 1, "s1"
|
13
27
|
r.zadd "foo", 2, "s2"
|
@@ -19,4 +19,15 @@ class TestDistributedCommandsOnHyperLogLog < Test::Unit::TestCase
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
|
22
|
+
def test_pfcount_multiple_keys_diff_nodes
|
23
|
+
target_version "2.8.9" do
|
24
|
+
assert_raise Redis::Distributed::CannotDistribute do
|
25
|
+
r.pfadd "foo", "s1"
|
26
|
+
r.pfadd "bar", "s2"
|
27
|
+
|
28
|
+
assert r.pfcount("res", "foo", "bar")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/test/helper.rb
CHANGED
data/test/internals_test.rb
CHANGED
@@ -375,8 +375,16 @@ class TestInternals < Test::Unit::TestCase
|
|
375
375
|
begin
|
376
376
|
s = Socket.new(af, Socket::SOCK_STREAM, 0)
|
377
377
|
begin
|
378
|
-
|
379
|
-
|
378
|
+
tries = 5
|
379
|
+
begin
|
380
|
+
sa = Socket.pack_sockaddr_in(1024 + Random.rand(63076), hosts[af])
|
381
|
+
s.bind(sa)
|
382
|
+
rescue Errno::EADDRINUSE
|
383
|
+
tries -= 1
|
384
|
+
retry if tries > 0
|
385
|
+
|
386
|
+
raise
|
387
|
+
end
|
380
388
|
yield
|
381
389
|
rescue Errno::EADDRNOTAVAIL
|
382
390
|
ensure
|
data/test/lint/hyper_log_log.rb
CHANGED
@@ -33,16 +33,28 @@ module Lint
|
|
33
33
|
|
34
34
|
def test_variadic_pfcount
|
35
35
|
target_version "2.8.9" do
|
36
|
-
assert_equal 0, r.pfcount(["foo", "bar"])
|
36
|
+
assert_equal 0, r.pfcount(["{1}foo", "{1}bar"])
|
37
37
|
|
38
|
-
assert_equal true, r.pfadd("foo", "s1")
|
39
|
-
assert_equal true, r.pfadd("bar", "s1")
|
40
|
-
assert_equal true, r.pfadd("bar", "s2")
|
38
|
+
assert_equal true, r.pfadd("{1}foo", "s1")
|
39
|
+
assert_equal true, r.pfadd("{1}bar", "s1")
|
40
|
+
assert_equal true, r.pfadd("{1}bar", "s2")
|
41
|
+
|
42
|
+
assert_equal 2, r.pfcount("{1}foo", "{1}bar")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_variadic_pfcount_expanded
|
47
|
+
target_version "2.8.9" do
|
48
|
+
assert_equal 0, r.pfcount("{1}foo", "{1}bar")
|
49
|
+
|
50
|
+
assert_equal true, r.pfadd("{1}foo", "s1")
|
51
|
+
assert_equal true, r.pfadd("{1}bar", "s1")
|
52
|
+
assert_equal true, r.pfadd("{1}bar", "s2")
|
41
53
|
|
42
|
-
assert_equal 2, r.pfcount(
|
54
|
+
assert_equal 2, r.pfcount("{1}foo", "{1}bar")
|
43
55
|
end
|
44
56
|
end
|
45
57
|
|
46
58
|
end
|
47
59
|
|
48
|
-
end
|
60
|
+
end
|
@@ -191,7 +191,7 @@ class TestPublishSubscribe < Test::Unit::TestCase
|
|
191
191
|
def test_subscribe_past_a_timeout
|
192
192
|
# For some reason, a thread here doesn't reproduce the issue.
|
193
193
|
sleep = %{sleep #{OPTIONS[:timeout] * 2}}
|
194
|
-
publish = %{
|
194
|
+
publish = %{ruby -rsocket -e 't=TCPSocket.new("127.0.0.1",#{OPTIONS[:port]});t.write("publish foo bar\\r\\n");t.read(4);t.close'}
|
195
195
|
cmd = [sleep, publish].join("; ")
|
196
196
|
|
197
197
|
IO.popen(cmd, "r+") do |pipe|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ezra Zygmuntowicz
|
@@ -16,36 +16,36 @@ authors:
|
|
16
16
|
autorequire:
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
|
-
date: 2014-
|
19
|
+
date: 2014-12-11 00:00:00.000000000 Z
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
22
|
name: rake
|
23
23
|
requirement: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
|
-
- -
|
25
|
+
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: '0'
|
28
28
|
type: :development
|
29
29
|
prerelease: false
|
30
30
|
version_requirements: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
|
-
- -
|
32
|
+
- - ">="
|
33
33
|
- !ruby/object:Gem::Version
|
34
34
|
version: '0'
|
35
|
-
description:
|
36
|
-
|
37
|
-
|
35
|
+
description: |2
|
36
|
+
A Ruby client that tries to match Redis' API one-to-one, while still
|
37
|
+
providing an idiomatic interface. It features thread-safety,
|
38
|
+
client-side sharding, pipelining, and an obsession for performance.
|
38
39
|
email:
|
39
40
|
- redis-db@googlegroups.com
|
40
41
|
executables: []
|
41
42
|
extensions: []
|
42
43
|
extra_rdoc_files: []
|
43
44
|
files:
|
44
|
-
- .gitignore
|
45
|
-
- .
|
46
|
-
- .travis
|
47
|
-
- .
|
48
|
-
- .yardopts
|
45
|
+
- ".gitignore"
|
46
|
+
- ".travis.yml"
|
47
|
+
- ".travis/Gemfile"
|
48
|
+
- ".yardopts"
|
49
49
|
- CHANGELOG.md
|
50
50
|
- Gemfile
|
51
51
|
- LICENSE
|
@@ -57,10 +57,14 @@ files:
|
|
57
57
|
- benchmarking/suite.rb
|
58
58
|
- benchmarking/worker.rb
|
59
59
|
- examples/basic.rb
|
60
|
+
- examples/consistency.rb
|
60
61
|
- examples/dist_redis.rb
|
61
62
|
- examples/incr-decr.rb
|
62
63
|
- examples/list.rb
|
63
64
|
- examples/pubsub.rb
|
65
|
+
- examples/sentinel.rb
|
66
|
+
- examples/sentinel/sentinel.conf
|
67
|
+
- examples/sentinel/start
|
64
68
|
- examples/sets.rb
|
65
69
|
- examples/unicorn/config.ru
|
66
70
|
- examples/unicorn/unicorn.rb
|
@@ -153,12 +157,12 @@ require_paths:
|
|
153
157
|
- lib
|
154
158
|
required_ruby_version: !ruby/object:Gem::Requirement
|
155
159
|
requirements:
|
156
|
-
- -
|
160
|
+
- - ">="
|
157
161
|
- !ruby/object:Gem::Version
|
158
162
|
version: '0'
|
159
163
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
160
164
|
requirements:
|
161
|
-
- -
|
165
|
+
- - ">="
|
162
166
|
- !ruby/object:Gem::Version
|
163
167
|
version: '0'
|
164
168
|
requirements: []
|
@@ -232,4 +236,3 @@ test_files:
|
|
232
236
|
- test/transactions_test.rb
|
233
237
|
- test/unknown_commands_test.rb
|
234
238
|
- test/url_param_test.rb
|
235
|
-
has_rdoc:
|
data/.order
DELETED
@@ -1,170 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"connection": [
|
3
|
-
"auth",
|
4
|
-
"select",
|
5
|
-
"ping",
|
6
|
-
"echo",
|
7
|
-
"quit"
|
8
|
-
],
|
9
|
-
"server": [
|
10
|
-
"bgrewriteaof",
|
11
|
-
"bgsave",
|
12
|
-
"config",
|
13
|
-
"dbsize",
|
14
|
-
"debug",
|
15
|
-
"flushall",
|
16
|
-
"flushdb",
|
17
|
-
"info",
|
18
|
-
"lastsave",
|
19
|
-
"monitor",
|
20
|
-
"save",
|
21
|
-
"shutdown",
|
22
|
-
"slaveof",
|
23
|
-
"slowlog",
|
24
|
-
"sync",
|
25
|
-
"time",
|
26
|
-
"client"
|
27
|
-
],
|
28
|
-
"generic": [
|
29
|
-
"persist",
|
30
|
-
"expire",
|
31
|
-
"expireat",
|
32
|
-
"ttl",
|
33
|
-
"pexpire",
|
34
|
-
"pexpireat",
|
35
|
-
"pttl",
|
36
|
-
"dump",
|
37
|
-
"restore",
|
38
|
-
"del",
|
39
|
-
"exists",
|
40
|
-
"keys",
|
41
|
-
"migrate",
|
42
|
-
"move",
|
43
|
-
"object",
|
44
|
-
"randomkey",
|
45
|
-
"rename",
|
46
|
-
"renamenx",
|
47
|
-
"sort",
|
48
|
-
"type"
|
49
|
-
],
|
50
|
-
"string": [
|
51
|
-
"decr",
|
52
|
-
"decrby",
|
53
|
-
"incr",
|
54
|
-
"incrby",
|
55
|
-
"incrbyfloat",
|
56
|
-
"set",
|
57
|
-
"setex",
|
58
|
-
"psetex",
|
59
|
-
"setnx",
|
60
|
-
"mset",
|
61
|
-
"mapped_mset",
|
62
|
-
"msetnx",
|
63
|
-
"mapped_msetnx",
|
64
|
-
"get",
|
65
|
-
"mget",
|
66
|
-
"mapped_mget",
|
67
|
-
"setrange",
|
68
|
-
"getrange",
|
69
|
-
"setbit",
|
70
|
-
"getbit",
|
71
|
-
"append",
|
72
|
-
"bitcount",
|
73
|
-
"getset",
|
74
|
-
"strlen",
|
75
|
-
"bitop"
|
76
|
-
],
|
77
|
-
"list": [
|
78
|
-
"llen",
|
79
|
-
"lpush",
|
80
|
-
"lpushx",
|
81
|
-
"rpush",
|
82
|
-
"rpushx",
|
83
|
-
"lpop",
|
84
|
-
"rpop",
|
85
|
-
"rpoplpush",
|
86
|
-
"_bpop",
|
87
|
-
"blpop",
|
88
|
-
"brpop",
|
89
|
-
"brpoplpush",
|
90
|
-
"lindex",
|
91
|
-
"linsert",
|
92
|
-
"lrange",
|
93
|
-
"lrem",
|
94
|
-
"lset",
|
95
|
-
"ltrim"
|
96
|
-
],
|
97
|
-
"set": [
|
98
|
-
"scard",
|
99
|
-
"sadd",
|
100
|
-
"srem",
|
101
|
-
"spop",
|
102
|
-
"srandmember",
|
103
|
-
"smove",
|
104
|
-
"sismember",
|
105
|
-
"smembers",
|
106
|
-
"sdiff",
|
107
|
-
"sdiffstore",
|
108
|
-
"sinter",
|
109
|
-
"sinterstore",
|
110
|
-
"sunion",
|
111
|
-
"sunionstore"
|
112
|
-
],
|
113
|
-
"sorted_set": [
|
114
|
-
"zcard",
|
115
|
-
"zadd",
|
116
|
-
"zincrby",
|
117
|
-
"zrem",
|
118
|
-
"zscore",
|
119
|
-
"zrange",
|
120
|
-
"zrevrange",
|
121
|
-
"zrank",
|
122
|
-
"zrevrank",
|
123
|
-
"zremrangebyrank",
|
124
|
-
"zrangebyscore",
|
125
|
-
"zrevrangebyscore",
|
126
|
-
"zremrangebyscore",
|
127
|
-
"zcount",
|
128
|
-
"zinterstore",
|
129
|
-
"zunionstore"
|
130
|
-
],
|
131
|
-
"hash": [
|
132
|
-
"hlen",
|
133
|
-
"hset",
|
134
|
-
"hsetnx",
|
135
|
-
"hmset",
|
136
|
-
"mapped_hmset",
|
137
|
-
"hget",
|
138
|
-
"hmget",
|
139
|
-
"mapped_hmget",
|
140
|
-
"hdel",
|
141
|
-
"hexists",
|
142
|
-
"hincrby",
|
143
|
-
"hincrbyfloat",
|
144
|
-
"hkeys",
|
145
|
-
"hvals",
|
146
|
-
"hgetall"
|
147
|
-
],
|
148
|
-
"pubsub": [
|
149
|
-
"publish",
|
150
|
-
"subscribed?",
|
151
|
-
"subscribe",
|
152
|
-
"unsubscribe",
|
153
|
-
"psubscribe",
|
154
|
-
"punsubscribe"
|
155
|
-
],
|
156
|
-
"transactions": [
|
157
|
-
"watch",
|
158
|
-
"unwatch",
|
159
|
-
"pipelined",
|
160
|
-
"multi",
|
161
|
-
"exec",
|
162
|
-
"discard"
|
163
|
-
],
|
164
|
-
"scripting": [
|
165
|
-
"script",
|
166
|
-
"_eval",
|
167
|
-
"eval",
|
168
|
-
"evalsha"
|
169
|
-
]
|
170
|
-
}
|