redis-client 0.27.0 → 0.28.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 +4 -4
- data/CHANGELOG.md +5 -1
- data/README.md +26 -1
- data/lib/redis_client/hash_ring.rb +87 -0
- data/lib/redis_client/version.rb +1 -1
- data/lib/redis_client.rb +17 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2fc78ecaeaed568c9917391585a7a185c90f8d7d569b8443c0fb2f0e918c2ca6
|
|
4
|
+
data.tar.gz: f9c7cbcb9c39267853681e926a71c27c3619bf0683fa5cd86885c585f2dfa239
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3738437f5876f4f8c09f82a19e4e75462779dae006ed37ac06d3d0ca997bb0190fc676b705e5f6186a5854caff4d98dddc8b035c0fbaa854f9d77b179cb01c94
|
|
7
|
+
data.tar.gz: 4f9788a5547647cee34ddf01f60400e9645ca2c73f5329155b58670019733ec945331674f94b5a7720754ea2dd12b85d62040060166145beaea9b462514e6500
|
data/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# Unreleased
|
|
2
2
|
|
|
3
|
+
# 0.28.0
|
|
4
|
+
|
|
5
|
+
- Added `RedisClient::HashRing` for horizontal sharing (compatible with `Redis::Distributed` from `redis-rb`).
|
|
6
|
+
|
|
3
7
|
# 0.27.0
|
|
4
8
|
|
|
5
|
-
- Added `idle_timeout` to revalidate connections that haven't been successfuly used in a long time. Defaults to
|
|
9
|
+
- Added `idle_timeout` to revalidate connections that haven't been successfuly used in a long time. Defaults to 30 seconds.
|
|
6
10
|
- Added `driver_info` configuration, to issue `CLIENT SETINFO` during connection prelude.
|
|
7
11
|
|
|
8
12
|
# 0.26.4
|
data/README.md
CHANGED
|
@@ -83,7 +83,7 @@ redis.call("GET", "mykey")
|
|
|
83
83
|
- `connect_timeout`: The connection timeout, takes precedence over the general timeout when connecting to the server.
|
|
84
84
|
- `read_timeout`: The read timeout, takes precedence over the general timeout when reading responses from the server.
|
|
85
85
|
- `write_timeout`: The write timeout, takes precedence over the general timeout when sending commands to the server.
|
|
86
|
-
- `idle_timeout`: Amount of time after which an idle connection has to be revalidated with a PING command. Defaults to `
|
|
86
|
+
- `idle_timeout`: Amount of time after which an idle connection has to be revalidated with a PING command. Defaults to `30` seconds.
|
|
87
87
|
- `reconnect_attempts`: Specify how many times the client should retry to send queries. Defaults to `0`. Makes sure to read the [reconnection section](#reconnection) before enabling it.
|
|
88
88
|
- `circuit_breaker`: A Hash with circuit breaker configuration. Defaults to `nil`. See the [circuit breaker section](#circuit-breaker) for details.
|
|
89
89
|
- `protocol:` The version of the RESP protocol to use. Default to `3`.
|
|
@@ -144,6 +144,31 @@ redis_config = RedisClient.sentinel(name: 'mymaster', sentinel_username: 'appuse
|
|
|
144
144
|
If you specify a username and/or password at the top level for your main Redis instance, Sentinel *will not* using thouse credentials
|
|
145
145
|
|
|
146
146
|
```ruby
|
|
147
|
+
|
|
148
|
+
### Consistent Hashing
|
|
149
|
+
|
|
150
|
+
To horizontally shard keys across multiple servers without relying on clustering, a `RedisClient::HashRing` class is provided:
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
ring = RedisClient.ring(
|
|
154
|
+
RedisClient.config(host: "10.0.1.1", port: 6380).new_pool(timeout: 0.5, size: 3),
|
|
155
|
+
RedisClient.config(host: "10.0.1.2", port: 6380).new_pool(timeout: 0.5, size: 3),
|
|
156
|
+
RedisClient.config(host: "10.0.1.3", port: 6380).new_pool(timeout: 0.5, size: 3),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
ring.node_for("cache_key").call("GET", "cache_key") # => "value"
|
|
160
|
+
|
|
161
|
+
ring.nodes_for("key1", "key2", "key3").each do |node, keys|
|
|
162
|
+
node.call("DEL", *keys)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
ring.nodes.each do |node|
|
|
166
|
+
node.close
|
|
167
|
+
end
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Note that regular clients do respond to `node_for`, `nodes_for` and `nodes`, so that code that support `RedisClient.ring` is also usable with a single server.
|
|
171
|
+
|
|
147
172
|
# Use 'mysecret' to authenticate against the mymaster instance, but skip authentication for the sentinels:
|
|
148
173
|
SENTINELS = [{ host: '127.0.0.1', port: 26380 },
|
|
149
174
|
{ host: '127.0.0.1', port: 26381 }]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'zlib'
|
|
4
|
+
|
|
5
|
+
class RedisClient
|
|
6
|
+
class HashRing
|
|
7
|
+
POINTS_PER_SERVER = 160
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
attr_writer :digest
|
|
11
|
+
|
|
12
|
+
def digest
|
|
13
|
+
@digest ||= begin
|
|
14
|
+
require 'digest/md5'
|
|
15
|
+
Digest::MD5
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :nodes
|
|
21
|
+
|
|
22
|
+
def initialize(nodes = [], replicas: POINTS_PER_SERVER, digest: self.class.digest)
|
|
23
|
+
@replicas = replicas
|
|
24
|
+
@ring = {}
|
|
25
|
+
@digest = digest
|
|
26
|
+
ids = {}
|
|
27
|
+
@nodes = nodes.dup.freeze
|
|
28
|
+
nodes.each do |node|
|
|
29
|
+
id = node.id || node.config.server_url
|
|
30
|
+
if ids[id]
|
|
31
|
+
raise ArgumentError, "duplicate node id: #{id.inspect}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
ids[id] = true
|
|
35
|
+
|
|
36
|
+
replicas.times do |i|
|
|
37
|
+
@ring[server_hash_for("#{id}:#{i}".freeze)] = node
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
@sorted_keys = @ring.keys
|
|
41
|
+
@sorted_keys.sort!
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# get the node in the hash ring for this key
|
|
45
|
+
def node_for(key)
|
|
46
|
+
hash = hash_for(key)
|
|
47
|
+
idx = binary_search(@sorted_keys, hash)
|
|
48
|
+
@ring[@sorted_keys[idx]]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def nodes_for(*keys)
|
|
52
|
+
keys.flatten!
|
|
53
|
+
mapping = {}
|
|
54
|
+
keys.each do |key|
|
|
55
|
+
(mapping[node_for(key)] ||= []) << key
|
|
56
|
+
end
|
|
57
|
+
mapping
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def hash_for(key)
|
|
63
|
+
Zlib.crc32(key)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def server_hash_for(key)
|
|
67
|
+
@digest.digest(key).unpack1("L>")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Find the closest index in HashRing with value <= the given value
|
|
71
|
+
def binary_search(ary, value)
|
|
72
|
+
upper = ary.size
|
|
73
|
+
lower = 0
|
|
74
|
+
|
|
75
|
+
while lower < upper
|
|
76
|
+
mid = (lower + upper) / 2
|
|
77
|
+
if ary[mid] > value
|
|
78
|
+
upper = mid
|
|
79
|
+
else
|
|
80
|
+
lower = mid + 1
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
upper - 1
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
data/lib/redis_client/version.rb
CHANGED
data/lib/redis_client.rb
CHANGED
|
@@ -61,7 +61,7 @@ class RedisClient
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
module Common
|
|
64
|
-
attr_reader :config, :id
|
|
64
|
+
attr_reader :config, :id, :nodes
|
|
65
65
|
attr_accessor :connect_timeout, :read_timeout, :write_timeout
|
|
66
66
|
|
|
67
67
|
def initialize(
|
|
@@ -78,11 +78,21 @@ class RedisClient
|
|
|
78
78
|
@write_timeout = write_timeout
|
|
79
79
|
@command_builder = config.command_builder
|
|
80
80
|
@pid = PIDCache.pid
|
|
81
|
+
@nodes = [self].freeze
|
|
81
82
|
end
|
|
82
83
|
|
|
83
84
|
def timeout=(timeout)
|
|
84
85
|
@connect_timeout = @read_timeout = @write_timeout = timeout
|
|
85
86
|
end
|
|
87
|
+
|
|
88
|
+
def node_for(_key)
|
|
89
|
+
self
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def nodes_for(*keys)
|
|
93
|
+
keys.flatten!
|
|
94
|
+
{ self => keys }
|
|
95
|
+
end
|
|
86
96
|
end
|
|
87
97
|
|
|
88
98
|
module HasConfig
|
|
@@ -229,6 +239,12 @@ class RedisClient
|
|
|
229
239
|
SentinelConfig.new(client_implementation: self, **kwargs)
|
|
230
240
|
end
|
|
231
241
|
|
|
242
|
+
def ring(*clients, **options)
|
|
243
|
+
clients.flatten!
|
|
244
|
+
require "redis_client/hash_ring" unless defined?(HashRing)
|
|
245
|
+
HashRing.new(clients, **options)
|
|
246
|
+
end
|
|
247
|
+
|
|
232
248
|
def new(arg = nil, **kwargs)
|
|
233
249
|
if arg.is_a?(Config::Common)
|
|
234
250
|
super
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: redis-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.28.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jean Boussier
|
|
@@ -39,6 +39,7 @@ files:
|
|
|
39
39
|
- lib/redis_client/config.rb
|
|
40
40
|
- lib/redis_client/connection_mixin.rb
|
|
41
41
|
- lib/redis_client/decorator.rb
|
|
42
|
+
- lib/redis_client/hash_ring.rb
|
|
42
43
|
- lib/redis_client/middlewares.rb
|
|
43
44
|
- lib/redis_client/pid_cache.rb
|
|
44
45
|
- lib/redis_client/pooled.rb
|