redis_cluster 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f4e3f46f9197fb5e41afea6d2b4e0f41af7defbc
4
- data.tar.gz: 21e8e6613f409e4d2ea6942cac0c5a44dc855bff
3
+ metadata.gz: e10ed9adf21d43959b884dd43b59474b469c84cf
4
+ data.tar.gz: dfab801d69bf40c1d92fdb3f123ba980cc7aeade
5
5
  SHA512:
6
- metadata.gz: d01fd2610f8e69cec1344105227b0ae007aa60fd066d4f5bc9d77320dbd6eb0b8af6bd524bc4fb9771a3a125cf43b686c4838ac3ec85d414dd7aa9d41a1fdab3
7
- data.tar.gz: e8006dabb95bfc9ff141fdf0b8120b308b649e65f3183fe6400ff77c4b655f00d91db5ceb5e4131ebfa48d343afe9dc1ca2f2382db0fb77a84acf379626ef3cf
6
+ metadata.gz: e0e96edb02ff52f9a4eda36d1e922288a4459669d645ac736a8856a35a4cdbc2ca10b0cc37ab6560b4302eabb2270fe1ead8d9fa4cfb4bea53360ffaab8cae8e
7
+ data.tar.gz: 80706f7728f362931760c1473bf7f01b68ee7e95953d85da47126170c43d30af0e138e5116179f8e11fd54544e451f8714351a5148b6c17cc071e4a70a42b741
data/.travis.yml CHANGED
@@ -1,10 +1,10 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 2.4.0
3
4
  - 2.3.3
4
5
  - 2.2
5
6
  - 2.1
6
7
  - 2.0
7
- - 1.9.3
8
8
  - jruby
9
9
  before_install:
10
10
  - gem update bundler
data/README.md CHANGED
@@ -2,79 +2,158 @@
2
2
 
3
3
  ![travis ci](https://travis-ci.org/zhchsf/redis_cluster.svg?branch=master)
4
4
 
5
- First see: [https://redis.io/topics/cluster-tutorial](https://redis.io/topics/cluster-tutorial)
5
+ Support: Ruby 2.0+
6
6
 
7
- RedisCluster for ruby is rewrited from [https://github.com/antirez/redis-rb-cluster](https://github.com/antirez/redis-rb-cluster)
7
+ Redis Cluster is a Redis configuration that allows data to be automatically
8
+ sharded across a number of different nodes. You can find its main documentation
9
+ at https://redis.io/topics/cluster-tutorial.
8
10
 
11
+ [`redis-rb`](https://github.com/redis/redis-rb), the most common Redis gem for
12
+ Ruby, doesn't offer Redis Cluster support. This gem works in conjunction with
13
+ redis-rb to add the missing functionality. It's based on [antirez's prototype
14
+ reference implementation](https://github.com/antirez/redis-rb-cluster) (which
15
+ is not maintained).
9
16
 
10
17
  ## Installation
11
18
 
12
- Add this line to your application's Gemfile:
19
+ Add it to your `Gemfile`:
13
20
 
14
21
  ```ruby
15
22
  gem 'redis_cluster'
16
23
  ```
17
24
 
18
- And then execute:
25
+ ## Usage
19
26
 
20
- $ bundle
27
+ Initialize `RedisCluster` with an array of Redis Cluster host nodes:
21
28
 
22
- Or install it yourself as:
29
+ ```ruby
30
+ rs = RedisCluster.new([
31
+ {host: '127.0.0.1', port: 7000},
32
+ {host: '127.0.0.1', port: 7001}
33
+ ])
34
+ rs.set "test", 1
35
+ rs.get "test"
36
+ ```
23
37
 
24
- $ gem install redis_cluster
38
+ The library will issue the `CLUSTER SLOTS` command to configured hosts it it
39
+ receives a `MOVED` response, so it's safe to configure it with only a subset of
40
+ the total nodes in the cluster.
25
41
 
26
- ## Usage
42
+ ### Other options
27
43
 
28
- First you need to configure redis cluster with some nodes! Please see: [https://redis.io/topics/cluster-tutorial](https://redis.io/topics/cluster-tutorial)
44
+ Most options are forwarded onto underlying `Redis` clients. If for example
45
+ `masterauth` and `requirepass` are enabled, the password can be set like this:
29
46
 
30
47
  ```ruby
31
- # Don't need all, gem can auto detect all nodes, and process failover if some master nodes down
32
- hosts = [{host: '127.0.0.1', port: 7000}, {host: '127.0.0.1', port: 7001}]
33
- rs = RedisCluster.new hosts
34
- rs.set "test", 1
35
- rs.get "test"
48
+ RedisCluster.new(hosts, password: 'password')
49
+ ```
50
+
51
+ ### Standalone Redis
52
+
53
+ If initialized with a host hash instead of an array, the library will assume
54
+ that it's operating on a standalone Redis, and cluster functionality will be
55
+ disabled:
56
+
57
+ ```ruby
58
+ rs = RedisCluster.new({host: '127.0.0.1', port: 7000})
36
59
  ```
37
60
 
38
- At development environment with single redis node, you can set hosts a hash value: {host: 'xx', port: 6379}.
61
+ When configured with an array of hosts the library normally requires that they
62
+ be part of a Redis Cluster, but that check can be disabled by setting
63
+ `force_cluster: false`. This may be useful for development or test environments
64
+ where a full cluster isn't available, but where a standalone Redis will do just
65
+ as well.
39
66
 
40
- If masterauth & requirepass configed, you can initialize below:
41
67
  ```ruby
42
- RedisCluster.new hosts, password: 'password'
68
+ rs = RedisCluster.new([
69
+ {host: '127.0.0.1', port: 7000},
70
+ ], force_cluster: false)
43
71
  ```
44
72
 
45
- now support keys command with scanning all nodes:
73
+ ### Logging
74
+
75
+ A logger can be specified with the `logger` option. It should be compatible
76
+ with the interface of Ruby's `Logger` from the standard library.
77
+
78
+ ```ruby
79
+ require 'logger'
80
+ logger = Logger.new(STDOUT)
81
+ logger.level = Logger::WARN
82
+ RedisCluster.new(hosts, logger: logger)
83
+ ```
84
+
85
+ ### `KEYS`
86
+
87
+ The `KEYS` command will scan all nodes:
88
+
46
89
  ```ruby
47
90
  rs.keys 'test*'
48
91
  ```
49
92
 
50
- limited support commands: pipelined, multi
93
+ ### Pipelining, `MULTI`
94
+
95
+ There is limited support for pipelining and `MULTI`:
96
+
51
97
  ```ruby
52
- # Only support pipeline commands to one redis node once
53
- # You must ensure keys at one slot: use same key or hash tags
54
- # If you don't, not raise any errors now
55
98
  rs.pipelined do
56
99
  rs.set "{foo}one", 1
57
100
  rs.set "{foo}two", 2
58
101
  end
59
102
  ```
60
103
 
61
- script, eval, evalsha
104
+ Note that all keys used in a pipeline must map to the same Redis node. This is
105
+ possible through the use of Redis Cluster "hash tags" where only the section of
106
+ a key name wrapped in `{}` when calculating a key's hash.
107
+
108
+ #### `EVAL`, `EVALSHA`, `SCRIPT`
109
+
110
+ `EVAL` and `EVALSHA` must only rely on keys that map to a single slot (again,
111
+ possible with hash tags). `KEYS` should be used to retrieve keys in Lua
112
+ scripts.
113
+
114
+ ```ruby
115
+ rs.eval "return redis.call('get', KEYS[1]) + ARGV[1]", [:test], [3]
116
+ rs.evalsha '727fc2fb7c0f11ec134d998654e3dadaacf31a97', [:test], [5]
117
+
118
+ # Even if a Lua script doesn't need any keys or argvs, you'll still need to
119
+ specify a dummy key.
120
+ rs.eval "return 'hello redis!'", [:foo]
121
+ ```
122
+
123
+ `SCRIPT` commands will run on all nodes:
124
+
62
125
  ```ruby
63
126
  # script commands will run on all nodes
64
127
  rs.script :load, "return redis.call('get', KEYS[1])"
65
128
  rs.script :exists, '4e6d8fc8bb01276962cce5371fa795a7763657ae'
66
129
  rs.script :flush
130
+ ```
67
131
 
68
- # eval/evalsha must executed at one node with hash tag keys in same slot
69
- # and must use KEYS to fetch keys in lua script
70
- rs.eval "return redis.call('get', KEYS[1]) + ARGV[1]", [:test], [3]
71
- rs.evalsha '727fc2fb7c0f11ec134d998654e3dadaacf31a97', [:test], [5]
132
+ ## Development
72
133
 
73
- # if lua script don't depend on any keys and argvs, you also need execute with a key
74
- rs.eval "return 'hello redis!'", [:foo]
134
+ Clone the repository and then install dependencies:
135
+
136
+ ```sh
137
+ bin/setup
75
138
  ```
76
139
 
77
- ## Benchmark test
140
+ Run tests:
141
+
142
+ ```sh
143
+ rake spec
144
+ ```
145
+
146
+ `bin/console` will bring up an interactive prompt for other experimentation.
147
+
148
+ ### Releases
149
+
150
+ To release a new version, update the version number in `version.rb` and run
151
+ `bundle exec rake release`. This will create a Git tag for the version, push
152
+ Git commits and tags to GitHub, and push the `.gem` file to Rubygems.
153
+
154
+ The gem can be installed locally with `bundle exec rake install`.
155
+
156
+ ### Benchmark test
78
157
 
79
158
  ```ruby
80
159
  Benchmark.bm do |x|
@@ -96,19 +175,18 @@ Benchmark.bm do |x|
96
175
  end
97
176
  ```
98
177
 
99
-
100
- ## Development
101
-
102
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
103
-
104
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
105
-
106
178
  ## Contributing
107
179
 
108
- Bug reports and pull requests are welcome on GitHub at https://github.com/zhchsf/redis_cluster. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
109
-
180
+ Bug reports and pull requests are welcome on GitHub. This project is intended
181
+ to be a safe, welcoming space for collaboration, and contributors are expected
182
+ to adhere to the [Contributor Covenant](http://contributor-covenant.org) code
183
+ of conduct.
110
184
 
111
185
  ## License
112
186
 
113
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
187
+ The gem is available as open source under the terms of the [MIT
188
+ License](http://opensource.org/licenses/MIT).
114
189
 
190
+ <!--
191
+ # vim: set tw=79:
192
+ -->
@@ -4,8 +4,9 @@ module RedisCluster
4
4
 
5
5
  class Client
6
6
 
7
- def initialize(startup_hosts, configs = {})
8
- @startup_hosts = startup_hosts
7
+ def initialize(hosts, configs = {})
8
+ @hosts = hosts.dup
9
+ @initial_hosts = hosts.dup
9
10
 
10
11
  # Extract configuration options relevant to Redis Cluster.
11
12
 
@@ -13,65 +14,98 @@ module RedisCluster
13
14
  # the option existed
14
15
  @force_cluster = configs.delete(:force_cluster) { |_key| true }
15
16
 
16
- # The number of times to retry a failed execute. Redis errors, `MOVE`, or
17
- # `ASK` are all considered failures that will count towards this tally. A
18
- # count of at least 2 is probably sensible because if a node disappears
19
- # the first try will be a Redis error, the second try retry will probably
20
- # be a `MOVE` (whereupon the node pool is reloaded), and it will take
21
- # until the third try to succeed.
22
- @retry_count = configs.delete(:retry_count) { |_key| 3 }
17
+ # An optional logger. Should respond like the standard Ruby `Logger`:
18
+ #
19
+ # http://ruby-doc.org/stdlib-2.4.0/libdoc/logger/rdoc/Logger.html
20
+ @logger = configs.delete(:logger) { |_key| nil }
21
+
22
+ # The number of times to retry when it detects a failure that looks like
23
+ # it might be intermittent.
24
+ #
25
+ # It might be worth setting this to `0` if you'd like full visibility
26
+ # around what kinds of errors are occurring. Possibly in conjunction with
27
+ # your own out-of-library retry loop and/or circuit breaker.
28
+ @retry_count = configs.delete(:retry_count) { |_key| 2 }
23
29
 
24
30
  # Any leftover configuration goes through to the pool and onto individual
25
31
  # Redis clients.
26
32
  @pool = Pool.new(configs)
27
33
  @mutex = Mutex.new
28
34
 
29
- reload_pool_nodes(true)
35
+ reload_pool_nodes
30
36
  end
31
37
 
32
38
  def execute(method, args, &block)
33
39
  asking = false
34
- last_error = nil
35
- retries_left = @retry_count
36
- try_random_node = false
37
-
38
- # We use `>= 0` instead of `> 0` because we decrement this counter on the
39
- # first run.
40
- while retries_left >= 0
41
- retries_left -= 1
42
-
43
- begin
44
- return @pool.execute(method, args, {asking: asking, random_node: try_random_node}, &block)
45
-
46
- rescue Errno::ECONNREFUSED, Redis::TimeoutError, Redis::CannotConnectError, Errno::EACCES => e
47
- last_error = e
48
-
40
+ retried = false
41
+
42
+ # Note that there are two levels of retry loops here.
43
+ #
44
+ # The first is for intermittent failures like "host unreachable" or
45
+ # timeouts. These are retried a number of times equal to @retry_count.
46
+ #
47
+ # The second is when it receives an `ASK` or `MOVED` error response from
48
+ # Redis. In this case the client will complete re-enter its execution
49
+ # loop and retry the command after any necessary prework (if `MOVED`, it
50
+ # will attempt to reload the node pool first). This will only ever be
51
+ # retried one time (see notes below). This loop uses Ruby's `retry`
52
+ # syntax for blocks, so keep an eye out for that in the code below.
53
+ #
54
+ # It's worth noting that if these conditions ever combine, you could see
55
+ # more network attempts than @retry_count. An initial execution attempt
56
+ # might fail intermittently a couple times before sending a `MOVED`. The
57
+ # client will then attempt to reload the node pool, an operation which is
58
+ # also retried for intermittent failures. It could then return to the
59
+ # main execution and fail another couple of times intermittently. This
60
+ # should be an extreme edge case, but it's worth considering if you're
61
+ # running at large scale.
62
+ begin
63
+ retry_intermittent_loop do |attempt|
49
64
  # Getting an error while executing may be an indication that we've
50
65
  # lost the node that we were talking to and in that case it makes
51
66
  # sense to try a different node and maybe reload our node pool (if
52
67
  # the new node issues a `MOVE`).
53
- try_random_node = true
68
+ try_random_node = attempt > 0
54
69
 
55
- rescue => e
56
- last_error = e
70
+ return @pool.execute(method, args, {asking: asking, random_node: try_random_node}, &block)
71
+ end
72
+ rescue Redis::CommandError => e
73
+ unless @logger.nil?
74
+ @logger.error("redis_cluster: Received error: #{e}")
75
+ end
76
+
77
+ # This is a special condition to protect against a misbehaving library
78
+ # or server. After we've gotten one ASK or MOVED and retried once,
79
+ # we'll never do so a second time. Receiving two of any operations in a
80
+ # row is probably indicative of a problem and we don't want to get
81
+ # stuck in an infinite retry loop.
82
+ raise if retried
83
+ retried = true
84
+
85
+ err_code = e.to_s.split.first
86
+ case err_code
87
+ when 'ASK'
88
+ unless @logger.nil?
89
+ @logger.info("redis_cluster: Received ASK; retrying operation (#{e})")
90
+ end
57
91
 
58
- err_code = e.to_s.split.first
59
- raise e unless %w(MOVED ASK).include?(err_code)
92
+ asking = true
93
+ retry
60
94
 
61
- if err_code == 'ASK'
62
- asking = true
63
- else
64
- # `MOVED` indicates a permanent redirect which means that our slot
65
- # mappings are stale: reload them.
66
- reload_pool_nodes(false)
95
+ when 'MOVED'
96
+ unless @logger.nil?
97
+ @logger.info("redis_cluster: Received MOVED; retrying operation (#{e})")
67
98
  end
99
+
100
+ # `MOVED` indicates a permanent redirect which means that our slot
101
+ # mappings are stale: reload them then try what we were doing again
102
+ reload_pool_nodes
103
+ retry
104
+
105
+ else
106
+ raise
68
107
  end
69
108
  end
70
-
71
- # If we ran out of retries (the maximum number may have been set to 0),
72
- # surface any error that was thrown back to the caller. We'd otherwise
73
- # suppress the error, which would return something quite unexpected.
74
- raise last_error
75
109
  end
76
110
 
77
111
  Configuration.method_names.each do |method_name|
@@ -84,13 +118,31 @@ module RedisCluster
84
118
  execute(method, args, &block)
85
119
  end
86
120
 
121
+ # Closes all open connections and reloads the client pool.
122
+ #
123
+ # Normally host information from the last time the node pool was reloaded
124
+ # is used, but if the `use_initial_hosts` is set to `true`, then the client
125
+ # is completely refreshed and the hosts that were specified when creating
126
+ # it originally are set instead.
127
+ def reconnect(options = {})
128
+ use_initial_hosts = options.fetch(:use_initial_hosts, false)
129
+
130
+ @hosts = @initial_hosts.dup if use_initial_hosts
131
+
132
+ @mutex.synchronize do
133
+ @pool.nodes.each{|node| node.connection.close}
134
+ @pool.nodes.clear
135
+ reload_pool_nodes_unsync
136
+ end
137
+ end
138
+
87
139
  private
88
140
 
89
141
  # Adds only a single node to the client pool and sets it result for the
90
142
  # entire space of slots. This is useful when running either a standalone
91
143
  # Redis or a single-node Redis Cluster.
92
144
  def create_single_node_pool
93
- host = @startup_hosts
145
+ host = @hosts
94
146
  if host.is_a?(Array)
95
147
  if host.length > 1
96
148
  raise ArgumentError, "Can only create single node pool for single host"
@@ -102,15 +154,22 @@ module RedisCluster
102
154
  end
103
155
 
104
156
  @pool.add_node!(host, [(0..Configuration::HASH_SLOTS)])
157
+
158
+ unless @logger.nil?
159
+ @logger.info("redis_cluster: Initialized single node pool: #{host}")
160
+ end
105
161
  end
106
162
 
107
- def create_multi_node_pool(raise_error)
108
- unless @startup_hosts.is_a?(Array)
163
+ def create_multi_node_pool
164
+ unless @hosts.is_a?(Array)
109
165
  raise ArgumentError, "Can only create multi-node pool for multiple hosts"
110
166
  end
111
167
 
112
- @startup_hosts.each do |options|
113
- begin
168
+ begin
169
+ retry_intermittent_loop do |attempt|
170
+ # Try a random host from our seed pool.
171
+ options = @hosts.sample
172
+
114
173
  redis = Node.redis(@pool.global_configs.merge(options))
115
174
  slots_mapping = redis.cluster("slots").group_by{|x| x[2]}
116
175
  @pool.delete_except!(slots_mapping.keys)
@@ -118,32 +177,27 @@ module RedisCluster
118
177
  slots_ranges = infos.map {|x| x[0]..x[1] }
119
178
  @pool.add_node!({host: host[0], port: host[1]}, slots_ranges)
120
179
  end
121
- rescue Redis::CommandError => e
122
- if e.message =~ /cluster\ support\ disabled$/
123
- if !@force_cluster
124
- # We're running outside of cluster-mode -- just create a
125
- # single-node pool and move on. The exception is if we've been
126
- # asked for force Redis Cluster, in which case we assume this is
127
- # a configuration problem and maybe raise an error.
128
- create_single_node_pool
129
- return
130
- elsif raise_error
131
- raise e
132
- end
133
- end
134
-
135
- raise e if e.message =~ /NOAUTH\ Authentication\ required/
180
+ end
181
+ rescue Redis::CommandError => e
182
+ unless @logger.nil?
183
+ @logger.error("redis_cluster: Received error: #{e}")
184
+ end
136
185
 
137
- # TODO: log error for visibility
138
- next
139
- rescue
140
- # TODO: log error for visibility
141
- next
186
+ if e.message =~ /cluster\ support\ disabled$/ && !@force_cluster
187
+ # We're running outside of cluster-mode -- just create a single-node
188
+ # pool and move on. The exception is if we've been asked for force
189
+ # Redis Cluster, in which case we assume this is a configuration
190
+ # problem and maybe raise an error.
191
+ create_single_node_pool
192
+ return
142
193
  end
143
194
 
144
- # We only need to see a `CLUSTER SLOTS` result from a single host, so
145
- # break after one success.
146
- break
195
+ raise
196
+ end
197
+
198
+ unless @logger.nil?
199
+ mappings = @pool.nodes.map{|node| "#{node.slots} -> #{node.options}"}
200
+ @logger.info("redis_cluster: Initialized multi-node pool: #{mappings}")
147
201
  end
148
202
  end
149
203
 
@@ -151,23 +205,56 @@ module RedisCluster
151
205
  # SLOTS` or just adding a node directly if running on standalone. Clients
152
206
  # are "upserted" so that we don't necessarily drop clients that are still
153
207
  # relevant.
154
- def reload_pool_nodes(raise_error)
208
+ def reload_pool_nodes
155
209
  @mutex.synchronize do
156
- if @startup_hosts.is_a?(Array)
157
- create_multi_node_pool(raise_error)
158
- refresh_startup_nodes
159
- else
160
- create_single_node_pool
161
- end
210
+ reload_pool_nodes_unsync
162
211
  end
163
212
  end
164
213
 
165
- # Refreshes the contents of @startup_hosts based on the hosts currently in
214
+ # The same as `#reload_pool_nodes`, but doesn't attempt to synchronize on
215
+ # the mutex. Use this only if you've already got a lock on it.
216
+ def reload_pool_nodes_unsync
217
+ if @hosts.is_a?(Array)
218
+ create_multi_node_pool
219
+ refresh_startup_nodes
220
+ else
221
+ create_single_node_pool
222
+ end
223
+ end
224
+
225
+ # Refreshes the contents of @hosts based on the hosts currently in
166
226
  # the client pool. This is useful because we may have been told about new
167
227
  # hosts after running `CLUSTER SLOTS`.
168
228
  def refresh_startup_nodes
169
- @pool.nodes.each {|node| @startup_hosts.push(node.host_hash) }
170
- @startup_hosts.uniq!
229
+ @pool.nodes.each {|node| @hosts.push(node.host_hash) }
230
+ @hosts.uniq!
231
+ end
232
+
233
+ # Retries an operation @retry_count times for intermittent connection
234
+ # errors. After exhausting retries, the error that was received on the last
235
+ # attempt is raised to the user.
236
+ def retry_intermittent_loop
237
+ last_error = nil
238
+
239
+ for attempt in 0..(@retry_count) do
240
+ begin
241
+ yield(attempt)
242
+
243
+ # Fall through on any success.
244
+ return
245
+ rescue Errno::EACCES, Redis::TimeoutError, Redis::CannotConnectError => e
246
+ last_error = e
247
+
248
+ unless @logger.nil?
249
+ @logger.error("redis_cluster: Received error: #{e} retries_left=#{@retry_count - attempt}")
250
+ end
251
+ end
252
+ end
253
+
254
+ # If we ran out of retries (the maximum number may have been set to 0),
255
+ # surface any error that was thrown back to the caller. We'd otherwise
256
+ # suppress the error, which would return something quite unexpected.
257
+ raise last_error
171
258
  end
172
259
 
173
260
  end # end client
@@ -1,6 +1,8 @@
1
1
  module RedisCluster
2
2
 
3
3
  class Node
4
+ attr_accessor :options
5
+
4
6
  # slots is a range array: [1..100, 300..500]
5
7
  attr_accessor :slots
6
8
 
@@ -35,7 +37,7 @@ module RedisCluster
35
37
  end
36
38
 
37
39
  def connection
38
- @connection ||= self.class.redis(@options)
40
+ @connection ||= self.class.redis(options)
39
41
  end
40
42
 
41
43
  def self.redis(options)
@@ -1,3 +1,3 @@
1
1
  module RedisCluster
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis_cluster
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - wangzc
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-02-16 00:00:00.000000000 Z
11
+ date: 2018-03-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -142,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
142
142
  version: '0'
143
143
  requirements: []
144
144
  rubyforge_project:
145
- rubygems_version: 2.6.13
145
+ rubygems_version: 2.6.10
146
146
  signing_key:
147
147
  specification_version: 4
148
148
  summary: redis cluster client