amqp 0.8.0.rc3 → 0.8.0.rc4
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|