amqp 0.8.0.rc13 → 0.8.0.rc14
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -1
- data/.travis.yml +8 -2
- data/.yardopts +1 -0
- data/CHANGELOG +9 -0
- data/Gemfile +17 -11
- data/README.md +26 -16
- data/amqp.gemspec +2 -2
- data/bin/ci/before_build.sh +21 -0
- data/docs/08Migration.textile +199 -5
- data/docs/AMQP091ModelExplained.textile +322 -0
- data/docs/Bindings.textile +24 -4
- data/docs/Clustering.textile +1 -1
- data/docs/ConnectingToTheBroker.textile +98 -82
- data/docs/ConnectionEncryptionWithTLS.textile +65 -5
- data/docs/DocumentationGuidesIndex.textile +93 -13
- data/docs/Durability.textile +1 -1
- data/docs/ErrorHandling.textile +458 -94
- data/docs/Exchanges.textile +901 -87
- data/docs/GettingStarted.textile +278 -143
- data/docs/PatternsAndUseCases.textile +420 -0
- data/docs/Queues.textile +730 -178
- data/docs/RabbitMQVersions.textile +18 -3
- data/docs/RunningTests.textile +1 -1
- data/docs/TestingWithEventedSpec.textile +121 -0
- data/docs/Troubleshooting.textile +15 -1
- data/docs/VendorSpecificExtensions.textile +1 -1
- data/docs/diagrams/001_hello_world_example_routing.png +0 -0
- data/docs/diagrams/002_blabbr_example_routing.png +0 -0
- data/docs/diagrams/003_weathr_example_routing.png +0 -0
- data/docs/diagrams/004_fanout_exchange.png +0 -0
- data/docs/diagrams/005_direct_exchange.png +0 -0
- data/docs/diagrams/redhat/direct_exchange.png +0 -0
- data/docs/diagrams/redhat/fanout_exchange.png +0 -0
- data/docs/diagrams/redhat/topic_exchange.png +0 -0
- data/examples/error_handling/automatic_recovery_of_channel_and_queues.rb +50 -0
- data/examples/error_handling/automatically_recovering_hello_world_consumer.rb +51 -0
- data/examples/error_handling/automatically_recovering_hello_world_consumer_that_uses_a_server_named_queue.rb +51 -0
- data/examples/error_handling/basic_connection_failover.rb +22 -0
- data/examples/error_handling/channel_level_exception.rb +9 -2
- data/examples/error_handling/connection_level_exception.rb +8 -1
- data/examples/error_handling/connection_level_exception_with_objects.rb +49 -0
- data/examples/error_handling/connection_loss_handler.rb +1 -5
- data/examples/error_handling/hello_world_producer.rb +43 -0
- data/examples/error_handling/insufficient_permissions.rb +54 -0
- data/examples/error_handling/manual_connection_and_channel_recovery.rb +71 -0
- data/examples/error_handling/queue_exclusivity_violation.rb +41 -0
- data/examples/error_handling/queue_name_violation.rb +31 -0
- data/examples/exchanges/autodeletion_of_exchanges.rb +1 -4
- data/examples/guides/queues/01a_declaring_a_server_named_queue_using_queue_constructor.rb +7 -8
- data/examples/guides/queues/01b_declaring_a_queue_using_queue_constructor.rb +7 -8
- data/examples/guides/queues/02a_declaring_a_durable_shared_queue.rb +5 -8
- data/examples/guides/queues/02b_declaring_a_durable_shared_queue.rb +5 -8
- data/examples/guides/queues/03a_declaring_a_temporary_exclusive_queue.rb +7 -8
- data/examples/guides/queues/04_bind_a_queue_using_exchange_instance.rb +9 -10
- data/examples/guides/queues/05_bind_a_queue_using_exchange_name.rb +8 -10
- data/examples/guides/queues/06_subscribe_to_receive_messages.rb +10 -12
- data/examples/guides/queues/07_fetch_a_message_from_the_queue.rb +14 -14
- data/examples/guides/queues/08_unsubscribing_a_consumer.rb +13 -16
- data/examples/guides/queues/09_unbinding_from_exchange.rb +16 -22
- data/examples/guides/queues/10_purge_a_queue.rb +13 -18
- data/examples/guides/queues/11_deleting_a_queue.rb +14 -19
- data/examples/guides/queues/12_objects_that_consume_messages.rb +69 -0
- data/examples/guides/queues/13_objects_that_consume_messages_take_two.rb +89 -0
- data/examples/hello_world.rb +1 -3
- data/examples/hello_world_with_an_empty_string.rb +5 -6
- data/examples/inspecting_server_information.rb +45 -0
- data/examples/issues/issue_93.rb +23 -0
- data/examples/issues/issue_94.rb +23 -0
- data/examples/patterns/command/consumer.rb +45 -0
- data/examples/patterns/command/producer.rb +26 -0
- data/examples/patterns/request_reply/client.rb +29 -0
- data/examples/patterns/request_reply/server.rb +26 -0
- data/examples/publishing/publishing_a_one_off_message.rb +6 -4
- data/examples/publishing/returned_messages.rb +2 -10
- data/examples/queues/accessing_message_metadata.rb +15 -13
- data/examples/queues/queue_status.rb +12 -15
- data/examples/routing/fanout_routing.rb +33 -0
- data/examples/routing/headers_routing.rb +17 -15
- data/examples/routing/round_robin_with_direct_exchange.rb +39 -0
- data/examples/routing/round_robin_with_the_default_exchange.rb +38 -0
- data/examples/routing/unroutable_mandatory_message_is_returned.rb +33 -0
- data/examples/routing/weather_updates.rb +15 -20
- data/examples/tls/using_tls.rb +41 -0
- data/lib/amqp/bit_set.rb +80 -0
- data/lib/amqp/broker.rb +72 -0
- data/lib/amqp/channel.rb +93 -13
- data/lib/amqp/client.rb +11 -22
- data/lib/amqp/compatibility/ruby187_patchlevel_check.rb +2 -0
- data/lib/amqp/connection.rb +2 -3
- data/lib/amqp/consumer.rb +208 -0
- data/lib/amqp/deprecated/fork.rb +2 -0
- data/lib/amqp/deprecated/mq.rb +2 -0
- data/lib/amqp/exchange.rb +6 -4
- data/lib/amqp/extensions/rabbitmq.rb +3 -1
- data/lib/amqp/header.rb +76 -14
- data/lib/amqp/int_allocator.rb +96 -0
- data/lib/amqp/logger.rb +2 -0
- data/lib/amqp/queue.rb +242 -86
- data/lib/amqp/rpc.rb +2 -0
- data/lib/amqp/session.rb +169 -9
- data/lib/amqp/utilities/event_loop_helper.rb +2 -0
- data/lib/amqp/utilities/server_type.rb +2 -0
- data/lib/amqp/version.rb +2 -2
- data/lib/mq.rb +4 -2
- data/lib/mq/logger.rb +3 -1
- data/lib/mq/rpc.rb +3 -1
- data/spec/integration/authentication_spec.rb +17 -10
- data/spec/integration/automatic_binding_for_default_direct_exchange_spec.rb +1 -1
- data/spec/integration/automatic_recovery_predicate_spec.rb +68 -0
- data/spec/integration/basic_get_spec.rb +2 -1
- data/spec/integration/{extensions/basic_return_spec.rb → basic_return_spec.rb} +2 -1
- data/spec/integration/channel_level_exception_handling_spec.rb +53 -0
- data/spec/integration/connection_level_exception_handling_spec.rb +49 -0
- data/spec/integration/declare_and_immediately_bind_a_server_named_queue_spec.rb +38 -17
- data/spec/integration/declare_one_hundred_server_named_queues_spec.rb +44 -0
- data/spec/integration/direct_exchange_routing_spec.rb +125 -0
- data/spec/integration/exchange_declaration_spec.rb +75 -46
- data/spec/integration/extensions/rabbitmq/publisher_confirmations_spec.rb +180 -0
- data/spec/integration/{workload_distribution_spec.rb → fanout_exchange_routing_spec.rb} +10 -9
- data/spec/integration/headers_exchange_routing_spec.rb +269 -0
- data/spec/integration/hello_world_spec.rb +77 -0
- data/spec/integration/immediate_messages_spec.rb +59 -0
- data/spec/integration/mandatory_messages_spec.rb +52 -0
- data/spec/integration/message_metadata_access_spec.rb +106 -0
- data/spec/integration/multiple_consumers_per_queue_spec.rb +319 -0
- data/spec/integration/ordering_of_published_messages_spec.rb +96 -0
- data/spec/integration/queue_declaration_spec.rb +8 -8
- data/spec/integration/queue_status_spec.rb +66 -0
- data/spec/integration/recovery/per_channel_automatic_recovery_on_graceful_broker_shutdown_spec.rb +76 -0
- data/spec/integration/recovery/per_channel_automatic_recovery_spec.rb +72 -0
- data/spec/integration/redelivery_of_unacknowledged_messages_spec.rb +96 -0
- data/spec/integration/regressions/concurrent_publishing_on_the_same_channel_spec.rb +91 -0
- data/spec/integration/regressions/empty_message_body_spec.rb +56 -0
- data/spec/integration/regressions/issue66_spec.rb +2 -1
- data/spec/integration/reply_queue_communication_spec.rb +2 -1
- data/spec/integration/store_and_forward_spec.rb +4 -3
- data/spec/integration/topic_subscription_spec.rb +2 -1
- data/spec/integration/tx_commit_spec.rb +124 -0
- data/spec/integration/tx_rollback_spec.rb +167 -0
- data/spec/spec_helper.rb +44 -71
- data/spec/unit/amqp/bit_set_spec.rb +127 -0
- data/spec/unit/amqp/channel_id_allocation_spec.rb +40 -0
- data/spec/unit/amqp/connection_spec.rb +4 -2
- data/spec/unit/amqp/int_allocator_spec.rb +116 -0
- metadata +92 -26
- data/CONTRIBUTORS +0 -29
- data/docs/Routing.textile +0 -30
- data/examples/real-world/task-queue/README.textile +0 -3
- data/examples/real-world/task-queue/consumer.rb +0 -27
- data/examples/real-world/task-queue/producer.rb +0 -22
- data/spec/unit/amqp/basic_spec.rb +0 -39
- data/tasks.rb +0 -4
data/docs/GettingStarted.textile
CHANGED
@@ -1,28 +1,29 @@
|
|
1
|
-
# @title Ruby
|
1
|
+
# @title Ruby amqp gem: Getting Started with AMQP and Ruby
|
2
2
|
|
3
|
-
h1. Getting started with
|
3
|
+
h1. Getting started with the Ruby amqp gem
|
4
4
|
|
5
5
|
|
6
6
|
h2. About this guide
|
7
7
|
|
8
|
-
This guide is a quick tutorial that helps you to get started with
|
9
|
-
It should take about 20 minutes to read and study provided code examples. This guide covers
|
8
|
+
This guide is a quick tutorial that helps you to get started with v0.9.1 of the AMQP specification in general and the "Ruby amqp gem":http://github.com/ruby-amqp/amqp in particular.
|
9
|
+
It should take about 20 minutes to read and study the provided code examples. This guide covers:
|
10
10
|
|
11
|
-
* Installing RabbitMQ, a mature popular implementation of
|
12
|
-
* Installing amqp gem via "Rubygems":http://rubygems.org and "Bundler":http://gembundler.com.
|
13
|
-
* Running
|
14
|
-
* Creating a "Twitter
|
15
|
-
* Creating a topic routing example with 2 publishers and 8 subscribers
|
11
|
+
* Installing RabbitMQ, a mature popular server implementation of the AMQP protocol.
|
12
|
+
* Installing the amqp gem via "Rubygems":http://rubygems.org and "Bundler":http://gembundler.com.
|
13
|
+
* Running a "Hello, world" messaging example that is a simple demonstration of 1:1 communication.
|
14
|
+
* Creating a "Twitter-like" publish/subscribe example with 1 publisher and 4 subscribers that demonstrates 1:n communication.
|
15
|
+
* Creating a topic routing example with 2 publishers and 8 subscribers showcasing n:m communication when subscribers only receive messages that they are interested in.
|
16
|
+
* Learning how the amqp gem can be integrated with Ruby objects in a way that makes unit testing easy.
|
16
17
|
|
17
18
|
|
18
|
-
h2.
|
19
|
+
h2. Which versions of the amqp gem does this guide cover?
|
19
20
|
|
20
|
-
This guide covers "Ruby amqp gem":
|
21
|
+
This guide covers v0.8.0 and later of the "Ruby amqp gem":https://github.com/ruby-amqp/amqp.
|
21
22
|
|
22
23
|
|
23
24
|
h2. Installing RabbitMQ
|
24
25
|
|
25
|
-
"RabbitMQ site":http://rabbitmq.com has a good "installation guide":http://www.rabbitmq.com/install.html that
|
26
|
+
The "RabbitMQ site":http://rabbitmq.com has a good "installation guide":http://www.rabbitmq.com/install.html that addresses many operating systems.
|
26
27
|
On Mac OS X, the fastest way to install RabbitMQ is with "Homebrew":http://mxcl.github.com/homebrew/:
|
27
28
|
|
28
29
|
<pre>
|
@@ -39,59 +40,56 @@ rabbitmq-server
|
|
39
40
|
</code>
|
40
41
|
</pre>
|
41
42
|
|
42
|
-
On Debian and Ubuntu, you can either "download RabbitMQ .deb package":http://www.rabbitmq.com/server.html and install it with
|
43
|
-
"dpkg":http://www.debian.org/doc/FAQ/ch-pkgtools.en.html or use "apt repository
|
44
|
-
For RPM-based distributions like RedHat or CentOS RabbitMQ team provides an "RPM package":http://www.rabbitmq.com/install.html#rpm.
|
43
|
+
On Debian and Ubuntu, you can either "download the RabbitMQ .deb package":http://www.rabbitmq.com/server.html and install it with
|
44
|
+
"dpkg":http://www.debian.org/doc/FAQ/ch-pkgtools.en.html or make use of the "apt repository":http://www.rabbitmq.com/debian.html#apt that the RabbitMQ team provides.
|
45
|
+
For RPM-based distributions like RedHat or CentOS, the RabbitMQ team provides an "RPM package":http://www.rabbitmq.com/install.html#rpm.
|
45
46
|
|
46
47
|
<span class="note">
|
47
|
-
RabbitMQ package
|
48
|
+
The RabbitMQ package that ships with recent Ubuntu 10.10 versions is outdated and *will not work with v0.8.0 and later of the amqp gem* (we need at least RabbitMQ v2.0 for use with this guide).
|
48
49
|
</span>
|
49
50
|
|
50
51
|
|
51
52
|
|
52
|
-
h2. Installing Ruby amqp gem
|
53
|
+
h2. Installing the Ruby amqp gem
|
53
54
|
|
54
|
-
h3. Make sure you have Ruby installed
|
55
|
+
h3. Make sure that you have Ruby and "Rubygems":http://docs.rubygems.org/read/chapter/3 installed
|
55
56
|
|
56
|
-
This
|
57
|
+
This guide assumes that you have installed one of the following supported Ruby implementations:
|
57
58
|
|
58
|
-
* Ruby
|
59
|
-
* Ruby
|
60
|
-
* JRuby (we recommend
|
61
|
-
* Rubinius
|
59
|
+
* Ruby v1.8.7
|
60
|
+
* Ruby v1.9.2
|
61
|
+
* JRuby (we recommend v1.6)
|
62
|
+
* Rubinius v1.2 or higher
|
62
63
|
* Ruby Enterprise Edition
|
63
64
|
|
65
|
+
h3. You can use Rubygems to install the amqp gem
|
64
66
|
|
65
|
-
|
66
|
-
|
67
|
-
To get amqp gem 0.8.0
|
68
|
-
|
69
|
-
h4. On Microsoft Windows 7
|
67
|
+
h4. On Microsoft Windows 7:
|
70
68
|
|
71
69
|
<pre>
|
72
70
|
gem install eventmachine --pre
|
73
|
-
gem install amqp --pre
|
71
|
+
gem install amqp --pre
|
74
72
|
</pre>
|
75
73
|
|
76
74
|
h4. On other OSes or JRuby:
|
77
75
|
|
78
76
|
<pre>
|
79
|
-
gem install amqp --pre
|
77
|
+
gem install amqp --pre
|
80
78
|
</pre>
|
81
79
|
|
82
|
-
h3.
|
80
|
+
h3. You can also use Bundler to install the gem
|
83
81
|
|
84
82
|
<pre>
|
85
83
|
<code>
|
86
84
|
source :rubygems
|
87
85
|
|
88
|
-
gem "amqp", "~> 0.8.0.
|
86
|
+
gem "amqp", "~> 0.8.0.RC13" # optionally: :git => "git://github.com/ruby-amqp/amqp.git", :branch => "master"
|
89
87
|
</code>
|
90
88
|
</pre>
|
91
89
|
|
92
90
|
h3. Verifying your installation
|
93
91
|
|
94
|
-
|
92
|
+
Let us verify your installation with this quick irb session:
|
95
93
|
|
96
94
|
<pre>
|
97
95
|
<code>
|
@@ -100,21 +98,21 @@ irb -rubygems
|
|
100
98
|
:001 > require "amqp"
|
101
99
|
=> true
|
102
100
|
:002 > AMQP::VERSION
|
103
|
-
=> "0.8.0.
|
101
|
+
=> "0.8.0.rc13"
|
104
102
|
</code>
|
105
103
|
</pre>
|
106
104
|
|
107
105
|
|
108
106
|
h2. "Hello, world" example
|
109
107
|
|
110
|
-
|
108
|
+
Let us begin with the classic "Hello, world" example. First, here is the code:
|
111
109
|
|
112
110
|
<script src="https://gist.github.com/998690.js"> </script>
|
113
111
|
|
114
112
|
(if the example above isn't displayed, see this "gist":https://gist.github.com/998690)
|
115
113
|
|
116
|
-
This example demonstrates a very common communication scenario:
|
117
|
-
a queue that
|
114
|
+
This example demonstrates a very common communication scenario: *application A* wants to publish a message that will end up in
|
115
|
+
a queue that *application B* listens on. In this case, the queue name is "amqpgem.examples.hello". Let us go through the code
|
118
116
|
step by step:
|
119
117
|
|
120
118
|
<pre>
|
@@ -124,7 +122,7 @@ require "amqp"
|
|
124
122
|
</code>
|
125
123
|
</pre>
|
126
124
|
|
127
|
-
is the simplest way to load amqp gem if you have installed it with RubyGems. The following piece of code
|
125
|
+
is the simplest way to load the amqp gem if you have installed it with RubyGems. The following piece of code
|
128
126
|
|
129
127
|
<pre>
|
130
128
|
<code>
|
@@ -134,10 +132,10 @@ end
|
|
134
132
|
</code>
|
135
133
|
</pre>
|
136
134
|
|
137
|
-
runs what is called EventMachine reactor.
|
138
|
-
|
135
|
+
runs what is called the "EventMachine":http://rubyeventmachine.com reactor. We will not go into what the term 'reactor' means here, but suffice it to say
|
136
|
+
that the amqp gem is asynchronous and is based on an asynchronous network I/O library called _EventMachine_.
|
139
137
|
|
140
|
-
|
138
|
+
The next line
|
141
139
|
|
142
140
|
<pre>
|
143
141
|
<code>
|
@@ -145,7 +143,9 @@ connection = AMQP.connect(:host => '127.0.0.1')
|
|
145
143
|
</code>
|
146
144
|
</pre>
|
147
145
|
|
148
|
-
connects to the server running on localhost, with default port, username, password and virtual host.
|
146
|
+
connects to the server running on localhost, with the default port (5672), username (guest), password (guest) and virtual host ('/').
|
147
|
+
|
148
|
+
The next line
|
149
149
|
|
150
150
|
<pre>
|
151
151
|
<code>
|
@@ -153,8 +153,8 @@ channel = AMQP::Channel.new(connection)
|
|
153
153
|
</code>
|
154
154
|
</pre>
|
155
155
|
|
156
|
-
opens
|
157
|
-
|
156
|
+
opens a new _channel_. AMQP is a multi-channeled protocol that uses channels to multiplex a TCP connection.
|
157
|
+
Channels are opened on a connection, therefore the AMQP::Channel constructor takes a connection object as a parameter.
|
158
158
|
|
159
159
|
This line
|
160
160
|
|
@@ -164,11 +164,11 @@ queue = channel.queue("amqpgem.examples.helloworld", :auto_delete => true)
|
|
164
164
|
</code>
|
165
165
|
</pre>
|
166
166
|
|
167
|
-
declares a
|
168
|
-
We
|
169
|
-
consuming messages from
|
167
|
+
declares a _queue_ on the channel that we have just opened. Consumer applications get messages from queues.
|
168
|
+
We declared this queue with the "auto-delete" parameter. Basically, this means that the queue will be deleted when there are no more processes
|
169
|
+
consuming messages from it.
|
170
170
|
|
171
|
-
The next line
|
171
|
+
The next line
|
172
172
|
|
173
173
|
<pre>
|
174
174
|
<code>
|
@@ -176,27 +176,24 @@ exchange = channel.direct("")
|
|
176
176
|
</code>
|
177
177
|
</pre>
|
178
178
|
|
179
|
-
instantiates an
|
180
|
-
according to rules called
|
181
|
-
|
182
|
-
into that,
|
179
|
+
instantiates an _exchange_. Exchanges receive messages that are sent by producers. Exchanges route messages to queues
|
180
|
+
according to rules called _bindings_. In this particular example, there are no explicitly defined bindings.
|
181
|
+
The exchange that we defined is known as the _default exchange_ and it has implied bindings to all queues. Before we get
|
182
|
+
into that, let us see how we define a handler for incoming messages
|
183
183
|
|
184
184
|
<pre>
|
185
185
|
<code>
|
186
186
|
queue.subscribe do |payload|
|
187
187
|
puts "Received a message: #{payload}. Disconnecting..."
|
188
|
-
|
189
|
-
connection.close {
|
190
|
-
EM.stop { exit }
|
191
|
-
}
|
188
|
+
connection.close { EventMachine.stop }
|
192
189
|
end
|
193
190
|
</code>
|
194
191
|
</pre>
|
195
192
|
|
196
|
-
{AMQP::Queue#subscribe} takes a block that will be called every time a message arrives. {AMQP::Session#close} closes
|
197
|
-
AMQP connection and runs a callback that stops EventMachine reactor.
|
193
|
+
{AMQP::Queue#subscribe} takes a block that will be called every time a message arrives. {AMQP::Session#close} closes the
|
194
|
+
AMQP connection and runs a callback that stops the EventMachine reactor.
|
198
195
|
|
199
|
-
Finally, we publish our message
|
196
|
+
Finally, we publish our message
|
200
197
|
|
201
198
|
<pre>
|
202
199
|
<code>
|
@@ -204,40 +201,40 @@ exchange.publish "Hello, world!", :routing_key => queue.name
|
|
204
201
|
</code>
|
205
202
|
</pre>
|
206
203
|
|
207
|
-
Routing key is one of _message attributes_.
|
208
|
-
as message's routing key. This is how our message ends up in amqpgem.examples.helloworld queue.
|
204
|
+
Routing key is one of the _message attributes_. The default exchange will route the message to a queue that has the same name
|
205
|
+
as the message's routing key. This is how our message ends up in the "amqpgem.examples.helloworld" queue.
|
209
206
|
|
210
|
-
This first example can be modified to use method chaining technique:
|
207
|
+
This first example can be modified to use the method chaining technique:
|
211
208
|
|
212
209
|
<script src="https://gist.github.com/998691.js"> </script>
|
213
210
|
|
214
|
-
|
211
|
+
This diagram demonstrates the "Hello, world" example data flow:
|
212
|
+
|
213
|
+
!https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/001_hello_world_example_routing.png!
|
215
214
|
|
216
|
-
|
217
|
-
sophisticated
|
215
|
+
For the sake of simplicity, both the message producer (App I) and the consumer (App II) are running in the same Ruby process.
|
216
|
+
Now let us move on to a little bit more sophisticated example.
|
218
217
|
|
219
218
|
|
220
|
-
h2. Blabblr: one-to-many publish/subscribe example
|
219
|
+
h2. Blabblr: one-to-many publish/subscribe (pubsub) example
|
221
220
|
|
222
|
-
|
223
|
-
using default exchange. Now
|
221
|
+
The previous example demonstrated how a connection to a broker is made and how to do 1:1 communication
|
222
|
+
using the default exchange. Now let us take a look at another common scenario: broadcast, or multiple consumers
|
224
223
|
and one producer.
|
225
224
|
|
226
|
-
A very well
|
225
|
+
A very well-known broadcast example is Twitter: every time a person tweets, followers receive a notification.
|
227
226
|
Blabbr, our imaginary information network, models this scenario: every network member has a separate
|
228
|
-
queue and publishes blabs to a separate exchange. 3 Blabbr members, Joe, Aaron and Bob, follow official NBA
|
229
|
-
account on Blabbr to get updates about what is
|
227
|
+
queue and publishes blabs to a separate exchange. 3 Blabbr members, Joe, Aaron and Bob, follow the official NBA
|
228
|
+
account on Blabbr to get updates about what is happening in the world of basketball. Here is the code:
|
230
229
|
|
231
230
|
<script src="https://gist.github.com/998692.js"> </script>
|
232
231
|
|
233
|
-
|
234
|
-
|
235
|
-
First line has a few difference from "Hello, world" example above:
|
232
|
+
The first line has a few differences from the "Hello, world" example above:
|
236
233
|
|
237
234
|
* We use {AMQP.start} instead of {AMQP.connect}
|
238
|
-
* Instead of return values, we pass connection method
|
239
|
-
object back as soon as connection is established.
|
240
|
-
* Instead of passing connection parameters as a hash, we
|
235
|
+
* Instead of return values, we pass a block to the connection method and it yields a connection
|
236
|
+
object back as soon as the connection is established.
|
237
|
+
* Instead of passing connection parameters as a hash, we use a URI string.
|
241
238
|
|
242
239
|
{AMQP.start} is just a convenient way to do
|
243
240
|
|
@@ -251,15 +248,15 @@ end
|
|
251
248
|
</code>
|
252
249
|
</pre>
|
253
250
|
|
254
|
-
{AMQP.start} call blocks current thread
|
251
|
+
The {AMQP.start} call blocks the current thread which means that its use is limited to scripts and small command
|
255
252
|
line applications. Blabbr is just that.
|
256
253
|
|
257
|
-
{AMQP.connect}, when invoked with a block, will yield connection object
|
258
|
-
is open. Finally, connection parameters
|
259
|
-
method documentation
|
254
|
+
{AMQP.connect}, when invoked with a block, will yield a connection object as soon as the AMQP connection
|
255
|
+
is open. Finally, connection parameters may be supplied as a Hash or as a connection string. The {AMQP.connect}
|
256
|
+
method documentation contains all of the details.
|
260
257
|
|
261
|
-
|
262
|
-
|
258
|
+
In this example, opening a channel is no different to opening a channel in the previous example,
|
259
|
+
however, the exchange is declared differently
|
263
260
|
|
264
261
|
<pre>
|
265
262
|
<code>
|
@@ -267,8 +264,8 @@ exchange = channel.fanout("nba.scores")
|
|
267
264
|
</code>
|
268
265
|
</pre>
|
269
266
|
|
270
|
-
|
271
|
-
|
267
|
+
The exchange that we declare above using {AMQP::Channel#fanout} is a _fanout exchange_. A fanout exchange delivers messages to all of the queues that
|
268
|
+
are bound to it: exactly what we want in the case of Blabbr!
|
272
269
|
|
273
270
|
This piece of code
|
274
271
|
|
@@ -280,9 +277,9 @@ end
|
|
280
277
|
</code>
|
281
278
|
</pre>
|
282
279
|
|
283
|
-
is similar to
|
284
|
-
method do? It sets up a
|
285
|
-
to make sure that our fanout exchange routes messages to
|
280
|
+
is similar to the subscription code that we used for message delivery previously, but what does that {AMQP::Queue#bind}
|
281
|
+
method do? It sets up a binding between the queue and the exchange that you pass to it. We need to do this
|
282
|
+
to make sure that our fanout exchange routes messages to the queues of any subscribed followers.
|
286
283
|
|
287
284
|
<pre>
|
288
285
|
<code>
|
@@ -290,9 +287,13 @@ exchange.publish("BOS 101, NYK 89").publish("ORL 85, ALT 88")
|
|
290
287
|
</code>
|
291
288
|
</pre>
|
292
289
|
|
293
|
-
demonstrates {AMQP::Exchange#publish}
|
294
|
-
for publishing, there is no need to specify routing key
|
295
|
-
|
290
|
+
demonstrates {AMQP::Exchange#publish} call chaining. Blabbr members use a fanout exchange
|
291
|
+
for publishing, so there is no need to specify a message routing key because every queue that is bound to the exchange will get its own copy of all messages, regardless of the queue name and routing key used.
|
292
|
+
|
293
|
+
A diagram for Blabbr looks like this:
|
294
|
+
|
295
|
+
!https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/002_blabbr_example_routing.png!
|
296
|
+
|
296
297
|
|
297
298
|
Next we use EventMachine's {http://eventmachine.rubyforge.org/EventMachine.html#M000466 add_timer} method to
|
298
299
|
run a piece of code in 1 second from now:
|
@@ -302,44 +303,40 @@ run a piece of code in 1 second from now:
|
|
302
303
|
EventMachine.add_timer(1) do
|
303
304
|
exchange.delete
|
304
305
|
|
305
|
-
connection.close {
|
306
|
-
EM.stop { exit }
|
307
|
-
}
|
306
|
+
connection.close { EventMachine.stop }
|
308
307
|
end
|
309
308
|
</code>
|
310
309
|
</pre>
|
311
310
|
|
312
|
-
The code we want to run deletes exchange we declared earlier using {AMQP::Exchange#delete} and closes AMQP
|
313
|
-
connection with {AMQP::Session#close}. Finally, we stop EventMachine event loop and exit.
|
311
|
+
The code that we want to run deletes the exchange that we declared earlier using {AMQP::Exchange#delete} and closes the AMQP
|
312
|
+
connection with {AMQP::Session#close}. Finally, we stop the EventMachine event loop and exit.
|
314
313
|
|
315
|
-
Blabbr is pretty unlikely to secure hundreds of millions in funding but it does a pretty good job of
|
314
|
+
Blabbr is pretty unlikely to secure hundreds of millions of dollars in funding, but it does a pretty good job of
|
316
315
|
demonstrating how one can use AMQP fanout exchanges to do broadcasting.
|
317
316
|
|
318
317
|
|
319
318
|
|
320
319
|
h2. Weathr: many-to-many topic routing example
|
321
320
|
|
322
|
-
So far we have seen point-to-point communication and
|
323
|
-
HTTP handles these scenarios just fine.
|
324
|
-
|
321
|
+
So far, we have seen point-to-point communication and broadcasting. Those two communication styles are possible with many protocols, for instance,
|
322
|
+
HTTP handles these scenarios just fine. You may ask "what differentiates AMQP?" Well, next we are going to introduce you to _topic
|
323
|
+
exchanges_ and routing with patterns, one of the features that makes AMQP very powerful.
|
325
324
|
|
326
|
-
Our third example
|
327
|
-
not all consumers are interested in all messages
|
328
|
-
weather in Hong Kong
|
325
|
+
Our third example involves weather condition updates. What makes it different from the previous two examples is that
|
326
|
+
not all of the consumers are interested in all of the messages. People who live in Portland usually do not care about
|
327
|
+
the weather in Hong Kong (unless they are visiting soon). They are much more interested in
|
329
328
|
weather conditions around Portland, possibly all of Oregon and sometimes a few neighbouring states.
|
330
329
|
|
331
330
|
Our example features multiple consumer applications monitoring updates for different regions. Some are
|
332
|
-
interested in updates for a specific city, others for a specific state and so on all the way up to continents.
|
333
|
-
Updates may overlap
|
334
|
-
on North America updates list.
|
331
|
+
interested in updates for a specific city, others for a specific state and so on, all the way up to continents.
|
332
|
+
Updates may overlap so that an update for San Diego, CA appears as an update for California, but also should show up
|
333
|
+
on the North America updates list.
|
335
334
|
|
336
335
|
Here is the code:
|
337
336
|
|
338
337
|
<script src="https://gist.github.com/998694.js"> </script>
|
339
338
|
|
340
|
-
|
341
|
-
|
342
|
-
First line that is different from Blabbr example is
|
339
|
+
The first line that is different from the Blabbr example is
|
343
340
|
|
344
341
|
<pre>
|
345
342
|
<code>
|
@@ -347,9 +344,9 @@ exchange = channel.topic("pub/sub", :auto_delete => true)
|
|
347
344
|
</code>
|
348
345
|
</pre>
|
349
346
|
|
350
|
-
We use a
|
351
|
-
where consumers indicate
|
352
|
-
|
347
|
+
We use a topic exchange here. Topic exchanges are used for "multicast":http://en.wikipedia.org/wiki/Multicast messaging
|
348
|
+
where consumers indicate which topics they are interested in (think of it as subscribing to a feed for an individual tag
|
349
|
+
in your favourite blog as opposed to the full feed). Routing with a topic exchange is done by specifying a _routing pattern_ on binding, for example:
|
353
350
|
|
354
351
|
<pre>
|
355
352
|
<code>
|
@@ -359,23 +356,25 @@ end
|
|
359
356
|
</code>
|
360
357
|
</pre>
|
361
358
|
|
362
|
-
Here we bind a queue with the name of "americas.south" to the topic exchange declared earlier using {AMQP::Queue#bind} method.
|
363
|
-
This means that only messages with routing key matching americas.south.# will be routed to that queue.
|
364
|
-
separated by dots,
|
359
|
+
Here we bind a queue with the name of "americas.south" to the topic exchange declared earlier using the {AMQP::Queue#bind} method.
|
360
|
+
This means that only messages with a routing key matching "americas.south.#" will be routed to that queue. A routing pattern consists of several words
|
361
|
+
separated by dots, in a similar way to URI path segments joined by slashes. Here are a few examples:
|
365
362
|
|
366
363
|
* asia.southeast.thailand.bangkok
|
367
364
|
* sports.basketball
|
368
365
|
* usa.nasdaq.aapl
|
369
366
|
* tasks.search.indexing.accounts
|
370
367
|
|
371
|
-
Now
|
368
|
+
Now let us take a look at a few routing keys that match the "americas.south.#" pattern:
|
372
369
|
|
373
370
|
* americas.south
|
374
371
|
* americas.south.*brazil*
|
375
372
|
* americas.south.*brazil.saopaolo*
|
376
373
|
* americas.south.*chile.santiago*
|
377
374
|
|
378
|
-
In other words, # part of the pattern matches 0 or more words.
|
375
|
+
In other words, the "#" part of the pattern matches 0 or more words.
|
376
|
+
|
377
|
+
For a pattern like "americas.south.*", some matching routing keys would be:
|
379
378
|
|
380
379
|
* americas.south.*brazil*
|
381
380
|
* americas.south.*chile*
|
@@ -386,24 +385,29 @@ but not
|
|
386
385
|
* americas.south
|
387
386
|
* americas.south.chile.santiago
|
388
387
|
|
389
|
-
so * matches a single word
|
388
|
+
so "*" only matches a single word. The AMQP v0.9.1 specification says that topic segments (words) may contain the letters A-Z and a-z
|
390
389
|
and digits 0-9.
|
391
390
|
|
391
|
+
A (very simplistic) diagram to demonstrate topic exchange in action:
|
392
|
+
|
393
|
+
!https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/003_weathr_example_routing.png!
|
394
|
+
|
395
|
+
|
392
396
|
One more thing that is different from previous examples is that the block we pass to {AMQP::Queue#subscribe} now takes two arguments:
|
393
|
-
|
394
|
-
examples of message metadata attributes are
|
397
|
+
a _header_ and a _body_ (often called the _payload_). Long story short, the header parameter lets you access metadata associated with the message. Some
|
398
|
+
examples of message metadata attributes are:
|
395
399
|
|
396
400
|
* message content type
|
397
401
|
* message content encoding
|
398
402
|
* message priority
|
399
403
|
* message expiration time
|
400
404
|
* message identifier
|
401
|
-
* reply to
|
402
|
-
* application id
|
405
|
+
* reply to (specifies which message this is a reply to)
|
406
|
+
* application id (identifier of the application that produced the message)
|
403
407
|
|
404
408
|
and so on.
|
405
409
|
|
406
|
-
As
|
410
|
+
As the following binding demonstrates, "#" and "*" can also appear at the beginning of routing patterns:
|
407
411
|
|
408
412
|
<pre>
|
409
413
|
<code>
|
@@ -413,9 +417,9 @@ end
|
|
413
417
|
</code>
|
414
418
|
</pre>
|
415
419
|
|
416
|
-
|
417
|
-
message published with routing key of "americas.north.us.ca.berkeley"
|
418
|
-
_server-named queue_ we declared by passing blank string as the name:
|
420
|
+
For this example the publishing of messages is no different from that of previous examples. If we were to run the program,
|
421
|
+
a message published with a routing key of "americas.north.us.ca.berkeley" would be routed to 2 queues: "us.california" and the
|
422
|
+
_server-named queue_ that we declared by passing a blank string as the name:
|
419
423
|
|
420
424
|
<pre>
|
421
425
|
<code>
|
@@ -427,49 +431,180 @@ end
|
|
427
431
|
</code>
|
428
432
|
</pre>
|
429
433
|
|
430
|
-
|
431
|
-
Because
|
434
|
+
The name of the server-named queue is generated by the broker and sent back to the client with a queue declaration confirmation.
|
435
|
+
Because the queue name is not known before the reply arrives, we pass {AMQP::Channel#queue} a callback and it yields us back
|
432
436
|
a queue object once confirmation has arrived.
|
433
437
|
|
434
438
|
|
435
439
|
h3. Avoid race conditions
|
436
440
|
|
437
441
|
A word of warning: you may find examples on the Web of {AMQP::Channel#queue} usage that do not use
|
438
|
-
|
439
|
-
to "race conditions":http://en.wikipedia.org/wiki/Race_condition
|
440
|
-
|
442
|
+
callbacks. We *recommend that you use a callback for server-named queues*, otherwise your code may be subject
|
443
|
+
to "race conditions":http://en.wikipedia.org/wiki/Race_condition. Even though the amqp gem tries to be reasonably smart and protect you from most common problems
|
444
|
+
(for example, binding operations will be delayed until after queue name is received from the broker), there
|
445
|
+
is no way it can do so for every case. The primary reason for supporting {AMQP::Channel#queue} usage without a callback for server-named queues is
|
441
446
|
backwards compatibility with earlier versions.
|
442
447
|
|
443
448
|
|
449
|
+
|
450
|
+
h2. Integration with objects
|
451
|
+
|
452
|
+
Since Ruby is a genuine object-oriented language, it is important to demonstrate how the Ruby amqp gem can be integrated
|
453
|
+
into rich object-oriented code.
|
454
|
+
|
455
|
+
The {AMQP::Queue#subscribe} callback does not have to be a block. It can be any Ruby object that responds to `call`.
|
456
|
+
A common technique is to combine {http://rubydoc.info/stdlib/core/1.8.7/Object:method Object#method} and {http://rubydoc.info/stdlib/core/1.8.7/Method:to_proc Method#to_proc}
|
457
|
+
and use object methods as message handlers.
|
458
|
+
|
459
|
+
An example to demonstrate this technique:
|
460
|
+
|
461
|
+
<pre>
|
462
|
+
<code>
|
463
|
+
class Consumer
|
464
|
+
|
465
|
+
#
|
466
|
+
# API
|
467
|
+
#
|
468
|
+
|
469
|
+
def handle_message(metadata, payload)
|
470
|
+
puts "Received a message: #{payload}, content_type = #{metadata.content_type}"
|
471
|
+
end # handle_message(metadata, payload)
|
472
|
+
end
|
473
|
+
|
474
|
+
|
475
|
+
class Worker
|
476
|
+
|
477
|
+
#
|
478
|
+
# API
|
479
|
+
#
|
480
|
+
|
481
|
+
|
482
|
+
def initialize(channel, queue_name = AMQ::Protocol::EMPTY_STRING, consumer = Consumer.new)
|
483
|
+
@queue_name = queue_name
|
484
|
+
|
485
|
+
@channel = channel
|
486
|
+
@channel.on_error(&method(:handle_channel_exception))
|
487
|
+
|
488
|
+
@consumer = consumer
|
489
|
+
end # initialize
|
490
|
+
|
491
|
+
def start
|
492
|
+
@queue = @channel.queue(@queue_name, :exclusive => true)
|
493
|
+
@queue.subscribe(&@consumer.method(:handle_message))
|
494
|
+
end # start
|
495
|
+
|
496
|
+
|
497
|
+
|
498
|
+
#
|
499
|
+
# Implementation
|
500
|
+
#
|
501
|
+
|
502
|
+
def handle_channel_exception(channel, channel_close)
|
503
|
+
puts "Oops... a channel-level exception: code = #{channel_close.reply_code}, message = #{channel_close.reply_text}"
|
504
|
+
end # handle_channel_exception(channel, channel_close)
|
505
|
+
end
|
506
|
+
</code>
|
507
|
+
</pre>
|
508
|
+
|
509
|
+
The "Hello, world" example can be ported to use this technique:
|
510
|
+
<script src="https://gist.github.com/1009447.js"> </script>
|
511
|
+
|
512
|
+
The most important line in this example is
|
513
|
+
|
514
|
+
<pre>
|
515
|
+
<code>
|
516
|
+
@queue.subscribe(&@consumer.method(:handle_message))
|
517
|
+
</code>
|
518
|
+
</pre>
|
519
|
+
|
520
|
+
Ampersand (&) preceding an object is equivalent to calling the #to_proc method on it. We obtain a Consumer#handle_message method reference
|
521
|
+
with
|
522
|
+
|
523
|
+
<pre>
|
524
|
+
<code>
|
525
|
+
@consumer.method(:handle_message)
|
526
|
+
</code>
|
527
|
+
</pre>
|
528
|
+
|
529
|
+
and then the ampersand calls #to_proc on it. {AMQP::Queue#subscribe} then will be using this Proc instance to handle incoming messages.
|
530
|
+
|
531
|
+
|
532
|
+
Note that the `Consumer` class above can be easily tested in isolation, without spinning up any AMQP connections.
|
533
|
+
Here is one example using "RSpec":http://relishapp.com/rspec
|
534
|
+
|
535
|
+
<pre>
|
536
|
+
<code>
|
537
|
+
require "ostruct"
|
538
|
+
require "json"
|
539
|
+
|
540
|
+
# RSpec example
|
541
|
+
describe Consumer do
|
542
|
+
describe "when a new message arrives" do
|
543
|
+
subject { described_class.new }
|
544
|
+
|
545
|
+
let(:metadata) do
|
546
|
+
o = OpenStruct.new
|
547
|
+
|
548
|
+
o.content_type = "application/json"
|
549
|
+
o
|
550
|
+
end
|
551
|
+
let(:payload) { JSON.encode({ :command => "reload_config" }) }
|
552
|
+
|
553
|
+
it "does some useful work" do
|
554
|
+
# check preconditions here if necessary
|
555
|
+
|
556
|
+
subject.handle_message(metadata, payload)
|
557
|
+
|
558
|
+
# add your code expectations here
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
</code>
|
563
|
+
</pre>
|
564
|
+
|
565
|
+
|
444
566
|
h2. Wrapping up
|
445
567
|
|
446
|
-
This
|
568
|
+
This is the end of the tutorial. Congratulations! You have learned quite a bit about both AMQP v0.9.1 and the amqp gem. This is only
|
569
|
+
the tip of the iceberg. AMQP has many more features built into the protocol:
|
570
|
+
|
571
|
+
* Reliable delivery of messages
|
572
|
+
* Message confirmations (a way to tell broker that a message was or was not processed successfully)
|
573
|
+
* Message redelivery when consumer applications fail or crash
|
574
|
+
* Load balancing of messages between multiple consumers
|
575
|
+
* Message metadata attributes
|
576
|
+
|
577
|
+
and so on. Other guides explain these features in depth, as well as use cases for them. To stay up to date with amqp gem
|
578
|
+
development, "follow @rubyamqp on Twitter":http://twitter.com/rubyamqp and "join our mailing list":http://groups.google.com/group/ruby-amqp.
|
579
|
+
|
580
|
+
|
447
581
|
|
448
582
|
|
449
583
|
h2. What to read next
|
450
584
|
|
451
585
|
Documentation is organized as a number of {file:docs/DocumentationGuidesIndex.textile documentation guides}, covering all kinds of
|
452
|
-
topics from {file:docs/
|
586
|
+
topics from {file:docs/Exchanges.textile use cases for various exchange types} to {file:docs/ErrorHandling.textile error handling} and
|
453
587
|
{file:docs/VendorSpecificExchanges.textile Broker-specific AMQP 0.9.1 extensions}.
|
454
588
|
|
455
|
-
|
589
|
+
We recommend that you read the following guides next, if possible, in this order:
|
456
590
|
|
457
|
-
* {file:docs/ConnectingToTheBroker.textile Connection to the broker}
|
458
|
-
* {file:docs/Queues.textile Queues}
|
459
|
-
* {file:docs/Exchanges.textile Exchanges}
|
460
|
-
* {file:docs/
|
591
|
+
* {file:docs/ConnectingToTheBroker.textile Connection to the broker}. This guide explains how to connect to an AMQP broker and how to integrate the amqp gem into standalone and Web applications.
|
592
|
+
* {file:docs/Queues.textile Working With Queues}. This guide focuses on features that consumer applications use heavily.
|
593
|
+
* {file:docs/Exchanges.textile Working With Exchanges}. This guide focuses on features that producer applications use heavily.
|
594
|
+
* {file:docs/PatternsAndUseCases.textile Patterns & Use Cases}. This guide focuses implementation of "common messaging patterns":http://www.eaipatterns.com/ using AMQP Model features as building blocks.
|
595
|
+
* {file:docs/ErrorHandling.textile Error Handling & Recovery}. This guide explains how to handle protocol errors, network failures and other things that may go wrong in real world projects.
|
461
596
|
|
462
|
-
If you are migrating your application from earlier versions of amqp gem (0.6.x and 0.7.x), to 0.8.x and later, there is
|
597
|
+
If you are migrating your application from earlier versions of the amqp gem (0.6.x and 0.7.x), to 0.8.x and later, there is the
|
463
598
|
{file:docs/08Migration.textile amqp gem 0.8 migration guide}.
|
464
599
|
|
465
600
|
|
466
601
|
h2. Tell us what you think!
|
467
602
|
|
468
|
-
Please take a moment
|
469
|
-
what was unclear
|
470
|
-
key to making documentation better.
|
603
|
+
Please take a moment to tell us what you think about this guide "on Twitter":http://twitter.com/rubyamqp or the "Ruby AMQP mailing list":http://groups.google.com/group/ruby-amqp.
|
604
|
+
Let us know what was unclear or what has not been covered. Maybe you do not like the guide style or grammar or discover spelling mistakes. Reader feedback is
|
605
|
+
key to making the documentation better.
|
471
606
|
|
472
|
-
If
|
607
|
+
If, for some reason, you cannot use the communication channels mentioned above, you can "contact the author of the guides directly":mailto:michael@novemberain.com?subject=amqp%20gem%20documentation
|
473
608
|
|
474
609
|
|
475
610
|
<div id="disqus_thread"></div>
|