resque_redis_composite 1.0.2
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.
- data/.gitignore +2 -0
- data/Gemfile +3 -0
- data/README.md +63 -0
- data/lib/resque/redis_composite.rb +116 -0
- data/lib/resque_redis_composite.rb +3 -0
- data/resque_redis_composite.gemspec +16 -0
- data/spec/resque/redis_composite_spec.rb +118 -0
- metadata +117 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
resque_redis_composite
|
2
|
+
======================
|
3
|
+
|
4
|
+
This gems allows Resque to handle queues located on seperate Redis servers.
|
5
|
+
|
6
|
+
Requirements
|
7
|
+
------------
|
8
|
+
|
9
|
+
Currently working with Resque 1.23.x and 1.24.x
|
10
|
+
I haven't looked into Resque 2 yet, but it might be possible to natively handle multiple Redis instances with the new `Resque::Backend` class.
|
11
|
+
|
12
|
+
Install & Setup
|
13
|
+
---------------
|
14
|
+
|
15
|
+
Configure Resque in your application:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
Resque.after_fork do |job|
|
19
|
+
Resque::RedisComposite.reconnect_all(job)
|
20
|
+
end
|
21
|
+
```
|
22
|
+
|
23
|
+
Usage
|
24
|
+
-----
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
config = {
|
28
|
+
"default" => "localhost:6379",
|
29
|
+
"some_other_queue" => "otherbox:6379"
|
30
|
+
}
|
31
|
+
|
32
|
+
Resque.redis = Resque::RedisComposite.create(config)
|
33
|
+
```
|
34
|
+
|
35
|
+
Alternatively, `config` can be one of :
|
36
|
+
|
37
|
+
* a single connection string, it will become the default connection:
|
38
|
+
|
39
|
+
`config = "localhost:6379"`
|
40
|
+
|
41
|
+
* a hash with any combination of server values supported by Resque:
|
42
|
+
* a Redis connection url : `redis://...`
|
43
|
+
* a Redis client : `Redis.new(:host => ..., :port => ...)`
|
44
|
+
* a Redis namespace : `Redis::Namespace(..., :redis => ...)`
|
45
|
+
|
46
|
+
Notes
|
47
|
+
-----
|
48
|
+
|
49
|
+
Resque Stats will always be stored on the `default` Redis server.
|
50
|
+
|
51
|
+
TODO
|
52
|
+
----
|
53
|
+
|
54
|
+
* Keep an eye out for Resque 2.0 and evalute if this gems is still going to be useful
|
55
|
+
* Review specs
|
56
|
+
|
57
|
+
Authors
|
58
|
+
-------
|
59
|
+
|
60
|
+
Original work done by [Dave Hoover](https://github.com/redsquirrel) and [jiHyunBae](https://github.com/jiHyunBae).
|
61
|
+
|
62
|
+
Updated and made into a gem by [Anthony Powles](https://github.com/yogin).
|
63
|
+
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Resque
|
2
|
+
class RedisComposite
|
3
|
+
class NoDefaultRedisServerError < StandardError; end
|
4
|
+
|
5
|
+
DEFAULT_SERVER_NAME = 'default'
|
6
|
+
|
7
|
+
attr_reader :mapping
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def create(config)
|
12
|
+
Redis::Namespace.new(nil, :redis => RedisComposite.new(config))
|
13
|
+
end
|
14
|
+
|
15
|
+
def reconnect_all(job = nil)
|
16
|
+
return unless Resque.redis.redis.kind_of?(Resque::RedisComposite)
|
17
|
+
Resque.redis.mapping.each { |_, server| server.redis.client.reconnect }
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(config)
|
23
|
+
config = { DEFAULT_SERVER_NAME => config } unless config.kind_of?(Hash)
|
24
|
+
|
25
|
+
config = HashWithIndifferentAccess.new(config)
|
26
|
+
raise NoDefaultRedisServerError, "No default server defined in configuration : #{config.inspect}" unless config.key?(DEFAULT_SERVER_NAME)
|
27
|
+
|
28
|
+
# quick backup for what's in Resque
|
29
|
+
original_resque_server = Resque.redis
|
30
|
+
|
31
|
+
@mapping = config.inject(HashWithIndifferentAccess.new) do |hash, (queue_name, server)|
|
32
|
+
# leaving Resque create Redis::Namespace instances
|
33
|
+
Resque.redis = server
|
34
|
+
|
35
|
+
hash[queue_name] = Resque.redis
|
36
|
+
hash
|
37
|
+
end
|
38
|
+
|
39
|
+
Resque.redis = original_resque_server
|
40
|
+
end
|
41
|
+
|
42
|
+
def method_missing(method_name, *args, &block)
|
43
|
+
# most redis command's first parameter is the key, so it should work most of the time
|
44
|
+
server_for(args.first).send(method_name, *args, &block)
|
45
|
+
end
|
46
|
+
|
47
|
+
def client(queue = DEFAULT_SERVER_NAME)
|
48
|
+
# TODO properly handle Resque.redis_id, probably need to return all our client ids
|
49
|
+
server_for(queue).client
|
50
|
+
end
|
51
|
+
|
52
|
+
# This is used to create a set of queue names, so needs some special treatment
|
53
|
+
def sadd(key, value)
|
54
|
+
if queues?(key)
|
55
|
+
server_for(value).sadd(key, value)
|
56
|
+
else
|
57
|
+
server_for(key).sadd(key, value)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# If we're using smembers to get queue names, we aggregate across all servers
|
62
|
+
def smembers(key)
|
63
|
+
if queues?(key)
|
64
|
+
servers.inject([]) { |a, s| a + s.smembers(key) }.uniq
|
65
|
+
else
|
66
|
+
server_for(key).smembers(key)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# This is used to completely delete a queue, so needs some special treatment
|
71
|
+
def srem(key, value)
|
72
|
+
if queues?(key)
|
73
|
+
server_for(value).srem(key, value)
|
74
|
+
else
|
75
|
+
server_for(key).srem(key, value)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Sometimes we're pushing onto the 'failed' queue, and we want to make sure
|
80
|
+
# the failures are pushed into the same Redis server as the queue is hosted on.
|
81
|
+
def rpush(key, value)
|
82
|
+
if failed?(key)
|
83
|
+
queue_with_failure = Resque.decode(value)["queue"]
|
84
|
+
server_for(queue_with_failure).rpush(key, value)
|
85
|
+
else
|
86
|
+
server_for(key).rpush(key, value)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
def servers
|
93
|
+
@mapping.values
|
94
|
+
end
|
95
|
+
|
96
|
+
def default_server
|
97
|
+
@mapping[DEFAULT_SERVER_NAME]
|
98
|
+
end
|
99
|
+
|
100
|
+
def server_for(queue)
|
101
|
+
# queue_name = queue.to_s.sub(/^queue:/, "")
|
102
|
+
# queue parsing : not match regular expression
|
103
|
+
queue_name = queue.to_s.sub(/[\W\w]*queue:/,"")
|
104
|
+
@mapping[queue_name] || default_server
|
105
|
+
end
|
106
|
+
|
107
|
+
def queues?(key)
|
108
|
+
key.to_s == "queues"
|
109
|
+
end
|
110
|
+
|
111
|
+
def failed?(key)
|
112
|
+
key.to_s == "failed"
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "resque_redis_composite"
|
3
|
+
s.description = "Allowing Resque to handle queues accross multiple Redis instances"
|
4
|
+
s.version = "1.0.2"
|
5
|
+
s.authors = [ "redsquirrel", "jiHyunBae", "Anthony Powles" ]
|
6
|
+
s.email = "rubygems+resque_redis_composite@idreamz.net"
|
7
|
+
s.summary = "Resque support for multiple Redis instances"
|
8
|
+
s.homepage = "https://github.com/yogin/resque_redis_composite"
|
9
|
+
s.files = `git ls-files`.split($/)
|
10
|
+
|
11
|
+
s.add_development_dependency "bundler", "~> 1.3"
|
12
|
+
s.add_development_dependency "rake"
|
13
|
+
s.add_development_dependency "pry"
|
14
|
+
|
15
|
+
s.add_dependency "resque"
|
16
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rspec'
|
3
|
+
require 'resque'
|
4
|
+
|
5
|
+
$:.unshift "#{File.dirname(__FILE__)}/../lib"
|
6
|
+
require 'resque/redis_composite'
|
7
|
+
|
8
|
+
describe Resque::RedisComposite do
|
9
|
+
describe "initialize" do
|
10
|
+
|
11
|
+
it "accepts a redis client" do
|
12
|
+
Resque.redis = Redis.new(:host => 'localhost', :port => 5380)
|
13
|
+
Resque.redis.should be_a(Redis::Namespace)
|
14
|
+
Resque.redis.client.port.should == 5380
|
15
|
+
end
|
16
|
+
|
17
|
+
it "accepts redis composite with a connection string" do
|
18
|
+
Resque.redis = Resque::RedisComposite.new("localhost:5380")
|
19
|
+
Resque.redis.client.port.should == 5380
|
20
|
+
end
|
21
|
+
|
22
|
+
it "accepts redis composite with a hash of connection strings" do
|
23
|
+
Resque.redis = Resque::RedisComposite.new("default" => "localhost:5380", "test" => "localhost:5381")
|
24
|
+
Resque.redis.client.port.should == 5380
|
25
|
+
Resque.redis.client("test").port.should == 5381
|
26
|
+
end
|
27
|
+
|
28
|
+
it "raises exception Resque::RedisComposite::MissingDefaultRedisInstance if there are no default instance" do
|
29
|
+
expect {
|
30
|
+
Resque::RedisComposite.new("foo" => "localhost:5380", "bar" => "localhost:5381")
|
31
|
+
}.to raise_error(Resque::RedisComposite::MissingDefaultRedisInstance)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "redis interface" do
|
36
|
+
# before do
|
37
|
+
#let(:default_instance) { mock("default") }
|
38
|
+
#let(:special_instance) { mock("special") }
|
39
|
+
#end
|
40
|
+
|
41
|
+
#it "properly namespaces the resque queues" do
|
42
|
+
#special_instance.should_receive(:sadd).with("resque:queues", "stuff")
|
43
|
+
#special_instance.should_receive(:rpush).with("resque:queue:stuff", '{"class":"SomeClass","args":"do something"}')
|
44
|
+
|
45
|
+
#Resque.redis = Resque::RedisComposite.new("default" => default_instance, "stuff" => special_instance)
|
46
|
+
#Resque.push("stuff", :class => "SomeClass", :args => "do something")
|
47
|
+
#end
|
48
|
+
|
49
|
+
#it "delegates most calls to the default server" do
|
50
|
+
#@default_server.should_receive(:srem)
|
51
|
+
#@default_server.should_receive(:get)
|
52
|
+
|
53
|
+
#redis = Resque::RedisComposite.new("default" => @default_server, "stuff" => @special_server)
|
54
|
+
#redis.srem(:workers, self)
|
55
|
+
#redis.get(:workers)
|
56
|
+
#end
|
57
|
+
|
58
|
+
#describe "Resque.push" do
|
59
|
+
#it "pushes jobs onto the specific Redis server" do
|
60
|
+
#@special_server.should_receive(:sadd)
|
61
|
+
#@special_server.should_receive(:rpush)
|
62
|
+
|
63
|
+
#Resque.redis = Resque::RedisComposite.new("default" => @default_server, "stuff" => @special_server)
|
64
|
+
#Resque.push("stuff", :class => "SomeClass", :args => "do something")
|
65
|
+
#end
|
66
|
+
|
67
|
+
#it "pushes jobs onto the default Redis server" do
|
68
|
+
#@default_server.should_receive(:sadd)
|
69
|
+
#@default_server.should_receive(:rpush)
|
70
|
+
|
71
|
+
#Resque.redis = Resque::RedisComposite.new("default" => @default_server, "stuff" => @special_server)
|
72
|
+
#Resque.push("email", :class => "SomeOtherClass", :args => "do something else")
|
73
|
+
#end
|
74
|
+
#end
|
75
|
+
|
76
|
+
#describe "Resque.pop" do
|
77
|
+
#it "pops jobs off the specific Redis server" do
|
78
|
+
#@special_server.should_receive(:lpop)
|
79
|
+
|
80
|
+
#Resque.redis = Resque::RedisComposite.new("default" => @default_server, "stuff" => @special_server)
|
81
|
+
#Resque.pop("stuff")
|
82
|
+
#end
|
83
|
+
|
84
|
+
#it "pops jobs off the default Redis server" do
|
85
|
+
#@default_server.should_receive(:lpop)
|
86
|
+
|
87
|
+
#Resque.redis = Resque::RedisComposite.new("default" => @default_server, "stuff" => @special_server)
|
88
|
+
#Resque.pop("email")
|
89
|
+
#end
|
90
|
+
#end
|
91
|
+
|
92
|
+
#describe "Resque.queues" do
|
93
|
+
#it "aggregates all the queues" do
|
94
|
+
#@default_server.should_receive(:smembers).with("resque:queues").and_return(%w(a b c d))
|
95
|
+
#@special_server.should_receive(:smembers).with("resque:queues").and_return(%w(d e f))
|
96
|
+
|
97
|
+
#Resque.redis = Resque::RedisComposite.new("default" => @default_server, "stuff" => @special_server)
|
98
|
+
#Resque.queues.sort.should == %w(a b c d e f)
|
99
|
+
#end
|
100
|
+
#end
|
101
|
+
|
102
|
+
#describe "Resque::Failure.create" do
|
103
|
+
#it "pushes the failure onto the specific Redis server" do
|
104
|
+
#@special_server.should_receive(:rpush)
|
105
|
+
|
106
|
+
#Resque.redis = Resque::RedisComposite.new("default" => @default_server, "stuff" => @special_server)
|
107
|
+
#Resque::Failure.create(:queue => "stuff", :exception => StandardError.new)
|
108
|
+
#end
|
109
|
+
|
110
|
+
#it "pushes the failure onto the default Redis server" do
|
111
|
+
#@default_server.should_receive(:rpush)
|
112
|
+
|
113
|
+
#Resque.redis = Resque::RedisComposite.new("default" => @default_server, "stuff" => @special_server)
|
114
|
+
#Resque::Failure.create(:queue => "transactions", :exception => StandardError.new)
|
115
|
+
#end
|
116
|
+
#end
|
117
|
+
end
|
118
|
+
end
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: resque_redis_composite
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- redsquirrel
|
9
|
+
- jiHyunBae
|
10
|
+
- Anthony Powles
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2013-07-11 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: bundler
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '1.3'
|
24
|
+
type: :development
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ~>
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '1.3'
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: rake
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: pry
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: resque
|
66
|
+
requirement: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ! '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
type: :runtime
|
73
|
+
prerelease: false
|
74
|
+
version_requirements: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
description: Allowing Resque to handle queues accross multiple Redis instances
|
81
|
+
email: rubygems+resque_redis_composite@idreamz.net
|
82
|
+
executables: []
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- Gemfile
|
88
|
+
- README.md
|
89
|
+
- lib/resque/redis_composite.rb
|
90
|
+
- lib/resque_redis_composite.rb
|
91
|
+
- resque_redis_composite.gemspec
|
92
|
+
- spec/resque/redis_composite_spec.rb
|
93
|
+
homepage: https://github.com/yogin/resque_redis_composite
|
94
|
+
licenses: []
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 1.8.23
|
114
|
+
signing_key:
|
115
|
+
specification_version: 3
|
116
|
+
summary: Resque support for multiple Redis instances
|
117
|
+
test_files: []
|