resque_redis_composite 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|