amqp 0.8.0.rc3 → 0.8.0.rc4
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/CHANGELOG +1 -1
- data/amqp.gemspec +1 -1
- data/docs/ConnectingToTheBroker.textile +96 -11
- data/docs/{TLS.textile → ConnectionEncryptionWithTLS.textile} +0 -0
- data/docs/DocumentationGuidesIndex.textile +1 -1
- data/docs/ErrorHandling.textile +154 -3
- data/docs/Exchanges.textile +66 -1
- data/docs/Queues.textile +527 -3
- data/examples/error_handling/handling_authentication_failure_with_a_callback.rb +1 -1
- data/examples/error_handling/handling_authentication_failure_with_a_rescue_block.rb +33 -0
- data/examples/guides/queues/05_binding_a_queue_using_exchange_instance.rb +22 -0
- data/examples/guides/queues/06_biding_a_queue_using_exchange_name_string.rb +22 -0
- data/examples/guides/queues/07_subscribing_to_receive_messages.rb +25 -0
- data/examples/guides/queues/08_poll_for_messages.rb +28 -0
- data/examples/guides/queues/09_unsubscribing_a_consumer.rb +28 -0
- data/examples/guides/queues/10_unbinding_from_exchange.rb +32 -0
- data/examples/guides/queues/11_purge_a_queue.rb +28 -0
- data/examples/guides/queues/12_deleting_a_queue.rb +27 -0
- data/lib/amqp/exceptions.rb +14 -5
- data/lib/amqp/exchange.rb +1 -1
- data/lib/amqp/queue.rb +1 -27
- data/lib/amqp/session.rb +11 -0
- data/lib/amqp/version.rb +1 -1
- metadata +214 -180
data/CHANGELOG
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
= Version 0.8.0
|
2
2
|
|
3
3
|
* [FEATURE] AMQP 0.9.1 support, including tx.* operations class.
|
4
|
+
* [API] Default authentication handler now raises AMQP::PossibleAuthenticationFailureError
|
4
5
|
* [API] AMQP::Channel#initialize now takes 3rd (optional) options hash.
|
5
6
|
* [API] Broker connection class is now AMQP::Session.
|
6
7
|
* [API] AMQP::Error instance now may carry cause, an exception that caused exception in question to be raised.
|
@@ -29,7 +30,6 @@
|
|
29
30
|
* [API] AMQP::Channel#recover now takes (an optional) callback that is called when basic.recover-ok is received.
|
30
31
|
* [API] AMQP::Frame is gone.
|
31
32
|
* [API] AMQP::Buffer is gone. Serialization & framing are now handled primarily by amq-protocol.
|
32
|
-
* [FEATURE] AMQP gem is now AMQP 0.9.1 compatible: it runs atop of amq-client gem
|
33
33
|
* [API] AMQP::Queue#publish is deprecated.
|
34
34
|
* [API] Name argument for AMQP::Queue.new and Channel#queue is optional.
|
35
35
|
|
data/amqp.gemspec
CHANGED
@@ -154,7 +154,7 @@ end
|
|
154
154
|
</code>
|
155
155
|
</pre>
|
156
156
|
|
157
|
-
{AMQP.connect} (and
|
157
|
+
{AMQP.connect} (and {AMQP.start}) will raise {AMQP::TCPConnectionFailed} if connection fails. Code that catches it can write to log
|
158
158
|
about the issue or use retry to execute begin block one more time. Because initial connection failures are due to misconfiguration or network outage, reconnection
|
159
159
|
to the same endpoint (hostname, port, vhost combination) will result in the same issue over and over. TBD: failover, connection to the cluster.
|
160
160
|
|
@@ -209,7 +209,7 @@ connection failure:
|
|
209
209
|
require "rubygems"
|
210
210
|
require "amqp"
|
211
211
|
|
212
|
-
puts "=>
|
212
|
+
puts "=> Authentication failure handling with a callback"
|
213
213
|
puts
|
214
214
|
|
215
215
|
handler = Proc.new { |settings| puts "Failed to connect, as expected"; EM.stop }
|
@@ -233,6 +233,41 @@ end
|
|
233
233
|
</code>
|
234
234
|
</pre>
|
235
235
|
|
236
|
+
default handler raises {AMQP::PossibleAuthenticationFailureError}:
|
237
|
+
|
238
|
+
<pre>
|
239
|
+
<code>
|
240
|
+
#!/usr/bin/env ruby
|
241
|
+
# encoding: utf-8
|
242
|
+
|
243
|
+
require "rubygems"
|
244
|
+
require "amqp"
|
245
|
+
|
246
|
+
puts "=> Authentication failure handling with a rescue block"
|
247
|
+
puts
|
248
|
+
|
249
|
+
handler = Proc.new { |settings| puts "Failed to connect, as expected"; EM.stop }
|
250
|
+
connection_settings = {
|
251
|
+
:port => 5672,
|
252
|
+
:vhost => "/amq_client_testbed",
|
253
|
+
:user => "amq_client_gem",
|
254
|
+
:password => "amq_client_gem_password_that_is_incorrect #{Time.now.to_i}",
|
255
|
+
:timeout => 0.3,
|
256
|
+
:on_tcp_connection_failure => handler
|
257
|
+
}
|
258
|
+
|
259
|
+
|
260
|
+
begin
|
261
|
+
AMQP.start(connection_settings) do |connection, open_ok|
|
262
|
+
raise "This should not be reachable"
|
263
|
+
end
|
264
|
+
rescue AMQP::PossibleAuthenticationFailureError => afe
|
265
|
+
puts "Authentication failed, as expected, caught #{afe.inspect}"
|
266
|
+
EventMachine.stop if EventMachine.reactor_running?
|
267
|
+
end
|
268
|
+
</code>
|
269
|
+
</pre>
|
270
|
+
|
236
271
|
In case you wonder why callback name has "possible" in it: {http://bit.ly/mTr1YN AMQP 0.9.1 spec} requires broker implementations to
|
237
272
|
simply close TCP connection without sending any more data when an exception (such as authentication failure) occurs before AMQP connection
|
238
273
|
is open. In practice, however, when broker closes TCP connection between successful TCP connection and before AMQP connection is open,
|
@@ -242,30 +277,80 @@ it means that authentication has failed.
|
|
242
277
|
|
243
278
|
h2. In Web applications (Ruby on Rails, Sinatra, Merb, Rack)
|
244
279
|
|
245
|
-
|
280
|
+
Web applications are different from standalone applications in that main thread is occupied by Web/application server like Unicorn
|
281
|
+
or Thin, so you need to start EventMachine reactor before you attempt to use {AMQP.connect}.
|
282
|
+
In a Ruby on Rails app, probably the best place for this is in initializer (like config/initializers/amqp.rb). For Merb apps, it is config/init.rb.
|
283
|
+
For Sinatra and pure Rack applications, place it next to other configuration code.
|
284
|
+
|
285
|
+
Next we are going to discuss issues specific to particular Web servers.
|
246
286
|
|
247
|
-
|
287
|
+
h3. Using amqp gem with Unicorn
|
248
288
|
|
289
|
+
h4. Use separate thread for EventMachine reactor
|
249
290
|
|
250
|
-
|
291
|
+
Since Unicorn is not EventMachine-based, you need to start EventMachine reactor in a separate thread like this:
|
292
|
+
|
293
|
+
<pre>
|
294
|
+
<code>
|
295
|
+
Thread.new { EventMachine.run }
|
296
|
+
# give EventMachine reactor a moment to start
|
297
|
+
sleep(0.5)
|
251
298
|
|
252
|
-
|
299
|
+
# now is a good moment to use AMQP.connect
|
300
|
+
</code>
|
301
|
+
</pre>
|
253
302
|
|
303
|
+
Otherwise EventMachine will block current thread.
|
254
304
|
|
255
|
-
|
305
|
+
h4. Starting EventMachine reactor after Unicorn forks worker processes
|
256
306
|
|
257
|
-
|
307
|
+
Because *Unicorn is a pre-forking server, you need to run the same piece of code in
|
308
|
+
after_fork hook in Unicorn configuration file for your app*, otherwise, worker processes won't have EventMachine reactor running:
|
258
309
|
|
310
|
+
<pre>
|
311
|
+
<code>
|
312
|
+
# example snippet of Unicorn configuration file ( config/unicorn/development.rb or similar)
|
313
|
+
after_fork do |server, worker|
|
314
|
+
Thread.new { EventMachine.run }
|
315
|
+
# give EventMachine reactor a moment to start
|
316
|
+
sleep(0.5)
|
259
317
|
|
260
|
-
|
318
|
+
# now is a good moment to use AMQP.connect
|
319
|
+
end
|
320
|
+
</code>
|
321
|
+
</pre>
|
322
|
+
|
323
|
+
|
324
|
+
h3. Using amqp gem with Passenger
|
325
|
+
|
326
|
+
TBD: if you are a Passenger user, please help us write this section!
|
327
|
+
|
328
|
+
|
329
|
+
|
330
|
+
h3. Using amqp gem with Thin and Goliath
|
331
|
+
|
332
|
+
h4. Thin and Goliath start EventMachine reactor for you, but there is a little nuance
|
333
|
+
|
334
|
+
If you use "Thin":http://code.macournoyer.com/thin/ or "Goliath":https://github.com/postrank-labs/goliath/, you are all set: those two servers use EventMachine under the hood.
|
335
|
+
There is no need to start EventMachine reactor. However, depending on app server, it's version, version of the framework and Rack middleware being used,
|
336
|
+
EventMachine reactor start may be slightly delayed. To not depend on this factor, use EventMachine.next_tick to delay connection until after reactor is actually running:
|
337
|
+
|
338
|
+
<pre>
|
339
|
+
<code>
|
340
|
+
EventMachine.next_tick { AMQP.connect(...) }
|
341
|
+
</code>
|
342
|
+
</pre>
|
261
343
|
|
262
|
-
|
344
|
+
So in case EventMachine reactor isn't running yet on server/application boot, connection won't fail but instead wait for reactor to start.
|
345
|
+
Thin and Goliath are not pre-forking servers so there is no need to re-establish connection the way you do it with Unicorn and Passenger.
|
263
346
|
|
264
347
|
|
265
348
|
|
266
349
|
h2. What to read next
|
267
350
|
|
268
|
-
|
351
|
+
* {file:docs/Queues.textile Queues}
|
352
|
+
* {file:docs/ErrorHandling.textile Error handling}
|
353
|
+
* {file:docs/ConnectionEncryptionWithTLS.textile Using TLS (SSL)} (if you want to use SSL encrypted connection to the broker)
|
269
354
|
|
270
355
|
|
271
356
|
h2. Tell us what you think!
|
File without changes
|
@@ -12,7 +12,7 @@ h2. Guide list
|
|
12
12
|
* {file:docs/ErrorHandling.textile Error handling}
|
13
13
|
* {file:docs/08Migration.textile Upgrading from version 0.6.x/0.7.x to 0.8.x and above}
|
14
14
|
* {file:docs/RabbitMQVersions.textile RabbitMQ versions}
|
15
|
-
* {file:docs/
|
15
|
+
* {file:docs/ConnectionEncryptionWithTLS.textile Using TLS (SSL)}
|
16
16
|
* {file:docs/VendorSpecificExtensions.textile Vendor-specific extensions to AMQP 0.9.1 spec}
|
17
17
|
|
18
18
|
|
data/docs/ErrorHandling.textile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
h1.
|
1
|
+
h1. Error handling and recovery
|
2
2
|
|
3
3
|
h2. About this guide
|
4
4
|
|
@@ -26,9 +26,160 @@ There are several {https://github.com/ruby-amqp/amqp/tree/master/examples/error_
|
|
26
26
|
free to contribute new examples.
|
27
27
|
|
28
28
|
|
29
|
-
|
29
|
+
h3. Broker connection failures
|
30
30
|
|
31
|
-
|
31
|
+
When applications connect to the broker, they need to handle connection failures. Networks are not 100% reliable, even with modern system configuration tools
|
32
|
+
like Chef or Puppet misconfigurations happen and broker might be down, too. Error detection should happen as early as possible. There are two ways of detecting
|
33
|
+
TCP connection failure, the first one is to catch an exception:
|
34
|
+
|
35
|
+
<pre>
|
36
|
+
<code>
|
37
|
+
#!/usr/bin/env ruby
|
38
|
+
# encoding: utf-8
|
39
|
+
|
40
|
+
require "rubygems"
|
41
|
+
require "amqp"
|
42
|
+
|
43
|
+
|
44
|
+
puts "=> TCP connection failure handling with a rescue statement"
|
45
|
+
puts
|
46
|
+
|
47
|
+
connection_settings = {
|
48
|
+
:port => 9689,
|
49
|
+
:vhost => "/amq_client_testbed",
|
50
|
+
:user => "amq_client_gem",
|
51
|
+
:password => "amq_client_gem_password",
|
52
|
+
:timeout => 0.3
|
53
|
+
}
|
54
|
+
|
55
|
+
begin
|
56
|
+
AMQP.start(connection_settings) do |connection, open_ok|
|
57
|
+
raise "This should not be reachable"
|
58
|
+
end
|
59
|
+
rescue AMQP::TCPConnectionFailed => e
|
60
|
+
puts "Caught AMQP::TCPConnectionFailed => TCP connection failed, as expected."
|
61
|
+
end
|
62
|
+
</code>
|
63
|
+
</pre>
|
64
|
+
|
65
|
+
{AMQP.connect} (and {AMQP.start}) will raise {AMQP::TCPConnectionFailed} if connection fails. Code that catches it can write to log
|
66
|
+
about the issue or use retry to execute begin block one more time. Because initial connection failures are due to misconfiguration or network outage, reconnection
|
67
|
+
to the same endpoint (hostname, port, vhost combination) will result in the same issue over and over. TBD: failover, connection to the cluster.
|
68
|
+
|
69
|
+
Alternative way of handling connection failure is with an errback (a callback for specific kind of error):
|
70
|
+
|
71
|
+
<pre>
|
72
|
+
<code>
|
73
|
+
#!/usr/bin/env ruby
|
74
|
+
# encoding: utf-8
|
75
|
+
|
76
|
+
require "rubygems"
|
77
|
+
require "amqp"
|
78
|
+
|
79
|
+
puts "=> TCP connection failure handling with a callback"
|
80
|
+
puts
|
81
|
+
|
82
|
+
handler = Proc.new { |settings| puts "Failed to connect, as expected"; EM.stop }
|
83
|
+
connection_settings = {
|
84
|
+
:port => 9689,
|
85
|
+
:vhost => "/amq_client_testbed",
|
86
|
+
:user => "amq_client_gem",
|
87
|
+
:password => "amq_client_gem_password",
|
88
|
+
:timeout => 0.3,
|
89
|
+
:on_tcp_connection_failure => handler
|
90
|
+
}
|
91
|
+
|
92
|
+
|
93
|
+
AMQP.start(connection_settings) do |connection, open_ok|
|
94
|
+
raise "This should not be reachable"
|
95
|
+
end
|
96
|
+
</code>
|
97
|
+
</pre>
|
98
|
+
|
99
|
+
:on_tcp_connection_failure option accepts any object that responds to #call.
|
100
|
+
|
101
|
+
If you connect to the broker from a code in a class (as opposed to top-level scope in a script), Object#method can be used to pass object method as a handler
|
102
|
+
instead of a Proc.
|
103
|
+
|
104
|
+
TBD: provide an example
|
105
|
+
|
106
|
+
|
107
|
+
h3. Authentication failures
|
108
|
+
|
109
|
+
Another reason why connection may fail is authentication failure. Handling authentication failure is very similar to handling initial TCP
|
110
|
+
connection failure:
|
111
|
+
|
112
|
+
<pre>
|
113
|
+
<code>
|
114
|
+
#!/usr/bin/env ruby
|
115
|
+
# encoding: utf-8
|
116
|
+
|
117
|
+
require "rubygems"
|
118
|
+
require "amqp"
|
119
|
+
|
120
|
+
puts "=> Authentication failure handling with a callback"
|
121
|
+
puts
|
122
|
+
|
123
|
+
handler = Proc.new { |settings| puts "Failed to connect, as expected"; EM.stop }
|
124
|
+
connection_settings = {
|
125
|
+
:port => 5672,
|
126
|
+
:vhost => "/amq_client_testbed",
|
127
|
+
:user => "amq_client_gem",
|
128
|
+
:password => "amq_client_gem_password_that_is_incorrect #{Time.now.to_i}",
|
129
|
+
:timeout => 0.3,
|
130
|
+
:on_tcp_connection_failure => handler,
|
131
|
+
:on_possible_authentication_failure => Proc.new { |settings|
|
132
|
+
puts "Authentication failed, as expected, settings are: #{settings.inspect}"
|
133
|
+
|
134
|
+
EM.stop
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
AMQP.start(connection_settings) do |connection, open_ok|
|
139
|
+
raise "This should not be reachable"
|
140
|
+
end
|
141
|
+
</code>
|
142
|
+
</pre>
|
143
|
+
|
144
|
+
default handler raises {AMQP::PossibleAuthenticationFailureError}:
|
145
|
+
|
146
|
+
<pre>
|
147
|
+
<code>
|
148
|
+
#!/usr/bin/env ruby
|
149
|
+
# encoding: utf-8
|
150
|
+
|
151
|
+
require "rubygems"
|
152
|
+
require "amqp"
|
153
|
+
|
154
|
+
puts "=> Authentication failure handling with a rescue block"
|
155
|
+
puts
|
156
|
+
|
157
|
+
handler = Proc.new { |settings| puts "Failed to connect, as expected"; EM.stop }
|
158
|
+
connection_settings = {
|
159
|
+
:port => 5672,
|
160
|
+
:vhost => "/amq_client_testbed",
|
161
|
+
:user => "amq_client_gem",
|
162
|
+
:password => "amq_client_gem_password_that_is_incorrect #{Time.now.to_i}",
|
163
|
+
:timeout => 0.3,
|
164
|
+
:on_tcp_connection_failure => handler
|
165
|
+
}
|
166
|
+
|
167
|
+
|
168
|
+
begin
|
169
|
+
AMQP.start(connection_settings) do |connection, open_ok|
|
170
|
+
raise "This should not be reachable"
|
171
|
+
end
|
172
|
+
rescue AMQP::PossibleAuthenticationFailureError => afe
|
173
|
+
puts "Authentication failed, as expected, caught #{afe.inspect}"
|
174
|
+
EventMachine.stop if EventMachine.reactor_running?
|
175
|
+
end
|
176
|
+
</code>
|
177
|
+
</pre>
|
178
|
+
|
179
|
+
In case you wonder why callback name has "possible" in it: {http://bit.ly/mTr1YN AMQP 0.9.1 spec} requires broker implementations to
|
180
|
+
simply close TCP connection without sending any more data when an exception (such as authentication failure) occurs before AMQP connection
|
181
|
+
is open. In practice, however, when broker closes TCP connection between successful TCP connection and before AMQP connection is open,
|
182
|
+
it means that authentication has failed.
|
32
183
|
|
33
184
|
|
34
185
|
|
data/docs/Exchanges.textile
CHANGED
@@ -11,12 +11,77 @@ h2. Covered versions
|
|
11
11
|
This guide covers amqp gem v0.8.0 and later.
|
12
12
|
|
13
13
|
|
14
|
+
h2. Exchanges in AMQP 0.9.1, briefly
|
14
15
|
|
15
|
-
|
16
|
+
TBD
|
17
|
+
|
18
|
+
|
19
|
+
h2. Exchange types
|
20
|
+
|
21
|
+
TBD
|
22
|
+
|
23
|
+
|
24
|
+
h2. Fanout exchanges
|
16
25
|
|
17
26
|
TBD
|
18
27
|
|
19
28
|
|
29
|
+
h2. Direct exchanges
|
30
|
+
|
31
|
+
TBD
|
32
|
+
|
33
|
+
|
34
|
+
h2. Topic exchanges
|
35
|
+
|
36
|
+
TBD
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
h2. Publishing messages
|
41
|
+
|
42
|
+
TBD
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
h2. Binding queues to exchanges
|
47
|
+
|
48
|
+
TBD
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
h2. Unbinding queues from exchanges
|
53
|
+
|
54
|
+
TBD
|
55
|
+
|
56
|
+
|
57
|
+
h2. Deleting exchanges
|
58
|
+
|
59
|
+
TBD
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
h2. Exchange durability vs Message durability
|
64
|
+
|
65
|
+
TBD
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
h2. Error handling and recovery
|
70
|
+
|
71
|
+
TBD
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
h2. Vendor-specific extensions related to exchanges
|
76
|
+
|
77
|
+
TBD
|
78
|
+
|
79
|
+
|
80
|
+
|
81
|
+
h2. What to read next
|
82
|
+
|
83
|
+
TBD
|
84
|
+
|
20
85
|
|
21
86
|
h2. Tell us what you think!
|
22
87
|
|
data/docs/Queues.textile
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
h1.
|
1
|
+
h1. AMQP queues in detail
|
2
2
|
|
3
3
|
|
4
4
|
h2. About this guide
|
5
5
|
|
6
|
-
|
6
|
+
This guide covers everything related to queues in AMQP 0.9.1, common usage scenarios and how to accomplish typical operations using
|
7
|
+
amqp gem.
|
7
8
|
|
8
9
|
|
9
10
|
h2. Covered versions
|
@@ -12,12 +13,535 @@ This guide covers amqp gem v0.8.0 and later.
|
|
12
13
|
|
13
14
|
|
14
15
|
|
15
|
-
h2.
|
16
|
+
h2. Queues in AMQP 0.9.1, briefly
|
17
|
+
|
18
|
+
h3. What are AMQP queues?
|
19
|
+
|
20
|
+
Queues store and forward messages to consumers. They are similar to mailboxes in SMTP.
|
21
|
+
Messages flow from producing applications to {file:docs/Exchanges.textile exchanges} that route them
|
22
|
+
to queues and finally queues deliver them to consumer applications (or consumer applications fetch messages as needed).
|
23
|
+
|
24
|
+
Note that unlike some other messaging protocols/systems, messages are not delivered directly
|
25
|
+
to queues. They are delivered to exchanges that route messages to queues using rules
|
26
|
+
knows as *bindings*.
|
27
|
+
|
28
|
+
AMQP is a programmable protocol, so queues and bindings alike are declared by applications.
|
29
|
+
|
30
|
+
|
31
|
+
h3. Concept of bindings
|
32
|
+
|
33
|
+
Binding is an association between a queue and an exchange. Queues must be bound to at least one exchange in order to receive messages from publishers.
|
34
|
+
Learn more about bindings in {file:docs/Bindings.textile Bindings guide}.
|
35
|
+
|
36
|
+
|
37
|
+
h3. Attributes
|
38
|
+
|
39
|
+
Queues have several attributes associated with them:
|
40
|
+
|
41
|
+
* Name
|
42
|
+
* Exclusivity
|
43
|
+
* Whether queue is auto-deleted when no longer used
|
44
|
+
* Other metadata (aka X-arguments)
|
45
|
+
|
46
|
+
These attributes define how queues can be used, what their lifecycle is like and other aspects of queue
|
47
|
+
behavior.
|
48
|
+
|
49
|
+
amqp gem represents queues as instances of {AMQP::Queue}.
|
50
|
+
|
51
|
+
h2. Queue names. Server-named queues. Predefined queues.
|
52
|
+
|
53
|
+
Every queue has a name that identifies it. Queue names often contain several segments separated by a dot (.), similarly to how URI
|
54
|
+
path segments are separated by a slash (/), although it may be almost any string, with some limitations (see below).
|
55
|
+
Applications may pick queue names or ask broker to generate a name for them. To do so, pass *empty string* as queue name argument.
|
56
|
+
|
57
|
+
Here is an example:
|
58
|
+
|
59
|
+
<pre>
|
60
|
+
<code>
|
61
|
+
# Declaring a server-named queue using AMQP::Queue constructor
|
62
|
+
AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672/") do |connection, open_ok|
|
63
|
+
AMQP::Channel.new do |channel, open_ok|
|
64
|
+
AMQP::Queue.new(channel, "", :auto_delete => true) do |queue, declare_ok|
|
65
|
+
puts "#{queue.name} is ready to go. AMQP method: #{declare_ok.inspect}"
|
66
|
+
|
67
|
+
connection.close {
|
68
|
+
EventMachine.stop { exit }
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
</code>
|
74
|
+
</pre>
|
75
|
+
|
76
|
+
If you want to declare a queue with a particular name, for example, "images.resize", pass it to Queue class constructor:
|
77
|
+
|
78
|
+
<pre>
|
79
|
+
<code>
|
80
|
+
# Declaring a server-named queue using AMQP::Queue constructor
|
81
|
+
AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672/") do |connection, open_ok|
|
82
|
+
AMQP::Channel.new do |channel, open_ok|
|
83
|
+
AMQP::Queue.new(channel, "images.resize", :auto_delete => true) do |queue, declare_ok|
|
84
|
+
puts "#{queue.name} is ready to go."
|
85
|
+
|
86
|
+
connection.close {
|
87
|
+
EventMachine.stop { exit }
|
88
|
+
}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
</code>
|
93
|
+
</pre>
|
94
|
+
|
95
|
+
Queue names starting with 'amq.' are reserved for internal use by the broker. Attempts to declare queue with a name that violates this
|
96
|
+
rule will result in AMQP::IncompatibleOptionsError to be thrown (when queue is re-declared on the same channel object) or channel-level exception
|
97
|
+
(when originally queue was declared on one channel and re-declaration with different attributes happens on another channel).
|
98
|
+
Learn more in Error handling and recovery section below.
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
h2. Common usage scenarios
|
103
|
+
|
104
|
+
h2. Queue life-cycles. When use of server-named queues is optimal and when it isn't.
|
105
|
+
|
106
|
+
To quote AMQP 0.9.1 spec, there are two common message queue life-cycles:
|
107
|
+
|
108
|
+
* Durable message queues that are shared by many consumers and have an independent existence: i.e. they
|
109
|
+
will continue to exist and collect messages whether or not there are consumers to receive them.
|
110
|
+
* Temporary message queues that are private to one consumer and are tied to that consumer. When the
|
111
|
+
consumer disconnects, the message queue is deleted.
|
112
|
+
|
113
|
+
There are some variations on these, such as shared message queues that are deleted when the last of
|
114
|
+
many consumers disconnects.
|
115
|
+
|
116
|
+
One example of durable message queues is well-known services like event collectors (event loggers).
|
117
|
+
They are usually up whether there are services to log anything or not. Other applications know what
|
118
|
+
queues they use and can rely on those queues being around all the time, survive broker restarts and
|
119
|
+
in general be available should an application in the network need to use them. In this case,
|
120
|
+
explicitly named durable queues are optimal and coupling it creates between applications is not
|
121
|
+
an issue. Another scenario of a well-known long-lived service is distributed metadata/directory/locking server
|
122
|
+
like Apache Zookeeper, Google's Chubby or DNS. Services like this benefit from using well-known, not generated
|
123
|
+
queue names, and so do other applications that use them.
|
124
|
+
|
125
|
+
Different scenario is in "a cloud settings" when some kind of workers/instances may come online and
|
126
|
+
go down basically any time and other applications cannot rely on them being available. Using well-known
|
127
|
+
queue names in this case is possible but server-generated, short-lived queues that are bound to
|
128
|
+
topic or fanout exchanges to receive relevant messages is a better idea.
|
129
|
+
|
130
|
+
Imagine a service that processes an endless stream of events (Twitter is one example). When traffic goes
|
131
|
+
up, development operations may spin up additional applications instances in the cloud to handle the load.
|
132
|
+
Those new instances want to subscribe to receive messages to process but the rest of the system doesn't
|
133
|
+
know anything about them, rely on them being online or try to address them directly: they process events
|
134
|
+
from a shared stream and are not different from their peers. In a case like this, there is no reason for
|
135
|
+
message consumers to not use queue names generated by the broker.
|
136
|
+
|
137
|
+
In general, use of explicitly named or server-named queues depends on messaging pattern your application needs.
|
138
|
+
{http://www.eaipatterns.com/ Enterprise Integration Patters} discusses many messaging patterns in depth.
|
139
|
+
RabbitMQ FAQ also has a section on {http://www.rabbitmq.com/faq.html#scenarios use cases}.
|
140
|
+
|
141
|
+
|
142
|
+
|
143
|
+
h2. Declaring a durable shared queue
|
144
|
+
|
145
|
+
To declare a durable shared queue, you pass queue name that is a non-blank string and use :durable option:
|
146
|
+
|
147
|
+
<pre>
|
148
|
+
<code>
|
149
|
+
#!/usr/bin/env ruby
|
150
|
+
# encoding: utf-8
|
151
|
+
|
152
|
+
require "rubygems"
|
153
|
+
require "amqp"
|
154
|
+
|
155
|
+
# Declaring a client-named queue using AMQP::Queue constructor
|
156
|
+
AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672/") do |connection, open_ok|
|
157
|
+
AMQP::Channel.new do |channel, open_ok|
|
158
|
+
AMQP::Queue.new(channel, "images.resize", :durable => true) do |queue, declare_ok|
|
159
|
+
puts "#{queue.name} is ready to go."
|
160
|
+
|
161
|
+
connection.close {
|
162
|
+
EventMachine.stop { exit }
|
163
|
+
}
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
</code>
|
168
|
+
</pre>
|
169
|
+
|
170
|
+
the same piece of code that uses {AMQP::Channel#queue} for convenience:
|
171
|
+
|
172
|
+
<pre>
|
173
|
+
<code>
|
174
|
+
#!/usr/bin/env ruby
|
175
|
+
# encoding: utf-8
|
176
|
+
|
177
|
+
require "rubygems"
|
178
|
+
require "amqp"
|
179
|
+
|
180
|
+
# Declaring a client-named queue using AMQP::Queue constructor
|
181
|
+
AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672/") do |connection, open_ok|
|
182
|
+
AMQP::Channel.new do |channel, open_ok|
|
183
|
+
channel.queue("images.resize", :durable => true) do |queue, declare_ok|
|
184
|
+
puts "#{queue.name} is ready to go."
|
185
|
+
|
186
|
+
connection.close {
|
187
|
+
EventMachine.stop { exit }
|
188
|
+
}
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
</code>
|
193
|
+
</pre>
|
194
|
+
|
195
|
+
|
196
|
+
h2. Declaring a temporary exclusive queue
|
197
|
+
|
198
|
+
To declare a server-named, exclusive, auto-deleted queue, pass "" (empty string) as queue name and
|
199
|
+
use :exclusive and :auto_delete options:
|
200
|
+
|
201
|
+
<pre>
|
202
|
+
<code>
|
203
|
+
#!/usr/bin/env ruby
|
204
|
+
# encoding: utf-8
|
205
|
+
|
206
|
+
require "rubygems"
|
207
|
+
require "amqp"
|
208
|
+
|
209
|
+
# Declaring a server-named queue using AMQP::Queue constructor
|
210
|
+
AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672/") do |connection, open_ok|
|
211
|
+
AMQP::Channel.new do |channel, open_ok|
|
212
|
+
AMQP::Queue.new(channel, "", :auto_delete => true, :exclusive => true) do |queue, declare_ok|
|
213
|
+
puts "#{queue.name} is ready to go."
|
214
|
+
|
215
|
+
connection.close {
|
216
|
+
EventMachine.stop { exit }
|
217
|
+
}
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
</code>
|
222
|
+
</pre>
|
223
|
+
|
224
|
+
the same piece of code that uses {AMQP::Channel#queue} for convenience:
|
225
|
+
|
226
|
+
<pre>
|
227
|
+
<code>
|
228
|
+
#!/usr/bin/env ruby
|
229
|
+
# encoding: utf-8
|
230
|
+
|
231
|
+
require "rubygems"
|
232
|
+
require "amqp"
|
233
|
+
|
234
|
+
# Declaring a server-named queue using AMQP::Queue constructor
|
235
|
+
AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672/") do |connection, open_ok|
|
236
|
+
AMQP::Channel.new do |channel, open_ok|
|
237
|
+
channel.queue("", :auto_delete => true, :exclusive => true) do |queue, declare_ok|
|
238
|
+
puts "#{queue.name} is ready to go."
|
239
|
+
|
240
|
+
connection.close {
|
241
|
+
EventMachine.stop { exit }
|
242
|
+
}
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
</code>
|
247
|
+
</pre>
|
248
|
+
|
249
|
+
|
250
|
+
|
251
|
+
h2. Binding queues to exchanges
|
252
|
+
|
253
|
+
In order to receive messages, a queue needs to be bound to at least one exchange. Most of the time binding is explcit (done by applications).
|
254
|
+
To bind a queue to an exchange, use {AMQP::Queue#bind). Argument can be either an {AMQP::Exchange} instance or exchange name:
|
255
|
+
|
256
|
+
<pre>
|
257
|
+
<code>
|
258
|
+
#!/usr/bin/env ruby
|
259
|
+
# encoding: utf-8
|
260
|
+
|
261
|
+
require "rubygems"
|
262
|
+
require "amqp"
|
263
|
+
|
264
|
+
# Binding a queue to an exchange
|
265
|
+
AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672/") do |connection, open_ok|
|
266
|
+
AMQP::Channel.new do |channel, open_ok|
|
267
|
+
exchange = channel.fanout("amq.fanout")
|
268
|
+
|
269
|
+
channel.queue("", :auto_delete => true, :exclusive => true) do |queue, declare_ok|
|
270
|
+
queue.bind(exchange) do |bind_ok|
|
271
|
+
puts "Just bound #{queue.name} to #{exchange.name}"
|
272
|
+
end
|
273
|
+
|
274
|
+
connection.close {
|
275
|
+
EventMachine.stop { exit }
|
276
|
+
}
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
</code>
|
281
|
+
</pre>
|
282
|
+
|
283
|
+
<pre>
|
284
|
+
<code>
|
285
|
+
#!/usr/bin/env ruby
|
286
|
+
# encoding: utf-8
|
287
|
+
|
288
|
+
require "rubygems"
|
289
|
+
require "amqp"
|
290
|
+
|
291
|
+
# Binding a queue to an exchange
|
292
|
+
AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672/") do |connection, open_ok|
|
293
|
+
AMQP::Channel.new do |channel, open_ok|
|
294
|
+
exchange_name = "amq.fanout"
|
295
|
+
|
296
|
+
channel.queue("", :auto_delete => true, :exclusive => true) do |queue, declare_ok|
|
297
|
+
queue.bind(exchange_name) do |bind_ok|
|
298
|
+
puts "Just bound #{queue.name} to #{exchange_name}"
|
299
|
+
end
|
300
|
+
|
301
|
+
connection.close {
|
302
|
+
EventMachine.stop { exit }
|
303
|
+
}
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
</code>
|
308
|
+
</pre>
|
309
|
+
|
310
|
+
|
311
|
+
h2. Subscribing to receive messages ("push API")
|
312
|
+
|
313
|
+
Each queue usually has one or more consumers (message handlers). Without it, queues are not very useful, right?
|
314
|
+
To subscribe to receive messages when they arrive to the queue ("start a queue consumer"), one uses {AMQP::Queue#subscribe} method.
|
315
|
+
Then when a message arrives, message header and body (aka payload) are passed to handling block:
|
316
|
+
|
317
|
+
<pre>
|
318
|
+
<code>
|
319
|
+
#!/usr/bin/env ruby
|
320
|
+
# encoding: utf-8
|
321
|
+
|
322
|
+
require "rubygems"
|
323
|
+
require "amqp"
|
324
|
+
|
325
|
+
AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672/") do |connection, open_ok|
|
326
|
+
AMQP::Channel.new do |channel, open_ok|
|
327
|
+
exchange = channel.fanout("amq.fanout")
|
328
|
+
|
329
|
+
channel.queue("", :auto_delete => true, :exclusive => true) do |queue, declare_ok|
|
330
|
+
queue.bind(exchange).subscribe do |headers, payload|
|
331
|
+
puts "Received a message: #{payload.inspect}. Shutting down..."
|
332
|
+
|
333
|
+
connection.close {
|
334
|
+
EM.stop { exit }
|
335
|
+
}
|
336
|
+
end
|
337
|
+
|
338
|
+
EventMachine.add_timer(0.2) do
|
339
|
+
exchange.publish("Ohai!")
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
</code>
|
345
|
+
</pre>
|
346
|
+
|
347
|
+
h3. Exclusive consumers
|
348
|
+
|
349
|
+
TBD
|
350
|
+
|
351
|
+
|
352
|
+
|
353
|
+
h2. Fetching messages when needed ("pull API")
|
354
|
+
|
355
|
+
AMQP 0.9.1 also provides a way for applications to fetch (pull) messages from the queue only when necessary. For that, use
|
356
|
+
{AMQP::Queue#pop}:
|
357
|
+
|
358
|
+
<pre>
|
359
|
+
<code>
|
360
|
+
#!/usr/bin/env ruby
|
361
|
+
# encoding: utf-8
|
362
|
+
|
363
|
+
require "rubygems"
|
364
|
+
require "amqp"
|
365
|
+
|
366
|
+
AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672/") do |connection, open_ok|
|
367
|
+
AMQP::Channel.new do |channel, open_ok|
|
368
|
+
exchange = channel.fanout("amq.fanout")
|
369
|
+
|
370
|
+
channel.queue("", :auto_delete => true, :exclusive => true) do |queue, declare_ok|
|
371
|
+
queue.bind(exchange) do |_|
|
372
|
+
puts "Bound. Publishing a message..."
|
373
|
+
exchange.publish("Ohai!")
|
374
|
+
end
|
375
|
+
|
376
|
+
EventMachine.add_timer(0.5) do
|
377
|
+
queue.pop do |response|
|
378
|
+
puts "Fetched a message: #{response.inspect}. Shutting down..."
|
379
|
+
|
380
|
+
connection.close {
|
381
|
+
EM.stop { exit }
|
382
|
+
}
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
</code>
|
389
|
+
</pre>
|
390
|
+
|
391
|
+
|
392
|
+
TBD
|
393
|
+
|
394
|
+
|
395
|
+
h2. Unsubscribing from messages
|
396
|
+
|
397
|
+
TBD
|
398
|
+
|
399
|
+
|
400
|
+
h2. Unbinding queues from exchanges
|
401
|
+
|
402
|
+
To unbind queue from exchange, use {AMQP::Queue#unbind}:
|
403
|
+
|
404
|
+
<pre>
|
405
|
+
<code>
|
406
|
+
#!/usr/bin/env ruby
|
407
|
+
# encoding: utf-8
|
408
|
+
|
409
|
+
require "rubygems"
|
410
|
+
require "amqp"
|
411
|
+
|
412
|
+
AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672/") do |connection, open_ok|
|
413
|
+
puts "Connected"
|
414
|
+
AMQP::Channel.new(connection) do |channel, open_ok|
|
415
|
+
puts "Opened a channel"
|
416
|
+
channel.on_error do |arg|
|
417
|
+
raise "Channel-level exception!"
|
418
|
+
end
|
419
|
+
exchange = channel.fanout("amq.fanout")
|
420
|
+
|
421
|
+
channel.queue("", :auto_delete => true, :exclusive => true) do |queue, declare_ok|
|
422
|
+
queue.bind(exchange) do |_|
|
423
|
+
puts "Bound"
|
424
|
+
end
|
425
|
+
|
426
|
+
EventMachine.add_timer(0.5) do
|
427
|
+
queue.unbind(exchange) do |_|
|
428
|
+
puts "Unbound. Shutting down..."
|
429
|
+
|
430
|
+
connection.close {
|
431
|
+
EM.stop { exit }
|
432
|
+
}
|
433
|
+
end
|
434
|
+
end # EventMachine.add_timer
|
435
|
+
end # channel.queue
|
436
|
+
end
|
437
|
+
end
|
438
|
+
</code>
|
439
|
+
</pre>
|
440
|
+
|
441
|
+
Note that unbinding an exchange queue was never bound to will result in an exception.
|
442
|
+
|
443
|
+
|
444
|
+
h2. Purging queues
|
445
|
+
|
446
|
+
It is possible to purge (remove all messages from) a queue using {AMQP::Queue#purge):
|
447
|
+
|
448
|
+
<pre>
|
449
|
+
<code>
|
450
|
+
#!/usr/bin/env ruby
|
451
|
+
# encoding: utf-8
|
452
|
+
|
453
|
+
require "rubygems"
|
454
|
+
require "amqp"
|
455
|
+
|
456
|
+
AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672/") do |connection, open_ok|
|
457
|
+
puts "Connected"
|
458
|
+
AMQP::Channel.new(connection) do |channel, open_ok|
|
459
|
+
puts "Opened a channel"
|
460
|
+
channel.on_error do |arg|
|
461
|
+
raise "Channel-level exception!"
|
462
|
+
end
|
463
|
+
exchange = channel.fanout("amq.fanout")
|
464
|
+
|
465
|
+
channel.queue("", :auto_delete => true, :exclusive => true) do |queue, declare_ok|
|
466
|
+
queue.purge do |_|
|
467
|
+
puts "Queue now has no messages"
|
468
|
+
end
|
469
|
+
|
470
|
+
EventMachine.add_timer(0.5) do
|
471
|
+
connection.close {
|
472
|
+
EM.stop { exit }
|
473
|
+
}
|
474
|
+
end # EventMachine.add_timer
|
475
|
+
end # channel.queue
|
476
|
+
end
|
477
|
+
end
|
478
|
+
</code>
|
479
|
+
</pre>
|
480
|
+
|
481
|
+
Callback is optional. However, remember that this operation takes some time.
|
482
|
+
|
483
|
+
|
484
|
+
h2. Deleting queues
|
485
|
+
|
486
|
+
To delete a queue, use {AMQP::Queue#delete}:
|
487
|
+
|
488
|
+
<pre>
|
489
|
+
<code>
|
490
|
+
#!/usr/bin/env ruby
|
491
|
+
# encoding: utf-8
|
492
|
+
|
493
|
+
require "rubygems"
|
494
|
+
require "amqp"
|
495
|
+
|
496
|
+
AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672/") do |connection, open_ok|
|
497
|
+
puts "Connected"
|
498
|
+
AMQP::Channel.new(connection) do |channel, open_ok|
|
499
|
+
puts "Opened a channel"
|
500
|
+
channel.on_error do |arg|
|
501
|
+
raise "Channel-level exception!"
|
502
|
+
end
|
503
|
+
exchange = channel.fanout("amq.fanout")
|
504
|
+
|
505
|
+
channel.queue("", :auto_delete => true, :exclusive => true) do |queue, declare_ok|
|
506
|
+
EventMachine.add_timer(0.5) do
|
507
|
+
queue.delete do
|
508
|
+
puts "Deleted a queue"
|
509
|
+
connection.close {
|
510
|
+
EM.stop { exit }
|
511
|
+
}
|
512
|
+
end
|
513
|
+
end # EventMachine.add_timer
|
514
|
+
end # channel.queue
|
515
|
+
end
|
516
|
+
end
|
517
|
+
</code>
|
518
|
+
</pre>
|
519
|
+
|
520
|
+
Callback can be omitted. However, remember that this operation takes some time.
|
521
|
+
|
522
|
+
h2. Queue durability vs Message durability
|
523
|
+
|
524
|
+
TBD
|
525
|
+
|
526
|
+
|
527
|
+
|
528
|
+
h2. Error handling and recovery
|
16
529
|
|
17
530
|
TBD
|
18
531
|
|
19
532
|
|
20
533
|
|
534
|
+
h2. Vendor-specific extensions related to queues
|
535
|
+
|
536
|
+
TBD
|
537
|
+
|
538
|
+
|
539
|
+
|
540
|
+
h2. What to read next
|
541
|
+
|
542
|
+
TBD
|
543
|
+
|
544
|
+
|
21
545
|
h2. Tell us what you think!
|
22
546
|
|
23
547
|
Please take a moment and tell us what you think about this guide on "Ruby AMQP mailing list":http://groups.google.com/group/ruby-amqp:
|