… | |
… | |
181 | |
181 | |
182 | use AnyEvent; |
182 | use AnyEvent; |
183 | use AnyEvent::MP; |
183 | use AnyEvent::MP; |
184 | use AnyEvent::MP::Global; |
184 | use AnyEvent::MP::Global; |
185 | |
185 | |
186 | initialise_node "eg_simple_receiver"; |
186 | configure nodeid => "eg_receiver", binds => ["*:4040"]; |
187 | |
187 | |
188 | my $port = port; |
188 | my $port = port; |
189 | |
189 | |
190 | AnyEvent::MP::Global::register $port, "eg_receivers"; |
190 | AnyEvent::MP::Global::register $port, "eg_receivers"; |
191 | |
191 | |
… | |
… | |
219 | have a few database backends, a few web frontends and some processing |
219 | have a few database backends, a few web frontends and some processing |
220 | distributed over a number of hosts: all of these would simply register |
220 | distributed over a number of hosts: all of these would simply register |
221 | themselves in the appropriate group, and your web frontends can start to |
221 | themselves in the appropriate group, and your web frontends can start to |
222 | find some database backend. |
222 | find some database backend. |
223 | |
223 | |
224 | =head3 C<initialise_node> And The Network |
224 | =head3 C<configure> and the Network |
225 | |
225 | |
226 | Now, let's have a look at the new function, C<initialise_node>: |
226 | Now, let's have a look at the new function, C<configure>: |
227 | |
227 | |
228 | initialise_node "eg_simple_receiver"; |
228 | configure nodeid => "eg_receiver", binds => ["*:4040"]; |
229 | |
229 | |
230 | Before we are able to send messages to other nodes we have to initialise |
230 | Before we are able to send messages to other nodes we have to initialise |
231 | ourself to become a "distributed node". Initialising a node means naming |
231 | ourself to become a "distributed node". Initialising a node means naming |
232 | the node, optionally binding some TCP listeners so that other nodes can |
232 | the node, optionally binding some TCP listeners so that other nodes can |
233 | contact it and connecting to a predefined set of seed addresses so the |
233 | contact it and connecting to a predefined set of seed addresses so the |
234 | node can discover the existing network - and the existing network can |
234 | node can discover the existing network - and the existing network can |
235 | discover the node! |
235 | discover the node! |
236 | |
236 | |
237 | The first argument, the string C<"eg_simple_receiver">, is the so-called |
237 | All of this (and more) can be passed to the C<configure> function - later |
238 | I<profile> to use: A profile holds some information about the application |
238 | we will see how we can do all this without even passing anything to |
239 | that is going to be a node in an L<AnyEvent::MP> network. Customarily you |
239 | C<configure>! |
240 | don't specify a profile name at all: in this case, AnyEvent::MP will use |
|
|
241 | the POSIX nodename. |
|
|
242 | |
240 | |
243 | The profile allows you to set the I<node ID> that your application will |
241 | The first parameter, C<nodeid>, specified the node ID (in this case |
244 | use (the node ID defaults to the profile name if not specified). You can |
242 | C<eg_receiver> - the default is to use the node name of the current host, |
245 | also set I<binds> in the profile, meaning that you can define TCP ports |
243 | but for this example we want to be able to run many nodes on the same |
246 | that the application will listen on for incoming connections from other |
244 | machine). Node IDs need to be unique within the network and can be almost |
247 | nodes of the network. |
245 | any string - if you don't care, you can specify a node ID of C<anon/> |
|
|
246 | which will then be replaced by a random node name. |
248 | |
247 | |
249 | You should also configure I<seeds> in the profile: A I<seed> is just a |
248 | The second parameter, C<binds>, specifies a list of C<address:port> pairs |
250 | TCP address of some other node in the network. To explain this a bit |
249 | to bind TCP listeners on. The special "address" of C<*> means to bind on |
251 | more detailed we have to look at the topology of an L<AnyEvent::MP> |
250 | every local IP address. |
|
|
251 | |
|
|
252 | The reason to bind on a TCP port is not just that other nodes can connect |
|
|
253 | to us: if no binds are specified, the node will still bind on a dynamic |
|
|
254 | port on all local addresses - but in this case we won't know the port, and |
|
|
255 | cannot tell other nodes to connect to it as seed node. |
|
|
256 | |
|
|
257 | A I<seed> is a (fixed) TCP address of some other node in the network. To |
|
|
258 | explain the need for seeds we have to look at the topology of a typical |
252 | network. The topology is called a I<fully connected mesh>, here an example |
259 | L<AnyEvent::MP> network. The topology is called a I<fully connected mesh>, |
253 | with 4 nodes: |
260 | here an example with 4 nodes: |
254 | |
261 | |
255 | N1--N2 |
262 | N1--N2 |
256 | | \/ | |
263 | | \/ | |
257 | | /\ | |
264 | | /\ | |
258 | N3--N4 |
265 | N3--N4 |
259 | |
266 | |
260 | Now imagine another I<node> C<N5>. wants to connect itself to that network: |
267 | Now imagine another node - C<N5> - wants to connect itself to that network: |
261 | |
268 | |
262 | N1--N2 |
269 | N1--N2 |
263 | | \/ | N5 |
270 | | \/ | N5 |
264 | | /\ | |
271 | | /\ | |
265 | N3--N4 |
272 | N3--N4 |
… | |
… | |
284 | N3--N4--- | |
291 | N3--N4--- | |
285 | \________/ |
292 | \________/ |
286 | |
293 | |
287 | All done: C<N5> is now happily connected to the rest of the network. |
294 | All done: C<N5> is now happily connected to the rest of the network. |
288 | |
295 | |
289 | =head3 Setting Up The Profiles |
296 | Of course, this process takes time, during which the node is already |
|
|
297 | running. This also means it takes time until the node is fully connected, |
|
|
298 | and global groups and other information is available. The best way to deal |
|
|
299 | with this is to either retry regularly until you found the resource you |
|
|
300 | were looking for, or to only start services on demand after a node has |
|
|
301 | become available. |
290 | |
302 | |
291 | Ok, so much to the profile. Now let's setup the C<eg_simple_receiver> |
|
|
292 | I<profile> for later use. For the receiver we just give the receiver a |
|
|
293 | I<bind>: |
|
|
294 | |
|
|
295 | aemp profile eg_simple_receiver setbinds localhost:12266 |
|
|
296 | |
|
|
297 | We use C<localhost> in the example, but in the real world, you usually |
|
|
298 | want to use the "real" IP address of your node, so hosts can connect to |
|
|
299 | it. Of course, you can specify many binds, and it is also perfectly useful |
|
|
300 | to run multiple nodes on the same host. Just keep in mind that other nodes |
|
|
301 | will try to I<connect> to those addresses, and this better succeeds if you |
|
|
302 | want your network to be in good working conditions. |
|
|
303 | |
|
|
304 | While we are at it, we setup the I<profile> for the sender in the |
|
|
305 | second part of this example, too. We will call the sender I<profile> |
|
|
306 | C<eg_simple_sender>. For the sender we set up a I<seed> pointing to the |
|
|
307 | receiver: |
|
|
308 | |
|
|
309 | aemp profile eg_simple_sender setseeds localhost:12266 |
|
|
310 | aemp profile eg_simple_sender setbinds |
|
|
311 | |
|
|
312 | You might wonder why we setup I<binds> to be empty here: actually, the the |
|
|
313 | I<fully> in the I<fully connected mesh> is not the complete truth: If you |
|
|
314 | don't configure any I<binds> for a node profile it will parse and try to |
|
|
315 | resolve the node ID to find addresses to bind to. In this case we pretend |
|
|
316 | that we do not want this and epxlicitly specify an empty binds list, so |
|
|
317 | the node will not actually listen on any TCP ports. |
|
|
318 | |
|
|
319 | Nodes without listeners will not be able to send messages to other nodes |
|
|
320 | without listeners, but they can still talk to all other nodes. For this |
|
|
321 | example, as well as in many cases in the real world, we can live with this |
|
|
322 | restriction, and this makes it easier to avoid DNS (assuming your setup is |
|
|
323 | broken, eliminating one potential problem :). |
|
|
324 | |
|
|
325 | Whee, setting up nodes can be complicated at first, but you only have to |
|
|
326 | do it once per network, and you can leave this boring task to the admins |
|
|
327 | or end-users that want to use your stuff :) |
|
|
328 | |
|
|
329 | =head3 Registering The Receiver |
303 | =head3 Registering the Receiver |
330 | |
304 | |
331 | Coming back to our example, we have now introduced the basic purpose of |
305 | Coming back to our example, we have now introduced the basic purpose of |
332 | L<AnyEvent::MP::Global> and C<initialise_node> and its use of profiles. We |
306 | L<AnyEvent::MP::Global> and C<configure> and its use of profiles. We |
333 | also set up our profiles for later use and now we will finally continue |
307 | also set up our profiles for later use and now we will finally continue |
334 | talking about the receiver. |
308 | talking about the receiver. |
335 | |
309 | |
336 | Let's look at the next line(s): |
310 | Let's look at the next line(s): |
337 | |
311 | |
338 | my $port = port; |
312 | my $port = port; |
339 | AnyEvent::MP::Global::register $port, "eg_receivers"; |
313 | AnyEvent::MP::Global::register $port, "eg_receivers"; |
340 | |
314 | |
341 | The C<port> function has already been discussed. It simply creates a new |
315 | The C<port> function has already been discussed. It simply creates a new |
342 | I<port> and returns the I<port ID>. The C<register> function, however, |
316 | I<port> and returns the I<port ID>. The C<register> function, however, |
343 | is new: The first argument is the I<port ID> that we want to add to a |
317 | is new: The first argument is the I<port ID> that we want to add to a |
344 | I<global group>, and its second argument is the name of that I<global |
318 | I<global group>, and its second argument is the name of that I<global |
345 | group>. |
319 | group>. |
346 | |
320 | |
347 | You can choose the name of such a I<global group> freely (prefixing your |
321 | You can choose the name of such a I<global group> freely (prefixing your |
348 | package name is highly recommended!). The purpose of such a group is to |
322 | package name is highly recommended!). The purpose of such a group is to |
349 | store a set of I<port IDs>. This set is made available throughout the |
323 | store a set of I<port IDs>. This set is made available throughout the |
350 | whole L<AnyEvent::MP> network, so that each node can see which ports |
324 | L<AnyEvent::MP> network, so that each node can see which ports belong to |
351 | belong to that group. |
325 | that group. |
352 | |
326 | |
353 | Later we will see how the sender looks for the ports in this I<global |
327 | Later we will see how the sender looks for the ports in this I<global |
354 | group> to send messages to them. |
328 | group> to send messages to them. |
355 | |
329 | |
356 | The last step in the example is to set up a receiver callback for those |
330 | The last step in the example is to set up a receiver callback for those |
… | |
… | |
365 | |
339 | |
366 | use AnyEvent; |
340 | use AnyEvent; |
367 | use AnyEvent::MP; |
341 | use AnyEvent::MP; |
368 | use AnyEvent::MP::Global; |
342 | use AnyEvent::MP::Global; |
369 | |
343 | |
370 | initialise_node "eg_simple_sender"; |
344 | configure nodeid => "eg_sender", seeds => ["*:4040"]; |
371 | |
345 | |
372 | my $find_timer = |
346 | my $find_timer = |
373 | AnyEvent->timer (after => 0, interval => 1, cb => sub { |
347 | AnyEvent->timer (after => 0, interval => 1, cb => sub { |
374 | my $ports = AnyEvent::MP::Global::find "eg_receivers" |
348 | my $ports = AnyEvent::MP::Global::find "eg_receivers" |
375 | or return; |
349 | or return; |
… | |
… | |
378 | for @$ports; |
352 | for @$ports; |
379 | }); |
353 | }); |
380 | |
354 | |
381 | AnyEvent->condvar->recv; |
355 | AnyEvent->condvar->recv; |
382 | |
356 | |
383 | It's even less code. The C<initialise_node> serves the same purpose as in |
357 | It's even less code. The C<configure> serves the same purpose as in the |
384 | the receiver, we just specify a different profile, the profile we set up |
358 | receiver, but instead of specifying binds we specify a list of seeds - |
385 | without the binds. |
359 | which happens to be the same as the binds used by the receiver, which |
|
|
360 | becomes our seed node. |
386 | |
361 | |
387 | Next we set up a timer that repeatedly (every second) calls this chunk of |
362 | Next we set up a timer that repeatedly (every second) calls this chunk of |
388 | code: |
363 | code: |
389 | |
364 | |
390 | my $ports = AnyEvent::MP::Global::find "eg_receivers" |
365 | my $ports = AnyEvent::MP::Global::find "eg_receivers" |
… | |
… | |
405 | the receiver I<port(s)>. |
380 | the receiver I<port(s)>. |
406 | |
381 | |
407 | We then just send a message with a tag and the current time to every |
382 | We then just send a message with a tag and the current time to every |
408 | I<port> in the global group. |
383 | I<port> in the global group. |
409 | |
384 | |
410 | =head3 Multiple Receivers |
385 | =head3 Splitting Network Configuration and Application Code |
411 | |
386 | |
412 | You can even run multiple receivers - the only problem is that they will |
387 | Ok, so far, this works. In the real world, however, the person configuring |
413 | use the same node ID. To avoid this problem, you can either not specify a |
388 | your application to run on a specific network (the end user or network |
414 | profile name at all and rely on DNS and your POSIX node name, or you can |
389 | administrator) is often different to the person coding the application. |
415 | use a special feature called "anonymous nodes": |
|
|
416 | |
390 | |
417 | aemp profile eg_simple_receiver setnodeid anon/ |
391 | Or to put it differently: the arguments passed to configure are usually |
|
|
392 | provided not by the programmer, but by whoeever is deplying the program. |
418 | |
393 | |
419 | The special name C<anon/> will be replaced by a random string each time |
394 | To make this easy, AnyEvent::MP supports a simple configuration database, |
420 | the node starts. This way you can start many receivers (they do not bind |
395 | using profiles, which can be managed using the F<aemp> command-line |
421 | on a TCP port, so cnanot collide with each other), and all of them will |
396 | utility. |
422 | receive the central time signal. |
397 | |
|
|
398 | When you change both programs above to simply call |
|
|
399 | |
|
|
400 | configure; |
|
|
401 | |
|
|
402 | then AnyEvent::MP tries to look up a profile using the current node name |
|
|
403 | in its configuration database, falling back to some global default. |
|
|
404 | |
|
|
405 | You can run "generic" nodes using the F<aemp> utility as well, and we will |
|
|
406 | exploit this in the following way: we configure a profile "seed" and run |
|
|
407 | a node using it, whose sole purpose is to be a seed node for our example |
|
|
408 | programs. |
|
|
409 | |
|
|
410 | We bind the seed node to port 4040 on all interfaces: |
|
|
411 | |
|
|
412 | aemp profile seed setbinds "*:4040" |
|
|
413 | |
|
|
414 | And we configure all nodes to use this as seed node (this only works when |
|
|
415 | running on the same host, for multiple machines you would provide the IP |
|
|
416 | address or hostname of the node running the seed): |
|
|
417 | |
|
|
418 | aemp setseeds "*:4040" |
|
|
419 | |
|
|
420 | Then we run the seed node: |
|
|
421 | |
|
|
422 | aemp run profile seed |
|
|
423 | |
|
|
424 | After that, we can start as many other nodes as we want, and they will all |
|
|
425 | use our generic seed node to discover each other. |
|
|
426 | |
|
|
427 | In fact, starting many receivers nicely illustrates that the time sender |
|
|
428 | can have multiple receivers. |
423 | |
429 | |
424 | That's all for now - next time we will teach you about monitoring by |
430 | That's all for now - next time we will teach you about monitoring by |
425 | writing a simple chat client and server :) |
431 | writing a simple chat client and server :) |
426 | |
432 | |
427 | =head1 SEE ALSO |
433 | =head1 SEE ALSO |