amqp 0.8.0.rc12 → 0.8.0.rc13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/.travis.yml +1 -1
  2. data/.yardopts +2 -1
  3. data/CONTRIBUTORS +29 -22
  4. data/Gemfile +2 -1
  5. data/README.md +241 -0
  6. data/amqp.gemspec +7 -5
  7. data/bin/set_test_suite_realms_up.sh +6 -6
  8. data/docs/08Migration.textile +3 -1
  9. data/docs/Bindings.textile +3 -1
  10. data/docs/Clustering.textile +4 -0
  11. data/docs/ConnectingToTheBroker.textile +108 -86
  12. data/docs/ConnectionEncryptionWithTLS.textile +3 -1
  13. data/docs/DocumentationGuidesIndex.textile +24 -2
  14. data/docs/Durability.textile +22 -1
  15. data/docs/ErrorHandling.textile +21 -1
  16. data/docs/Exchanges.textile +181 -9
  17. data/docs/GettingStarted.textile +65 -167
  18. data/docs/Queues.textile +400 -355
  19. data/docs/RabbitMQVersions.textile +34 -3
  20. data/docs/Routing.textile +4 -1
  21. data/docs/RunningTests.textile +116 -0
  22. data/docs/Troubleshooting.textile +131 -0
  23. data/docs/VendorSpecificExtensions.textile +20 -0
  24. data/examples/channels/qos_aka_prefetch.rb +3 -3
  25. data/examples/channels/qos_aka_prefetch_without_callback.rb +2 -2
  26. data/examples/error_handling/channel_level_exception.rb +1 -1
  27. data/examples/error_handling/channel_level_exception_with_multiple_channels_involved.rb +1 -0
  28. data/examples/error_handling/connection_level_exception.rb +26 -0
  29. data/examples/error_handling/global_channel_level_exception_handler.rb +3 -3
  30. data/examples/exchanges/autodeletion_of_exchanges.rb +37 -0
  31. data/examples/extensions/rabbitmq/per_queue_message_ttl.rb +3 -3
  32. data/examples/extensions/rabbitmq/publisher_confirmations_with_transient_messages.rb +2 -2
  33. data/examples/guides/getting_started/{03_babblr.rb → 03_blabbr.rb} +0 -0
  34. data/examples/guides/queues/01a_declaring_a_server_named_queue_using_queue_constructor.rb +18 -0
  35. data/examples/guides/queues/01b_declaring_a_queue_using_queue_constructor.rb +18 -0
  36. data/examples/guides/queues/02a_declaring_a_durable_shared_queue.rb +18 -0
  37. data/examples/guides/queues/02b_declaring_a_durable_shared_queue.rb +18 -0
  38. data/examples/guides/queues/03a_declaring_a_temporary_exclusive_queue.rb +19 -0
  39. data/examples/guides/queues/03b_declaring_a_temporary_exclusive_queue.rb +18 -0
  40. data/examples/guides/queues/{05_binding_a_queue_using_exchange_instance.rb → 04_bind_a_queue_using_exchange_instance.rb} +2 -2
  41. data/examples/guides/queues/{06_biding_a_queue_using_exchange_name_string.rb → 05_bind_a_queue_using_exchange_name.rb} +2 -2
  42. data/examples/guides/queues/{07_subscribing_to_receive_messages.rb → 06_subscribe_to_receive_messages.rb} +1 -1
  43. data/examples/guides/queues/{08_poll_for_messages.rb → 07_fetch_a_message_from_the_queue.rb} +1 -1
  44. data/examples/guides/queues/{09_unsubscribing_a_consumer.rb → 08_unsubscribing_a_consumer.rb} +0 -0
  45. data/examples/guides/queues/{10_unbinding_from_exchange.rb → 09_unbinding_from_exchange.rb} +2 -2
  46. data/examples/guides/queues/{11_purge_a_queue.rb → 10_purge_a_queue.rb} +2 -2
  47. data/examples/guides/queues/{12_deleting_a_queue.rb → 11_deleting_a_queue.rb} +2 -2
  48. data/examples/hello_world.rb +1 -1
  49. data/examples/hello_world_with_an_empty_string.rb +33 -0
  50. data/examples/issues/amq_client_issue_7.rb +31 -0
  51. data/examples/issues/amq_protocol_issue_14.rb +46 -0
  52. data/examples/issues/issue_75.rb +23 -0
  53. data/examples/issues/issue_79.rb +35 -0
  54. data/examples/issues/issue_80.rb +40 -0
  55. data/examples/publishing/{publishing_and_immediately_stopping_event_loop.rb → publishing_a_one_off_message.rb} +9 -12
  56. data/examples/publishing/publishing_callback.rb +52 -0
  57. data/examples/publishing/returned_messages.rb +3 -3
  58. data/examples/queues/accessing_message_metadata.rb +60 -0
  59. data/examples/queues/queue_status.rb +0 -7
  60. data/examples/queues/rejecting_messages_without_requeueuing.rb +47 -0
  61. data/examples/queues/using_explicit_acknowledgements.rb +96 -0
  62. data/examples/routing/headers_routing.rb +54 -0
  63. data/lib/amqp/channel.rb +245 -40
  64. data/lib/amqp/client.rb +23 -11
  65. data/lib/amqp/exchange.rb +58 -41
  66. data/lib/amqp/queue.rb +66 -13
  67. data/lib/amqp/version.rb +1 -1
  68. data/spec/integration/authentication_spec.rb +5 -5
  69. data/spec/integration/basic_get_spec.rb +1 -1
  70. data/spec/integration/channel_close_spec.rb +10 -3
  71. data/spec/integration/queue_declaration_spec.rb +26 -5
  72. data/spec/integration/topic_subscription_spec.rb +1 -1
  73. data/spec/unit/amqp/client_spec.rb +7 -54
  74. data/tasks.rb +1 -8
  75. metadata +64 -23
  76. data/README.textile +0 -229
@@ -1,4 +1,6 @@
1
- h1. AMQP queues in detail
1
+ # @title Ruby AMQP gem: Working with queues
2
+
3
+ h1. Working with queues
2
4
 
3
5
 
4
6
  h2. About this guide
@@ -9,7 +11,7 @@ amqp gem.
9
11
 
10
12
  h2. Covered versions
11
13
 
12
- This guide covers amqp gem v0.8.0 and later.
14
+ This guide covers "Ruby amqp gem":http://github.com/ruby-amqp/amqp v0.8.0 and later.
13
15
 
14
16
 
15
17
 
@@ -40,6 +42,7 @@ Queues have several attributes associated with them:
40
42
 
41
43
  * Name
42
44
  * Exclusivity
45
+ * Durability
43
46
  * Whether queue is auto-deleted when no longer used
44
47
  * Other metadata (aka X-arguments)
45
48
 
@@ -56,41 +59,11 @@ Applications may pick queue names or ask broker to generate a name for them. To
56
59
 
57
60
  Here is an example:
58
61
 
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>
62
+ <script src="https://gist.github.com/998720.js"> </script>
75
63
 
76
64
  If you want to declare a queue with a particular name, for example, "images.resize", pass it to Queue class constructor:
77
65
 
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>
66
+ <script src="https://gist.github.com/998721.js"> </script>
94
67
 
95
68
  Queue names starting with 'amq.' are reserved for internal use by the broker. Attempts to declare queue with a name that violates this
96
69
  rule will result in AMQP::IncompatibleOptionsError to be thrown (when queue is re-declared on the same channel object) or channel-level exception
@@ -99,18 +72,16 @@ Learn more in Error handling and recovery section below.
99
72
 
100
73
 
101
74
 
102
- h2. Common usage scenarios
103
-
104
- h2. Queue life-cycles. When use of server-named queues is optimal and when it isn't.
75
+ h2. Queue life-cycle patterns.
105
76
 
106
- To quote AMQP 0.9.1 spec, there are two common message queue life-cycles:
77
+ To quote AMQP 0.9.1 spec, there are two common message queue life-cycle patterns:
107
78
 
108
79
  * Durable message queues that are shared by many consumers and have an independent existence: i.e. they
109
80
  will continue to exist and collect messages whether or not there are consumers to receive them.
110
81
  * Temporary message queues that are private to one consumer and are tied to that consumer. When the
111
82
  consumer disconnects, the message queue is deleted.
112
83
 
113
- There are some variations on these, such as shared message queues that are deleted when the last of
84
+ There are some variations of these, such as shared message queues that are deleted when the last of
114
85
  many consumers disconnects.
115
86
 
116
87
  One example of durable message queues is well-known services like event collectors (event loggers).
@@ -144,255 +115,421 @@ h2. Declaring a durable shared queue
144
115
 
145
116
  To declare a durable shared queue, you pass queue name that is a non-blank string and use :durable option:
146
117
 
118
+ <script src="https://gist.github.com/998723.js"> </script>
119
+
120
+ the same piece of code that uses {AMQP::Channel#queue} for convenience:
121
+
122
+ <script src="https://gist.github.com/998724.js"> </script>
123
+
124
+
125
+
126
+ h2. Declaring a temporary exclusive queue
127
+
128
+ To declare a server-named, exclusive, auto-deleted queue, pass "" (empty string) as queue name and
129
+ use :exclusive and :auto_delete options:
130
+
131
+ <script src="https://gist.github.com/998725.js"> </script>
132
+
133
+ the same piece of code that uses {AMQP::Channel#queue} for convenience:
134
+
135
+ <script src="https://gist.github.com/998726.js"> </script>
136
+
137
+
138
+
139
+ h2. Binding queues to exchanges
140
+
141
+ 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).
142
+ To bind a queue to an exchange, use {AMQP::Queue#bind}. Argument can be either an {AMQP::Exchange} instance, as demonstrated in this example
143
+
144
+ <script src="https://gist.github.com/998727.js"> </script>
145
+
146
+ or an exchange name given as a string:
147
+
148
+ <script src="https://gist.github.com/998729.js"> </script>
149
+
150
+
151
+
152
+
153
+ h2. Subscribing to receive messages ("push API")
154
+
155
+ To subscribe to receive messages when they arrive to the queue ("start a queue consumer"), one uses {AMQP::Queue#subscribe} method.
156
+ Then when a message arrives, message header and body (aka payload) are passed to the handler:
157
+
158
+ <script src="https://gist.github.com/998731.js"> </script>
159
+
160
+ Subscriptions for message delivery are usually referred to as "consumers" in the AMQP 0.9.1 spec, client libraries documentation and books.
161
+ Consumers last as long as the channel they were declared on, or until the client cancels them (unsubscribes).
162
+
163
+ Consumers are identified by <i>consumer tags</i>. If you need to obtain consumer tag of a queue that is subscribed to receive messages,
164
+ use {AMQP::Queue#consumer_tag}.
165
+
166
+
167
+ h3. Accessing message metadata
168
+
169
+ `header` object in the example above provides access to message metadata and delivery information:
170
+
171
+ * Message content type
172
+ * Message content­encoding
173
+ * Message routing key
174
+ * Message delivery mode (persistent or not)
175
+ * Consumer tag this delivery is for
176
+ * Delivery tag
177
+ * Message priority
178
+ * Whether or not message is redelivered
179
+ * Producer application id
180
+
181
+ and so on. An example to demonstrate how to access some of those attributes:
182
+
183
+ <script src="https://gist.github.com/998739.js"> </script>
184
+
185
+
186
+ h3. Exclusive consumers
187
+
188
+ Consumers can request exclusive access to the queue (meaning only this consumer can access the queue). This is useful when you want a long-lived shared
189
+ queue to be temporarily accessible by just one application (or thread, or process). If application exclusive consumer is part of crashes or loses
190
+ TCP connection to the broker, channel is closed and exclusive consumer is thus cancelled.
191
+
192
+ To exclusively receive messages from the queue, pass :exclusive option to {AMQP::Queue#subscribe}:
193
+
147
194
  <pre>
148
195
  <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
196
+ queue.subscribe(:exclusive => true) do |metadata, payload|
197
+ # message handling logic...
166
198
  end
167
199
  </code>
168
200
  </pre>
169
201
 
170
- the same piece of code that uses {AMQP::Channel#queue} for convenience:
202
+ TBD: describe what happens when exclusivity property is violated and how to handle it.
203
+
204
+
205
+ h3. Message acknowledgements
206
+
207
+ Consumer applications (applications that receive and process messages) may (and will) occasionally fail to process individual messages, or will just
208
+ crash. That's not to mention possible network issues. This raises a question: when should AMQP broker remove messages from queues? AMQP 0.9.1 lets
209
+ you choose one of two answers:
210
+
211
+ * After broker sends a message to an application (using either basic.deliver or basic.get-ok methods).
212
+ * After the application sends back an acknowledgement (using basic.ack AMQP method).
213
+
214
+ The former model is called *automatic acknowledgement model* while the latter is *explicit acknowledgement model*. With the explicit model, application
215
+ chooses when it's time to send an ack: it can be right after receiving it, or after persisting it to a data store before processing, or after fully
216
+ processing the message (for example, successfully fetching a Web page, processing and storing it into some persistent data store).
217
+
218
+ If a consumer dies without sending an ack, AMQP broker will redeliver it to another consumer (or, if none are available at the time, it will wait
219
+ until at least one consumer is registered for the same queue).
220
+
221
+ Acknowledgement model is chosen when a new consumer is registered for a queue. By default, {AMQP::Queue#subscribe} will use the *automatic* model.
222
+ To switch to the *explicit* model, :ack option should be used:
171
223
 
172
224
  <pre>
173
225
  <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
226
+ queue.subscribe(:ack => true) do |metadata, payload|
227
+ # message handling logic...
191
228
  end
192
229
  </code>
193
230
  </pre>
194
231
 
232
+ To demonstrate how redelivery works, lets have a look at the following code example:
195
233
 
196
- h2. Declaring a temporary exclusive queue
234
+ <script src="https://gist.github.com/999396.js"> </script>
197
235
 
198
- To declare a server-named, exclusive, auto-deleted queue, pass "" (empty string) as queue name and
199
- use :exclusive and :auto_delete options:
236
+ So what is going on here? This example uses 3 AMQP connections to imitate 3 applications, 1 producer and two consumers. Each AMQP connection opens a single
237
+ channel:
200
238
 
201
239
  <pre>
202
240
  <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
241
+ # open two connections to imitate two apps
242
+ connection1 = AMQP.connect
243
+ connection2 = AMQP.connect
244
+ connection3 = AMQP.connect
245
+
246
+ channel_exception_handler = Proc.new { |ch, channel_close| EventMachine.stop; raise "channel error: #{channel_close.reply_text}" }
247
+
248
+ # open two channels
249
+ channel1 = AMQP::Channel.new(connection1)
250
+ channel1.on_error(&channel_exception_handler)
251
+ # ...
252
+
253
+ channel2 = AMQP::Channel.new(connection2)
254
+ channel2.on_error(&channel_exception_handler)
255
+ # ...
256
+
257
+ # app 3 will just publish messages
258
+ channel3 = AMQP::Channel.new(connection3)
259
+ channel3.on_error(&channel_exception_handler)
221
260
  </code>
222
261
  </pre>
223
262
 
224
- the same piece of code that uses {AMQP::Channel#queue} for convenience:
263
+ Consumers share a queue and producer publishes messages there periodically using `amq.direct` exchange. Both "applications" subscribe to receive messages
264
+ using explicit acknowledgement model. AMQP broker by default will send each message to the next consumer, in sequence
265
+ (this kind of load balancing is knownas *round-robin*). So some messages will be delivered to consumer #1 and some to consumer #2.
225
266
 
226
267
  <pre>
227
268
  <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
269
+ exchange = channel3.direct("amq.direct")
270
+
271
+ # ...
272
+
273
+ queue1 = channel1.queue("amqpgem.examples.acknowledgements.explicit", :auto_delete => false)
274
+ # purge the queue so that we don't get any redeliveries from previous runs
275
+ queue1.purge
276
+ queue1.bind(exchange).subscribe(:ack => true) do |metadata, payload|
277
+ # do some work
278
+ sleep(0.2)
279
+
280
+ # acknowledge some messages, they will be removed from the queue
281
+ if rand > 0.5
282
+ # FYI: there is a shortcut, metadata.ack
283
+ channel1.acknowledge(metadata.delivery_tag, false)
284
+ puts "[consumer1] Got message ##{metadata.headers['i']}, ack-ed"
285
+ else
286
+ # odd messages are not ack-ed and will remain in the queue for redelivery
287
+ # when app #1 connection is closed (either properly or due to a crash)
288
+ puts "[consumer1] Got message ##{metadata.headers['i']}, SKIPPPED"
244
289
  end
245
290
  end
291
+
292
+ queue2 = channel2.queue!("amqpgem.examples.acknowledgements.explicit", :auto_delete => false)
293
+ queue2.subscribe(:ack => true) do |metadata, payload|
294
+ metadata.ack
295
+ # app 2 always acks messages
296
+ puts "[consumer2] Received #{payload}, redelivered = #{metadata.redelivered}, ack-ed"
297
+ end
246
298
  </code>
247
299
  </pre>
248
300
 
301
+ To demonstrate message redelivery we make consumer #1 randomly select what messages to acknowledge. After 4 seconds we disconnect it (to imitate
302
+ a crash). When that happens, AMQP broker redelivers unacknowledged messages to the consumer #2 which acknowledges them unconditionally. After 10 seconds,
303
+ this example closes all outstanding connections and exits.
249
304
 
305
+ An example output produced by this example:
250
306
 
251
- h2. Binding queues to exchanges
307
+ <pre>
308
+ => Subscribing for messages using explicit acknowledgements model
309
+
310
+ [consumer2] Received Message #0, redelivered = false, ack-ed
311
+ [consumer1] Got message #1, SKIPPPED
312
+ [consumer1] Got message #2, SKIPPPED
313
+ [consumer1] Got message #3, ack-ed
314
+ [consumer2] Received Message #4, redelivered = false, ack-ed
315
+ [consumer1] Got message #5, SKIPPPED
316
+ [consumer2] Received Message #6, redelivered = false, ack-ed
317
+ [consumer2] Received Message #7, redelivered = false, ack-ed
318
+ [consumer2] Received Message #8, redelivered = false, ack-ed
319
+ [consumer2] Received Message #9, redelivered = false, ack-ed
320
+ [consumer2] Received Message #10, redelivered = false, ack-ed
321
+ [consumer2] Received Message #11, redelivered = false, ack-ed
322
+ ----- Connection 1 is now closed (we pretend that it has crashed) -----
323
+ [consumer2] Received Message #5, redelivered = true, ack-ed
324
+ [consumer2] Received Message #1, redelivered = true, ack-ed
325
+ [consumer2] Received Message #2, redelivered = true, ack-ed
326
+ [consumer2] Received Message #12, redelivered = false, ack-ed
327
+ [consumer2] Received Message #13, redelivered = false, ack-ed
328
+ [consumer2] Received Message #14, redelivered = false, ack-ed
329
+ [consumer2] Received Message #15, redelivered = false, ack-ed
330
+ [consumer2] Received Message #16, redelivered = false, ack-ed
331
+ [consumer2] Received Message #17, redelivered = false, ack-ed
332
+ [consumer2] Received Message #18, redelivered = false, ack-ed
333
+ [consumer2] Received Message #19, redelivered = false, ack-ed
334
+ [consumer2] Received Message #20, redelivered = false, ack-ed
335
+ [consumer2] Received Message #21, redelivered = false, ack-ed
336
+ [consumer2] Received Message #22, redelivered = false, ack-ed
337
+ [consumer2] Received Message #23, redelivered = false, ack-ed
338
+ [consumer2] Received Message #24, redelivered = false, ack-ed
339
+ [consumer2] Received Message #25, redelivered = false, ack-ed
340
+ [consumer2] Received Message #26, redelivered = false, ack-ed
341
+ [consumer2] Received Message #27, redelivered = false, ack-ed
342
+ [consumer2] Received Message #28, redelivered = false, ack-ed
343
+ [consumer2] Received Message #29, redelivered = false, ack-ed
344
+ [consumer2] Received Message #30, redelivered = false, ack-ed
345
+ [consumer2] Received Message #31, redelivered = false, ack-ed
346
+ [consumer2] Received Message #32, redelivered = false, ack-ed
347
+ [consumer2] Received Message #33, redelivered = false, ack-ed
348
+ [consumer2] Received Message #34, redelivered = false, ack-ed
349
+ [consumer2] Received Message #35, redelivered = false, ack-ed
350
+ </pre>
252
351
 
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:
352
+ As we can see, consumer #1 did not acknowledge 3 messages (labelled 1, 2 and 5):
353
+
354
+ <pre>
355
+ [consumer1] Got message #1, SKIPPPED
356
+ [consumer1] Got message #2, SKIPPPED
357
+ ...
358
+ [consumer1] Got message #5, SKIPPPED
359
+ </pre>
360
+
361
+ and then, once consumer #1 had "crashed", those messages were immediately redelivered to the consumer #2:
362
+
363
+ <pre>
364
+ Connection 1 is now closed (we pretend that it has crashed)
365
+ [consumer2] Received Message #5, redelivered = true, ack-ed
366
+ [consumer2] Received Message #1, redelivered = true, ack-ed
367
+ [consumer2] Received Message #2, redelivered = true, ack-ed
368
+ </pre>
369
+
370
+ To acknowledge a message, use {AMQP::Channel#acknowledge}:
255
371
 
256
372
  <pre>
257
373
  <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
374
+ channel1.acknowledge(metadata.delivery_tag, false)
280
375
  </code>
281
376
  </pre>
282
377
 
378
+ {AMQP::Channel#acknowledge} takes two arguments: message *delivery tag* and a flag that indicates whether or not we acknowledge
379
+ multiple messages at once. Delivery tag is simply a channel-specific increasing number that server uses to identify deliveries.
380
+
381
+ When acknowledging multiple messages at once, the delivery tag is treated as "up to and including". For example, for delivery
382
+ tag = 5 that would mean "acknowledge messages 1, 2, 3, 4 and 5".
383
+
384
+ As a shortcut, it is possible to acknowledge messages using {AMQP::Header#ack} method:
385
+
283
386
  <pre>
284
387
  <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
388
+ queue2.subscribe(:ack => true) do |metadata, payload|
389
+ metadata.ack
306
390
  end
307
391
  </code>
308
392
  </pre>
309
393
 
394
+ <span class="note">
395
+ Acknowledgements are channel-specific. Applications must not receive messages on one channel and acknowledge them on another channel.
396
+ </span>
310
397
 
311
- h2. Subscribing to receive messages ("push API")
398
+ <span class="note">
399
+ A message MUST not be acknowledged more than once. Doing so will result in a channel-level exception (PRECONDITION_FAILED)
400
+ with an error message like this: «PRECONDITION_FAILED - unknown delivery tag»
401
+ </span>
312
402
 
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:
403
+
404
+
405
+ h3. Rejecting messages.
406
+
407
+ When a consumer application receives a message, processing of that message may or may not succeed. Application can
408
+ indicate it to the broker that message processing has failed (or cannot be accomplished at the time) by rejecting a message.
409
+ While rejecting a message, application can ask broker to discard or requeue it.
410
+
411
+ To reject a message, use {AMQP::Channel#reject} method:
316
412
 
317
413
  <pre>
318
414
  <code>
319
- #!/usr/bin/env ruby
320
- # encoding: utf-8
415
+ queue.bind(exchange).subscribe do |metadata, payload|
416
+ # reject but don't requeue (simply discard)
417
+ channel.reject(metadata.delivery_tag)
418
+ end
419
+ </code>
420
+ </pre>
321
421
 
322
- require "rubygems"
323
- require "amqp"
422
+ in the example above messages are rejected without requeueing (broker will simply discard them). To requeue rejected message,
423
+ use the second argument {AMQP::Channel#reject} takes:
324
424
 
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")
425
+ <pre>
426
+ <code>
427
+ queue.bind(exchange).subscribe do |metadata, payload|
428
+ # reject & requeue
429
+ channel.reject(metadata.delivery_tag, true)
430
+ end
431
+ </code>
432
+ </pre>
328
433
 
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..."
434
+ <span class="note">
435
+ When there is only one consumer on a queue, make sure you don't create infinite message delivery loops by rejecting & requeueing
436
+ message from that consumer over and over.
437
+ </span>
332
438
 
333
- connection.close {
334
- EM.stop { exit }
335
- }
336
- end
439
+ Another way to reject a message is by using {AMQP::Header#reject}:
337
440
 
338
- EventMachine.add_timer(0.2) do
339
- exchange.publish("Ohai!")
340
- end
341
- end
342
- end
441
+ <pre>
442
+ <code>
443
+ queue.bind(exchange).subscribe do |metadata, payload|
444
+ # reject but don't requeue (simply discard)
445
+ metadata.reject
446
+ end
447
+ </code>
448
+ </pre>
449
+
450
+ <pre>
451
+ <code>
452
+ queue.bind(exchange).subscribe do |metadata, payload|
453
+ # reject & requeue
454
+ metadata.reject(true)
343
455
  end
344
456
  </code>
345
457
  </pre>
346
458
 
347
- In books, articles and documentation about AMQP 0.9.1 you may come around discussions of _consumer tags_. Consumer tag in AMQP
348
- parlance is an identifier for subscription: most often, it is used to unsubscribe from messages (more on that later in this chapter).
349
- If you need to obtain consumer tag of a queue that is subscribed to receive messages, use {AMQP::Queue#consumer_tag}.
350
459
 
460
+ h3. Negative acknowledgements.
351
461
 
352
- h3. Exclusive consumers
462
+ Messages are rejected with `basic.reject` AMQP method. There is one limitation `basic.reject` has: there is no way to reject multiple
463
+ messages, like you can do with acknowledgements. If you are using "RabbitMQ":http://rabbitmq.com, there is a solution: RabbitMQ provides
464
+ an AMQP 0.9.1 extension known as "negative acknowledgements":http://www.rabbitmq.com/extensions.html#negative-acknowledgements (nacks) and
465
+ amqp gem supports it. For more information, please refer to the {file:docs/VendorSpecificExtensions.textile Vendor-specific Extensions guide}.
353
466
 
354
- TBD
355
467
 
468
+ h3. QoS. Prefetching messages.
356
469
 
470
+ For cases when multiple consumers share a queue, it is useful to be able to specify how many messages each consumer can be sent at once
471
+ (before sending the next acknowledgement). This can be used as a simple load balancing technique or to improve throughput if messages tend
472
+ to be published in batches (for example, if producing application sends them every minute because of the nature of the work it is doing).
357
473
 
358
- h2. Fetching messages when needed ("pull API")
474
+ Imagine a Website that takes data from social media sources like Twitter or Facebook during the Champions League final (or the Superbowl), and then
475
+ calculates how many tweets mentioned a particular team over the last minute. It can be structured as 3 applications:
359
476
 
360
- AMQP 0.9.1 also provides a way for applications to fetch (pull) messages from the queue only when necessary. For that, use
361
- {AMQP::Queue#pop}:
477
+ * A crawler that uses streaming APIs to fetch tweets/statuses, normalizes them and sends them in JSON for processing by other applications ("app A").
478
+ * A calculator that detects what team is mentioned in a message, updates statistics and pushes an update to the Web UI once a minute ("app B").
479
+ * A Web UI that fans visit to see the stats ("app C").
480
+
481
+ In this imaginary example, tweets per second rate will vary but to improve throughput of the system and decrease maximum number of messages AMQP broker
482
+ has to hold in memory at once, applications can be designed in such a way that application "app B", the "calculator", receives 5000 messages and then
483
+ acknowledges them all at once. Broker will not send message 5001 to it unless it receives an acknowledgement for #1, either individually or in bulk
484
+ with other messages.
485
+
486
+ In AMQP parlance this is know as *QoS* or *message prefetching*. Prefetching is configured on per-channel (typically) or per-connection (rarely used)
487
+ basis. To configure prefetching per channel, use {AMQP::Channel#prefetch} method. Lets come back to the example we used in the "Message acknowledgements"
488
+ section:
362
489
 
363
490
  <pre>
364
491
  <code>
365
- #!/usr/bin/env ruby
366
- # encoding: utf-8
367
-
368
- require "rubygems"
369
- require "amqp"
370
-
371
- AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672") do |connection, open_ok|
372
- AMQP::Channel.new do |channel, open_ok|
373
- exchange = channel.fanout("amq.fanout")
374
-
375
- channel.queue("", :auto_delete => true, :exclusive => true) do |queue, declare_ok|
376
- queue.bind(exchange) do |_|
377
- puts "Bound. Publishing a message..."
378
- exchange.publish("Ohai!")
379
- end
380
-
381
- EventMachine.add_timer(0.5) do
382
- queue.pop do |response|
383
- puts "Fetched a message: #{response.inspect}. Shutting down..."
384
-
385
- connection.close {
386
- EM.stop { exit }
387
- }
388
- end
389
- end
390
- end
391
- end
392
- end
492
+ # first app will be given up to 3 messages at a time. If it doesn't
493
+ # ack any messages after it was delivered 3, messages will be routed to
494
+ # the app #2.
495
+ channel1.prefetch(3)
496
+
497
+ # app #2 processes messages one-by-one and has to send and ack every time
498
+ channel2.prefetch(1)
393
499
  </code>
394
500
  </pre>
395
501
 
502
+ In that example, one consumer prefetches 3 messages and another consumer prefetches just 1. If we take a look at the output that example produces,
503
+ we will see that `consumer1` fetched 4 messages and acknowledged 1. After that, all subsequent messages were delivered to the `consumer2`:
504
+
505
+ <pre>
506
+ [consumer2] Received Message #0, redelivered = false, ack-ed
507
+ [consumer1] Got message #1, SKIPPPED
508
+ [consumer1] Got message #2, SKIPPPED
509
+ [consumer1] Got message #3, ack-ed
510
+ [consumer2] Received Message #4, redelivered = false, ack-ed
511
+ [consumer1] Got message #5, SKIPPPED
512
+ --- by now consumer 1 has received 3 messages it did not acknowledge. With prefetch = 3, AMQP broker won't send it any more messages until consumer 1 sends an ack ---
513
+ [consumer2] Received Message #6, redelivered = false, ack-ed
514
+ [consumer2] Received Message #7, redelivered = false, ack-ed
515
+ [consumer2] Received Message #8, redelivered = false, ack-ed
516
+ [consumer2] Received Message #9, redelivered = false, ack-ed
517
+ [consumer2] Received Message #10, redelivered = false, ack-ed
518
+ [consumer2] Received Message #11, redelivered = false, ack-ed
519
+ </pre>
520
+
521
+ <span class="note">
522
+ Prefetching setting is ignored for consumers that do not use explicit acknowledgements
523
+ </span>
524
+
525
+
526
+
527
+ h2. Fetching messages when needed ("pull API")
528
+
529
+ AMQP 0.9.1 also provides a way for applications to fetch (pull) messages from the queue only when necessary. For that, use
530
+ {AMQP::Queue#pop}:
531
+
532
+ <script src="https://gist.github.com/998732.js"> </script>
396
533
 
397
534
  TBD
398
535
 
@@ -401,163 +538,44 @@ h2. Unsubscribing from messages
401
538
 
402
539
  Sometimes it is necessary to unsubscribe from messages without deleting a queue. To do that, use {AMQP::Queue#unsubscribe} method:
403
540
 
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
- AMQP::Channel.new do |channel, open_ok|
414
- exchange = channel.fanout("amq.fanout")
415
-
416
- channel.queue("", :auto_delete => true, :exclusive => true) do |queue, declare_ok|
417
- queue.bind(exchange).subscribe do |headers, payload|
418
- puts "Received a new message"
419
- end
420
-
421
- EventMachine.add_timer(0.3) do
422
- queue.unsubscribe
423
- puts "Unsubscribed. Shutting down..."
424
-
425
- connection.close {
426
- EM.stop { exit }
427
- }
428
- end # EventMachine.add_timer
429
- end # channel.queue
430
- end
431
- end
432
- </code>
433
- </pre>
541
+ <script src="https://gist.github.com/998734.js"> </script>
434
542
 
435
543
  By default {AMQP::Queue#unsubscribe} uses :noack option to inform broker that there is no need to send a
436
- confirmation. In other words, it does not expect you to pass in a callback, because consumer tag and registered
437
- callbacks are cleared immediately.
544
+ confirmation. In other words, it does not expect you to pass in a callback, because consumer tag on the queue instance and registered
545
+ callback for messages are cleared immediately.
438
546
 
439
547
 
440
548
  h2. Unbinding queues from exchanges
441
549
 
442
550
  To unbind queue from exchange, use {AMQP::Queue#unbind}:
443
551
 
444
- <pre>
445
- <code>
446
- #!/usr/bin/env ruby
447
- # encoding: utf-8
448
-
449
- require "rubygems"
450
- require "amqp"
451
-
452
- AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672") do |connection, open_ok|
453
- puts "Connected"
454
- AMQP::Channel.new(connection) do |channel, open_ok|
455
- puts "Opened a channel"
456
- channel.on_error do |arg|
457
- raise "Channel-level exception!"
458
- end
459
- exchange = channel.fanout("amq.fanout")
460
-
461
- channel.queue("", :auto_delete => true, :exclusive => true) do |queue, declare_ok|
462
- queue.bind(exchange) do |_|
463
- puts "Bound"
464
- end
465
-
466
- EventMachine.add_timer(0.5) do
467
- queue.unbind(exchange) do |_|
468
- puts "Unbound. Shutting down..."
469
-
470
- connection.close {
471
- EM.stop { exit }
472
- }
473
- end
474
- end # EventMachine.add_timer
475
- end # channel.queue
476
- end
477
- end
478
- </code>
479
- </pre>
552
+ <script src="https://gist.github.com/998742.js"> </script>
480
553
 
481
- Note that unbinding an exchange queue was never bound to will result in an exception.
554
+ Note that unbinding an exchange queue was never bound to will result in a channel-level exception.
482
555
 
483
556
 
484
557
  h2. Purging queues
485
558
 
486
- It is possible to purge (remove all messages from) a queue using {AMQP::Queue#purge):
559
+ It is possible to purge a queue (remove all messages from it) using {AMQP::Queue#purge}:
487
560
 
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
- queue.purge do |_|
507
- puts "Queue now has no messages"
508
- end
509
-
510
- EventMachine.add_timer(0.5) do
511
- connection.close {
512
- EM.stop { exit }
513
- }
514
- end # EventMachine.add_timer
515
- end # channel.queue
516
- end
517
- end
518
- </code>
519
- </pre>
561
+ <script src="https://gist.github.com/998743.js"> </script>
562
+
563
+
564
+ This method takes a callback but it is optional. However, remember that this operation is performed asynchronously.
520
565
 
521
- Callback is optional. However, remember that this operation takes some time.
522
566
 
523
567
 
524
568
  h2. Deleting queues
525
569
 
526
570
  To delete a queue, use {AMQP::Queue#delete}:
527
571
 
528
- <pre>
529
- <code>
530
- #!/usr/bin/env ruby
531
- # encoding: utf-8
532
-
533
- require "rubygems"
534
- require "amqp"
535
-
536
- AMQP.start("amqp://guest:guest@dev.rabbitmq.com:5672") do |connection, open_ok|
537
- puts "Connected"
538
- AMQP::Channel.new(connection) do |channel, open_ok|
539
- puts "Opened a channel"
540
- channel.on_error do |arg|
541
- raise "Channel-level exception!"
542
- end
543
- exchange = channel.fanout("amq.fanout")
544
-
545
- channel.queue("", :auto_delete => true, :exclusive => true) do |queue, declare_ok|
546
- EventMachine.add_timer(0.5) do
547
- queue.delete do
548
- puts "Deleted a queue"
549
- connection.close {
550
- EM.stop { exit }
551
- }
552
- end
553
- end # EventMachine.add_timer
554
- end # channel.queue
555
- end
556
- end
557
- </code>
558
- </pre>
572
+ <script src="https://gist.github.com/998744.js"> </script>
573
+
574
+ This method takes a callback but it is optional. However, remember that this operation is performed asynchronously.
575
+
576
+ When queue is deleted, all the messages in it are deleted as well.
577
+
559
578
 
560
- Callback can be omitted. However, remember that this operation takes some time.
561
579
 
562
580
  h2. Queue durability vs Message durability
563
581
 
@@ -567,19 +585,28 @@ See {file:docs/Durability.textile Durability guide}
567
585
 
568
586
  h2. Error handling and recovery
569
587
 
570
- TBD
588
+ See {file:docs/ErrorHandling.textile Error handling and recovery guide}
571
589
 
572
590
 
573
591
 
574
592
  h2. Vendor-specific extensions related to queues
575
593
 
576
- TBD
594
+ See {file:docs/VendorSpecificExtensions.textile Vendor-specific Extensions guide}
577
595
 
578
596
 
579
597
 
580
598
  h2. What to read next
581
599
 
582
- TBD
600
+ Documentation is organized as several {file:docs/DocumentationGuidesIndex.textile documentation guides}, covering all kinds of
601
+ topics. Guides related to this one are
602
+
603
+ * {file:docs/Exchanges.textile Exchanges}
604
+ * {file:docs/Bindings.textile Bindings}
605
+ * {file:docs/ErrorHandling.textile Error handling and recovery}
606
+
607
+ RabbitMQ implements a number of extensions to AMQP 0.9.1 functionality, covered in the {file:docs/VendorSpecificExtensions.textile Vendor-specific Extensions guide}.
608
+ At least one extension, per-queue messages time-to-live (TTL), is related to this guide and can be used with amqp gem 0.8.0 and later.
609
+
583
610
 
584
611
 
585
612
  h2. Tell us what you think!
@@ -589,3 +616,21 @@ what was unclear? what wasn't covered? maybe you don't like guide style or gramm
589
616
  key to making documentation better.
590
617
 
591
618
  If mailing list communication is not an option for you for some reason, you can "contact guides author directly":mailto:michael@novemberain.com?subject=amqp%20gem%20documentation
619
+
620
+
621
+ <div id="disqus_thread"></div>
622
+ <script type="text/javascript">
623
+ /* * * CONFIGURATION VARIABLES * * */
624
+ var disqus_shortname = 'rubyamqpdocs'; // required: replace example with your forum shortname
625
+
626
+ var disqus_developer = 0; // set to 1 on local machine for testing comments
627
+ var disqus_identifier = 'amqp_queues';
628
+ var disqus_url = 'http://rdoc.info/github/ruby-amqp/amqp/master/file/docs/Queues.textile';
629
+
630
+ /* * * DON'T EDIT BELOW THIS LINE * * */
631
+ (function() {
632
+ var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
633
+ dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
634
+ (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
635
+ })();
636
+ </script>