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.
@@ -0,0 +1,2 @@
1
+ *.gem
2
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -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,3 @@
1
+ require 'resque'
2
+ require 'resque/redis_composite'
3
+
@@ -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: []