resque_redis_composite 1.0.2

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