sidekiq-bus 0.5.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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rbenv-version +1 -0
- data/.rspec +1 -0
- data/Gemfile +5 -0
- data/MIT-LICENSE +20 -0
- data/README.mdown +264 -0
- data/Rakefile +2 -0
- data/lib/sidekiq-bus.rb +9 -0
- data/lib/sidekiq_bus/adapter.rb +28 -0
- data/lib/sidekiq_bus/tasks.rb +93 -0
- data/lib/sidekiq_bus/version.rb +3 -0
- data/sidekiq-bus.gemspec +27 -0
- data/spec/adapter/support.rb +14 -0
- data/spec/adapter_spec.rb +14 -0
- data/spec/application_spec.rb +152 -0
- data/spec/config_spec.rb +83 -0
- data/spec/dispatch_spec.rb +76 -0
- data/spec/driver_spec.rb +109 -0
- data/spec/heartbeat_spec.rb +44 -0
- data/spec/integration_spec.rb +53 -0
- data/spec/matcher_spec.rb +143 -0
- data/spec/publish_spec.rb +98 -0
- data/spec/publisher_spec.rb +7 -0
- data/spec/rider_spec.rb +39 -0
- data/spec/spec_helper.rb +69 -0
- data/spec/subscriber_spec.rb +270 -0
- data/spec/subscription_list_spec.rb +43 -0
- data/spec/subscription_spec.rb +53 -0
- data/spec/worker_spec.rb +32 -0
- metadata +167 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8c073296475ba07b08f7f92b019d091b38b4038e
|
4
|
+
data.tar.gz: cb95f95a5ec099cc471dcd8695cbf2ec478a9b6a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 846ac47ae7600da4249d89e55e81e9e16f4de85e768697017f343b5f3952683232b29975055feddf2dcea83dc15a48e25c1e2f6d4ceee452b94f9bf32d4d590f
|
7
|
+
data.tar.gz: a5a9156736395bf84e9acbebbd533ce27a71436d74bc10923c893e06cf07c734e9756ddc2e42bfd64822823e322491a57b5cdb4facacbb71a7c09b17135f1715
|
data/.gitignore
ADDED
data/.rbenv-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.5
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Brian Leonard
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.mdown
ADDED
@@ -0,0 +1,264 @@
|
|
1
|
+
## Resque Bus
|
2
|
+
|
3
|
+
This gem uses Redis and Resque to allow simple asynchronous communication between apps.
|
4
|
+
|
5
|
+
### Install
|
6
|
+
|
7
|
+
To install, include the 'resque-bus' gem and add the following to your Rakefile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require "sidekiq_bus/tasks"
|
11
|
+
```
|
12
|
+
|
13
|
+
### Example
|
14
|
+
|
15
|
+
Application A can publish an event
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
# config
|
19
|
+
Resque.redis = "192.168.1.1:6379"
|
20
|
+
|
21
|
+
# business logic
|
22
|
+
ResqueBus.publish("user_created", "id" => 42, "first_name" => "John", "last_name" => "Smith")
|
23
|
+
|
24
|
+
# or do it later
|
25
|
+
ResqueBus.publish_at(1.hour.from_now, "user_created", "id" => 42, "first_name" => "John", "last_name" => "Smith")
|
26
|
+
```
|
27
|
+
|
28
|
+
Application B is subscribed to events
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
# config
|
32
|
+
Resque.redis = "192.168.1.1:6379"
|
33
|
+
|
34
|
+
# initializer
|
35
|
+
ResqueBus.dispatch("app_b") do
|
36
|
+
# processes event on app_b_default queue
|
37
|
+
# subscribe is short-hand to subscribe to your 'default' queue and this block with process events with the name "user_created"
|
38
|
+
subscribe "user_created" do |attributes|
|
39
|
+
NameCount.find_or_create_by_name(attributes["last_name"]).increment!
|
40
|
+
end
|
41
|
+
|
42
|
+
# processes event on app_b_critical queue
|
43
|
+
# critical is short-hand to subscribe to your 'critical' queue and this block with process events with the name "user_paid"
|
44
|
+
critical "user_paid" do |attributes|
|
45
|
+
CreditCard.charge!(attributes)
|
46
|
+
end
|
47
|
+
|
48
|
+
# you can pass any queue name you would like to process from as well IE: `banana "peeled" do |attributes|`
|
49
|
+
|
50
|
+
# and regexes work as well. note that with the above configuration along with this regex,
|
51
|
+
# the following as well as the corresponding block above would both be executed
|
52
|
+
subscribe /^user_/ do |attributes|
|
53
|
+
Metrics.record_user_action(attributes["bus_event_type"], attributes["id"])
|
54
|
+
end
|
55
|
+
|
56
|
+
# the above all filter on just the event_type, but you can filter on anything
|
57
|
+
# this would be _any_ event that has a user_id and the page value of homepage regardless of bus_event_type
|
58
|
+
subscribe "my_key", { "user_id" => :present, "page" => "homepage"} do
|
59
|
+
Mixpanel.homepage_action!(attributes["action"])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
Applications can also subscribe within classes using the provided `Subscriber` module.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
class SimpleSubscriber
|
68
|
+
include ResqueBus::Subscriber
|
69
|
+
subscribe :my_method
|
70
|
+
|
71
|
+
def my_method(attributes)
|
72
|
+
# heavy lifting
|
73
|
+
end
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
The following is equivalent to the original initializer and shows more options:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
class OtherSubscriber
|
81
|
+
include ResqueBus::Subscriber
|
82
|
+
application :app_b
|
83
|
+
|
84
|
+
subscribe :user_created
|
85
|
+
subscribe_queue :app_b_critical, :user_paid
|
86
|
+
subscribe_queue :app_b_default, :user_action, :bus_event_type => /^user_/
|
87
|
+
subscribe :homepage_method, :user_id => :present, :page => "homepage"
|
88
|
+
|
89
|
+
def user_created(attributes)
|
90
|
+
NameCount.find_or_create_by_name(attributes["last_name"]).increment!
|
91
|
+
end
|
92
|
+
|
93
|
+
def user_paid(attributes)
|
94
|
+
CreditCard.charge!(attributes)
|
95
|
+
end
|
96
|
+
|
97
|
+
def user_action(attributes)
|
98
|
+
Metrics.record_user_action(attributes["bus_event_type"], attributes["id"])
|
99
|
+
end
|
100
|
+
|
101
|
+
def homepage_method
|
102
|
+
Mixpanel.homepage_action!(attributes["action"])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
Note: This subscribes when this class is loaded, so it needs to be in your load or otherwise referenced/required during app initialization to work properly.
|
108
|
+
|
109
|
+
### Commands
|
110
|
+
|
111
|
+
Each app needs to tell Redis about its subscriptions:
|
112
|
+
|
113
|
+
$ rake resquebus:subscribe
|
114
|
+
|
115
|
+
The subscription block is run inside a Resque worker which needs to be started for each app.
|
116
|
+
|
117
|
+
$ rake resquebus:setup resque:work
|
118
|
+
|
119
|
+
The incoming queue also needs to be processed on a dedicated or all the app servers.
|
120
|
+
|
121
|
+
$ rake resquebus:driver resque:work
|
122
|
+
|
123
|
+
If you want retry to work for subscribing apps, you should run resque-scheduler
|
124
|
+
|
125
|
+
$ rake resque:scheduler
|
126
|
+
|
127
|
+
### Adapters
|
128
|
+
|
129
|
+
ResqueBus now supports multiple adapters! By default ResqueBus uses Resque but you can now configure your application to use Sidekiq to drive and subscribe the bus.
|
130
|
+
|
131
|
+
First be sure to configure ResqueBus to use Sidekiq early in your applications' initialization cycle:
|
132
|
+
```
|
133
|
+
ResqueBus.adapter = 'Sidekiq'
|
134
|
+
```
|
135
|
+
You will be responsible for setting up the queues for your Sidekiq clients however you can get the appropriate queue names with the following tasks:
|
136
|
+
For driving applications:
|
137
|
+
```
|
138
|
+
$ rake resquebus:driver:sidekiq
|
139
|
+
```
|
140
|
+
For subscribing applications:
|
141
|
+
```
|
142
|
+
$ rake resquebus:setup:sidekiq
|
143
|
+
```
|
144
|
+
These tasks will provide the queue_names and some minimal suggestions for starting the client.
|
145
|
+
|
146
|
+
Your subscribing applications will still need to also use the appropriate rake task:
|
147
|
+
```
|
148
|
+
$ rake resquebus:subscribe:sidekiq
|
149
|
+
```
|
150
|
+
|
151
|
+
At the moment you are expected to include the Sidekiq gem in your own applications.
|
152
|
+
|
153
|
+
And yes we are planning on renaming and restructuring the project! Please contact the maintainer if you would like to add a different adapter.
|
154
|
+
|
155
|
+
### Heartbeat
|
156
|
+
|
157
|
+
We've found it useful to have the bus act like `cron`, triggering timed jobs throughout the system. Resque Bus calls this a heartbeat.
|
158
|
+
It uses resque-scheduler to trigger the events. You can enable it in your Rakefile.
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
# resque.rake
|
162
|
+
namespace :resque do
|
163
|
+
task :setup => [:environment] do
|
164
|
+
ResqueBus.heartbeat!
|
165
|
+
end
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
Or add it to your `schedule.yml` directly
|
170
|
+
|
171
|
+
```yaml
|
172
|
+
resquebus_heartbeat:
|
173
|
+
cron: "* * * * *"
|
174
|
+
class: "::ResqueBus::Heartbeat"
|
175
|
+
queue: resquebus_incoming
|
176
|
+
description: "I publish a heartbeat_minutes event every minute"
|
177
|
+
```
|
178
|
+
|
179
|
+
It is the equivalent of doing this every minute
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
seconds = minutes * (60)
|
183
|
+
hours = minutes / (60)
|
184
|
+
days = minutes / (60*24)
|
185
|
+
|
186
|
+
now = Time.at(seconds)
|
187
|
+
|
188
|
+
attributes = {}
|
189
|
+
|
190
|
+
now = Time.now
|
191
|
+
seconds = now.to_i
|
192
|
+
ResqueBus.publish("hearbeat_minutes", {
|
193
|
+
"epoch_seconds" => seconds,
|
194
|
+
"epoch_minutes" => seconds / 1.minute,
|
195
|
+
"epoch_hours" => seconds / 1.hour,
|
196
|
+
"epoch_days" => seconds / 1.day,
|
197
|
+
"minute" => now.min
|
198
|
+
"hour" => now.hour
|
199
|
+
"day" => now.day
|
200
|
+
"month" => now.month
|
201
|
+
"year" => now.year
|
202
|
+
"yday" => now.yday
|
203
|
+
"wday" => now.wday
|
204
|
+
})
|
205
|
+
```
|
206
|
+
|
207
|
+
This allows you do something like this:
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
ResqueBus.dispatch("app_c") do
|
211
|
+
# runs at 10:20, 11:20, etc
|
212
|
+
subscribe "once_an_hour", 'bus_event_type' => 'heartbeat_minutes', 'minute' => 20 do |attributes|
|
213
|
+
Sitemap.generate!
|
214
|
+
end
|
215
|
+
|
216
|
+
# runs every five minutes
|
217
|
+
subscribe "every_five_minutes", 'bus_event_type' => 'heartbeat_minutes' do |attributes|
|
218
|
+
next unless attributes["epoch_minutes"] % 5 == 0
|
219
|
+
HealthCheck.run!
|
220
|
+
end
|
221
|
+
|
222
|
+
# runs at 8am on the first of every month
|
223
|
+
subscribe "new_month_morning", 'bus_event_type' => 'heartbeat_minutes', 'day' => 1, hour' => 8, 'minute' => 0, do |attributes|
|
224
|
+
next unless attributes["epoch_minutes"] % 5 == 0
|
225
|
+
Token.old.expire!
|
226
|
+
end
|
227
|
+
end
|
228
|
+
```
|
229
|
+
|
230
|
+
### Local Mode
|
231
|
+
|
232
|
+
For development, a local mode is provided and is specified in the configuration.
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
# config
|
236
|
+
ResqueBus.local_mode = :standalone
|
237
|
+
or
|
238
|
+
ResqueBus.local_mode = :inline
|
239
|
+
```
|
240
|
+
|
241
|
+
Standalone mode does not require a separate resquebus:driver task to be running to process the
|
242
|
+
incoming queue. Simply publishing to the bus will distribute the incoming events
|
243
|
+
to the appropriate application specific queue. A separate resquebus:work task does
|
244
|
+
still need to be run to process these events
|
245
|
+
|
246
|
+
Inline mode skips queue processing entirely and directly dispatches the
|
247
|
+
event to the appropriate code block.
|
248
|
+
|
249
|
+
You can also say `ResqueBus.local_mode = :suppress` to turn off publishing altogether.
|
250
|
+
This can be helpful inside some sort of migration, for example.
|
251
|
+
|
252
|
+
### TODO
|
253
|
+
|
254
|
+
* Sidekiq adapter
|
255
|
+
* Refactor rake tasks for resque/sidekiq
|
256
|
+
* Refactor to a storage adapter for Redis, so we can store subscription info in MySQL or something else
|
257
|
+
* Replace local modes with adapters
|
258
|
+
* There are a few spots in the code with TODO notes
|
259
|
+
* Make this not freak out in development without Redis or when Redis is down
|
260
|
+
* We might not actually need to publish in tests
|
261
|
+
* Add some rspec helpers for the apps to use: should_ post an event_publish or something along those lines
|
262
|
+
* Allow calling resquebus:setup and resquebus:driver together (append to ENV['QUEUES'], don't replace it)
|
263
|
+
|
264
|
+
Copyright (c) 2011 Brian Leonard, released under the MIT license
|
data/Rakefile
ADDED
data/lib/sidekiq-bus.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module QueueBus
|
2
|
+
module Adapters
|
3
|
+
class Sidekiq < QueueBus::Adapters::Base
|
4
|
+
def enabled!
|
5
|
+
# know we are using it
|
6
|
+
require 'sidekiq'
|
7
|
+
::QueueBus::Worker.include ::Sidekiq::Worker
|
8
|
+
end
|
9
|
+
|
10
|
+
def redis(&block)
|
11
|
+
::Sidekiq.redis(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def enqueue(queue_name, klass, hash)
|
15
|
+
::Sidekiq::Client.push('queue' => queue_name, 'class' => klass, 'args' => [hash])
|
16
|
+
end
|
17
|
+
|
18
|
+
def enqueue_at(epoch_seconds, queue_name, klass, hash)
|
19
|
+
::Sidekiq::Client.push('queue' => queue_name, 'class' => klass, 'args' => [hash], 'at' => epoch_seconds)
|
20
|
+
end
|
21
|
+
|
22
|
+
def setup_heartbeat!(queue_name)
|
23
|
+
# TODO: not sure how to do this or what is means to set this up in Sidekiq
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# require 'sidekiq_bus/tasks'
|
2
|
+
# will give you these tasks
|
3
|
+
|
4
|
+
|
5
|
+
require "resque/tasks"
|
6
|
+
namespace :sidekiqbus do
|
7
|
+
|
8
|
+
desc "Setup will configure a resque task to run before resque:work"
|
9
|
+
task :setup => [ :preload ] do
|
10
|
+
|
11
|
+
if ENV['QUEUES'].nil?
|
12
|
+
manager = ::QueueBus::TaskManager.new(true)
|
13
|
+
queues = manager.queue_names
|
14
|
+
ENV['QUEUES'] = queues.join(",")
|
15
|
+
else
|
16
|
+
queues = ENV['QUEUES'].split(",")
|
17
|
+
end
|
18
|
+
|
19
|
+
if queues.size == 1
|
20
|
+
puts " >> Working Queue : #{queues.first}"
|
21
|
+
else
|
22
|
+
puts " >> Working Queues: #{queues.join(", ")}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "Subscribes this application to QueueBus events"
|
27
|
+
task :subscribe => [ :preload ] do
|
28
|
+
manager = ::QueueBus::TaskManager.new(true)
|
29
|
+
count = manager.subscribe!
|
30
|
+
raise "No subscriptions created" if count == 0
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "Unsubscribes this application from QueueBus events"
|
34
|
+
task :unsubscribe => [ :preload ] do
|
35
|
+
require 'resque-bus'
|
36
|
+
manager = ::QueueBus::TaskManager.new(true)
|
37
|
+
count = manager.unsubscribe!
|
38
|
+
puts "No subscriptions unsubscribed" if count == 0
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "Sets the queue to work the driver Use: `rake sidekiqbus:driver resque:work`"
|
42
|
+
task :driver => [ :preload ] do
|
43
|
+
ENV['QUEUES'] = ::QueueBus.incoming_queue
|
44
|
+
end
|
45
|
+
|
46
|
+
# Preload app files if this is Rails
|
47
|
+
task :preload do
|
48
|
+
require "sidekiq"
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
# examples to test out the system
|
53
|
+
namespace :example do
|
54
|
+
desc "Publishes events to example applications"
|
55
|
+
task :publish => [ "sidekiqbus:preload", "sidekiqbus:setup" ] do
|
56
|
+
which = ["one", "two", "three", "other"][rand(4)]
|
57
|
+
QueueBus.publish("event_#{which}", { "rand" => rand(99999)})
|
58
|
+
QueueBus.publish("event_all", { "rand" => rand(99999)})
|
59
|
+
QueueBus.publish("none_subscribed", { "rand" => rand(99999)})
|
60
|
+
puts "published event_#{which}, event_all, none_subscribed"
|
61
|
+
end
|
62
|
+
|
63
|
+
desc "Sets up an example config"
|
64
|
+
task :register => [ "sidekiqbus:preload"] do
|
65
|
+
QueueBus.dispatch("example") do
|
66
|
+
subscribe "event_one" do
|
67
|
+
puts "event1 happened"
|
68
|
+
end
|
69
|
+
|
70
|
+
subscribe "event_two" do
|
71
|
+
puts "event2 happened"
|
72
|
+
end
|
73
|
+
|
74
|
+
high "event_three" do
|
75
|
+
puts "event3 happened (high)"
|
76
|
+
end
|
77
|
+
|
78
|
+
low "event_.*" do |attributes|
|
79
|
+
puts "LOG ALL: #{attributes.inspect}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
desc "Subscribes this application to QueueBus example events"
|
85
|
+
task :subscribe => [ :register, "sidekiqbus:subscribe" ]
|
86
|
+
|
87
|
+
desc "Start a QueueBus example worker"
|
88
|
+
task :work => [ :register, "sidekiqbus:setup", "resque:work" ]
|
89
|
+
|
90
|
+
desc "Start a QueueBus example worker"
|
91
|
+
task :driver => [ :register, "sidekiqbus:driver", "resque:work" ]
|
92
|
+
end
|
93
|
+
end
|