Permalink
Browse files

Edited Chapter 4

  • Loading branch information...
1 parent 79e1162 commit ea0339c1ca8299f3f5e80b6cdd4cce168930e452 @hintjens hintjens committed Nov 9, 2012
Showing with 433 additions and 456 deletions.
  1. +36 −33 chapter3.txt
  2. +174 −144 chapter4.txt
  3. +4 −4 chapter5.txt
  4. +0 −39 chapter6.txt
  5. 0 examples/C#/{lruqueue.cs → lbbroker.cs}
  6. 0 examples/C#/{lruqueue2.cs → lbbroker2.cs}
  7. 0 examples/C++/{lruqueue.cpp → lbbroker.cpp}
  8. +4 −4 examples/C/.gitignore
  9. +4 −4 examples/C/asyncsrv.c
  10. +36 −36 examples/C/bstar.c
  11. +12 −12 examples/C/bstarsrv.c
  12. +2 −2 examples/C/bstarsrv2.c
  13. +31 −31 examples/C/clonesrv6.c
  14. +2 −2 examples/C/flserver2.c
  15. +2 −2 examples/C/flserver3.c
  16. +23 −23 examples/C/{lruqueue.c → lbbroker.c}
  17. +11 −11 examples/C/{lruqueue2.c → lbbroker2.c}
  18. +13 −13 examples/C/{lruqueue3.c → lbbroker3.c}
  19. +22 −22 examples/C/mdbroker.c
  20. +1 −1 examples/C/mdwrkapi.c
  21. +10 −10 examples/C/peering2.c
  22. +12 −11 examples/C/peering3.c
  23. +17 −17 examples/C/ppqueue.c
  24. +9 −9 examples/C/spqueue.c
  25. +3 −3 examples/C/spworker.c
  26. +0 −23 examples/C/testit.c
  27. 0 examples/CL/{lruqueue.asd → lbbroker.asd}
  28. 0 examples/CL/{lruqueue.lisp → lbbroker.lisp}
  29. +5 −0 examples/ChangeLog
  30. 0 examples/Clojure/{lruqueue.clj → lbbroker.clj}
  31. 0 examples/Erlang/{lruqueue.es → lbbroker.es}
  32. 0 examples/F#/{lruqueue.fsx → lbbroker.fsx}
  33. 0 examples/Haskell/{lruqueue.hs → lbbroker.hs}
  34. 0 examples/Haxe/{lruqueue.hx → lbbroker.hx}
  35. 0 examples/Haxe/{lruqueue2.hx → lbbroker2.hx}
  36. 0 examples/Haxe/{lruqueue3.hx → lbbroker3.hx}
  37. 0 examples/Java/{lruqueue.java → lbbroker.java}
  38. 0 examples/Lua/{lruqueue.lua → lbbroker.lua}
  39. 0 examples/Lua/{lruqueue2.lua → lbbroker2.lua}
  40. 0 examples/PHP/{lruqueue.php → lbbroker.php}
  41. 0 examples/PHP/{lruqueue2.php → lbbroker2.php}
  42. 0 examples/Python/{lruqueue.py → lbbroker.py}
  43. 0 examples/Python/{lruqueue2.py → lbbroker2.py}
  44. 0 examples/Python/{lruqueue3.py → lbbroker3.py}
  45. 0 examples/Scala/{lruqueue.scala → lbbroker.scala}
  46. 0 examples/Scala/{lruqueue2.scala → lbbroker2.scala}
  47. 0 examples/Tcl/{lruqueue.tcl → lbbroker.tcl}
View

Large diffs are not rendered by default.

Oops, something went wrong.
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -449,11 +449,11 @@ So, Model Six introduces these changes over Model Five:
* We add heartbeats to server updates (to clients), so that a client can detect when the primary server has died. It can then switch over to the backup server.
-* We connect the two servers using the Binary Star {{bstar}} reactor class. Binary Star relies on the clients to 'vote' by making an explicit request to the server they consider "master". We'll use snapshot requests for this.
+* We connect the two servers using the Binary Star {{bstar}} reactor class. Binary Star relies on the clients to 'vote' by making an explicit request to the server they consider "active". We'll use snapshot requests for this.
* We make all update messages uniquely identifiable by adding a UUID field. The client generates this, and the server propagates it back on re-published updates.
-* The slave server keeps a "pending list" of updates that it has received from clients, but not yet from the master server. Or, updates it's received from the master, but not yet clients. The list is ordered from oldest to newest, so that it is easy to remove updates off the head.
+* The passive server keeps a "pending list" of updates that it has received from clients, but not yet from the active server. Or, updates it's received from the active, but not yet clients. The list is ordered from oldest to newest, so that it is easy to remove updates off the head.
It's useful to design the client logic as a finite state machine. The client cycles through three states:
@@ -503,7 +503,7 @@ Fail-over happens as follows:
When the primary server comes back on-line, it will:
-* Start up as slave server, and connect to the backup server as a Clone client.
+* Start up as passive server, and connect to the backup server as a Clone client.
* Start to receive updates from clients, via its SUB socket.
@@ -611,7 +611,7 @@ This main program is only a few hundred lines of code, but it took some time to
I'm sure the code still has flaws which kind readers will spend weekends debugging and fixing for me. I'm happy enough with this model to use it as the basis for real applications.
-To test the sixth model, start the primary server and backup server, and a set of clients, in any order. Then kill and restart one of the servers, randomly, and keep doing this. If the design and code is accurate, clients will continue to get the same stream of updates from whatever server is currently master.
+To test the sixth model, start the primary server and backup server, and a set of clients, in any order. Then kill and restart one of the servers, randomly, and keep doing this. If the design and code is accurate, clients will continue to get the same stream of updates from whatever server is currently active.
++++ Clone Protocol Specification
View
@@ -15,7 +15,6 @@ We'll cover:
* How to write and license an protocol specification.
* How to do fast restartable file transfer over 0MQ.
* How to do credit-based flow control.
-* How to do heartbeating for different 0MQ patterns.
* How to build protocol servers and clients as state machines.
* How to make a secure protocol over 0MQ (yay!).
* A large-scale file publishing system (FileMQ).
@@ -936,44 +935,6 @@ To make this work (and we will, my dear readers), we need to be a little more ex
And this gives us "credit-based flow control", which effectively removes the need for HWMs, and any risk of memory overflow.
-+++ Heartbeating
-
-Just as a real protocol needs to solve the problem of flow control, it also needs to solve the problem of knowing whether a peer is alive or dead. This is not an issue specific to 0MQ. TCP has a long timeout (30 minutes or so), that means that it can be impossible to know whether a peer has died, been disconnected, or gone on a weekend to Prague with a case of vodka, a redhead, and a large expense account.
-
-Heartbeating is not easy to get right, and as with flow control it can make the difference between a working, and failing architecture. So using our standard approach, let's start with the simplest possible heartbeat design, and develop better and better designs until we have one with no visible faults.
-
-++++ Shrugging It Off
-
-A decent first iteration is to do no heartbeating at all and see what actually happens. Many if not most 0MQ applications do this. 0MQ encourages this by hiding peers in many cases. What problems does this approach cause?
-
-* When we use a ROUTER socket in an application that tracks peers, as peers disconnect and reconnect, the application will leak memory and get slower and slower.
-
-* When we use SUB or DEALER-based data recipients, we can't tell the difference between good silence (there's no data) and bad silence (the other end died). When a recipient knows the other side died, it can for example switch over to a backup route.
-
-* If we use a TCP connection that stays silent for a long while, it will, in some networks, just die. Sending something (technically, a "keep-alive" more than a heartbeat), will keep the network alive.
-
-++++ One-Way Heartbeats
-
-So, our first solution is to sending a "heartbeat" message from each node to its peers, every second or so. When one node hears nothing from another, within some timeout (several seconds, typically), it will treat that peer as dead. Sounds good, right? Sadly no. This works in some cases but has nasty edge cases in other cases.
-
-For PUB-SUB, this does work, and it's the only model you can use. SUB sockets cannot talk back to PUB sockets, but PUB sockets can happily send "I'm alive" messages to their subscribers.
-
-As an optimization, you can send heartbeats only when there is no real data to send. Furthermore, you can send heartbeats progressively slower and slower, if network activity is an issue (e.g. on mobile networks where activity drains the battery). As long as the recipient can detect a failure (sharp stop in activity), that's fine.
-
-Now the typical problems with this design:
-
-* It can be inaccurate when we send large amounts of data, since heartbeats will be delayed behind that data. If heartbeats are delayed, you can get false timeouts and disconnections due to network congestion. Thus, always treat //any// incoming data as a heartbeat, whether or not the sender optimizes out heartbeats.
-
-* While the PUB-SUB pattern will drop messages for disappeared recipients, PUSH and DEALER sockets will queue them. So, if you send heartbeats to a dead peer, and it comes back, it'll get all the heartbeats you sent. Which can be thousands. Whoa, whoa!
-
-* This design assumes that heartbeat timeouts are the same across the whole network. But that won't be accurate. Some peers will want very aggressive heart-beating, to detect faults rapidly. And some will want very relaxed heart-beating, to let sleeping networks lie, and save power.
-
-++++ Ping-Pong Heartbeats
-
-Our third design uses a ping-pong dialog. One peer sends a ping command to the other, which replies with a pong command. Neither command has any payload. Pings and pongs are not correlated. Since the roles of "client" and "server" are often arbitrary, we specify that either peer can in fact send a ping and expect a pong in response. However, since the timeouts depend on network topologies known best to dynamic clients, it is usually the client which pings the server.
-
-This works for all ROUTER-based brokers. The same optimizations we used in the second model make this work even better: treat any incoming data as a pong, and only send a ping when not otherwise sending data.
-
+++ State Machines
Software engineers tend to treat (finite) state machines as a kind of intermediary interpreter. That is, you take a regular language and compile that into a state machine, then execute the state machine. The state machine itself is rarely visible to the developer: it's an internal representation, optimized, compressed, and bizarre.
File renamed without changes.
File renamed without changes.
File renamed without changes.
View
@@ -34,8 +34,8 @@ durapub
durapub2
durasub
identity
-lruqueue
-lruqueue2
+lbbroker
+lbbroker2
peering1
peering2
peering3
@@ -85,7 +85,7 @@ bstarsrv
bstarcli
bstarsrv2
version
-lruqueue3
+lbbroker3
tstkvsimple
espresso
rrworker
@@ -100,4 +100,4 @@ udpping1
udpping2
udpping3
eagain
-
+rtreq
View
@@ -88,9 +88,9 @@ server_worker (void *args, zctx_t *ctx, void *pipe)
zsocket_connect (worker, "inproc://backend");
while (true) {
- // The DEALER socket gives us the address envelope and message
+ // The DEALER socket gives us the reply envelope and message
zmsg_t *msg = zmsg_recv (worker);
- zframe_t *address = zmsg_pop (msg);
+ zframe_t *identity = zmsg_pop (msg);
zframe_t *content = zmsg_pop (msg);
assert (content);
zmsg_destroy (&msg);
@@ -100,10 +100,10 @@ server_worker (void *args, zctx_t *ctx, void *pipe)
for (reply = 0; reply < replies; reply++) {
// Sleep for some fraction of a second
zclock_sleep (randof (1000) + 1);
- zframe_send (&address, worker, ZFRAME_REUSE + ZFRAME_MORE);
+ zframe_send (&identity, worker, ZFRAME_REUSE + ZFRAME_MORE);
zframe_send (&content, worker, ZFRAME_REUSE);
}
- zframe_destroy (&address);
+ zframe_destroy (&identity);
zframe_destroy (&content);
}
}
View
@@ -33,10 +33,10 @@ struct _bstar_t {
int64_t peer_expiry; // When peer is considered 'dead'
zloop_fn *voter_fn; // Voting socket handler
void *voter_arg; // Arguments for voting handler
- zloop_fn *master_fn; // Call when become master
- void *master_arg; // Arguments for handler
- zloop_fn *slave_fn; // Call when become slave
- void *slave_arg; // Arguments for handler
+ zloop_fn *active_fn; // Call when become active
+ void *active_arg; // Arguments for handler
+ zloop_fn *passive_fn; // Call when become passive
+ void *passive_arg; // Arguments for handler
};
// The finite-state machine is the same as in the proof-of-concept server.
@@ -59,32 +59,32 @@ s_execute_fsm (bstar_t *self)
// Accepts CLIENT_REQUEST events in this state
if (self->state == STATE_PRIMARY) {
if (self->event == PEER_BACKUP) {
- zclock_log ("I: connected to backup (slave), ready as master");
+ zclock_log ("I: connected to backup (passive), ready as active");
self->state = STATE_ACTIVE;
- if (self->master_fn)
- (self->master_fn) (self->loop, NULL, self->master_arg);
+ if (self->active_fn)
+ (self->active_fn) (self->loop, NULL, self->active_arg);
}
else
if (self->event == PEER_ACTIVE) {
- zclock_log ("I: connected to backup (master), ready as slave");
+ zclock_log ("I: connected to backup (active), ready as passive");
self->state = STATE_PASSIVE;
- if (self->slave_fn)
- (self->slave_fn) (self->loop, NULL, self->slave_arg);
+ if (self->passive_fn)
+ (self->passive_fn) (self->loop, NULL, self->passive_arg);
}
else
if (self->event == CLIENT_REQUEST) {
- // Allow client requests to turn us into the master if we've
+ // Allow client requests to turn us into the active if we've
// waited sufficiently long to believe the backup is not
- // currently acting as master (i.e., after a failover)
+ // currently acting as active (i.e., after a failover)
assert (self->peer_expiry > 0);
if (zclock_time () >= self->peer_expiry) {
- zclock_log ("I: request from client, ready as master");
+ zclock_log ("I: request from client, ready as active");
self->state = STATE_ACTIVE;
- if (self->master_fn)
- (self->master_fn) (self->loop, NULL, self->master_arg);
+ if (self->active_fn)
+ (self->active_fn) (self->loop, NULL, self->active_arg);
} else
// Don't respond to clients yet - it's possible we're
- // performing a failback and the backup is currently master
+ // performing a failback and the backup is currently active
rc = -1;
}
}
@@ -93,10 +93,10 @@ s_execute_fsm (bstar_t *self)
// Rejects CLIENT_REQUEST events in this state
if (self->state == STATE_BACKUP) {
if (self->event == PEER_ACTIVE) {
- zclock_log ("I: connected to primary (master), ready as slave");
+ zclock_log ("I: connected to primary (active), ready as passive");
self->state = STATE_PASSIVE;
- if (self->slave_fn)
- (self->slave_fn) (self->loop, NULL, self->slave_arg);
+ if (self->passive_fn)
+ (self->passive_fn) (self->loop, NULL, self->passive_arg);
}
else
if (self->event == CLIENT_REQUEST)
@@ -108,8 +108,8 @@ s_execute_fsm (bstar_t *self)
// The only way out of ACTIVE is death
if (self->state == STATE_ACTIVE) {
if (self->event == PEER_ACTIVE) {
- // Two masters would mean split-brain
- zclock_log ("E: fatal error - dual masters, aborting");
+ // Two actives would mean split-brain
+ zclock_log ("E: fatal error - dual actives, aborting");
rc = -1;
}
}
@@ -119,38 +119,38 @@ s_execute_fsm (bstar_t *self)
if (self->state == STATE_PASSIVE) {
if (self->event == PEER_PRIMARY) {
// Peer is restarting - become active, peer will go passive
- zclock_log ("I: primary (slave) is restarting, ready as master");
+ zclock_log ("I: primary (passive) is restarting, ready as active");
self->state = STATE_ACTIVE;
}
else
if (self->event == PEER_BACKUP) {
// Peer is restarting - become active, peer will go passive
- zclock_log ("I: backup (slave) is restarting, ready as master");
+ zclock_log ("I: backup (passive) is restarting, ready as active");
self->state = STATE_ACTIVE;
}
else
if (self->event == PEER_PASSIVE) {
// Two passives would mean cluster would be non-responsive
- zclock_log ("E: fatal error - dual slaves, aborting");
+ zclock_log ("E: fatal error - dual passives, aborting");
rc = -1;
}
else
if (self->event == CLIENT_REQUEST) {
- // Peer becomes master if timeout has passed
+ // Peer becomes active if timeout has passed
// It's the client request that triggers the failover
assert (self->peer_expiry > 0);
if (zclock_time () >= self->peer_expiry) {
// If peer is dead, switch to the active state
- zclock_log ("I: failover successful, ready as master");
+ zclock_log ("I: failover successful, ready as active");
self->state = STATE_ACTIVE;
}
else
// If peer is alive, reject connections
rc = -1;
}
// Call state change handler if necessary
- if (self->state == STATE_ACTIVE && self->master_fn)
- (self->master_fn) (self->loop, NULL, self->master_arg);
+ if (self->state == STATE_ACTIVE && self->active_fn)
+ (self->active_fn) (self->loop, NULL, self->active_arg);
}
return rc;
}
@@ -289,19 +289,19 @@ bstar_voter (bstar_t *self, char *endpoint, int type, zloop_fn handler,
// Register handlers to be called each time there's a state change:
void
-bstar_new_master (bstar_t *self, zloop_fn handler, void *arg)
+bstar_new_active (bstar_t *self, zloop_fn handler, void *arg)
{
- assert (!self->master_fn);
- self->master_fn = handler;
- self->master_arg = arg;
+ assert (!self->active_fn);
+ self->active_fn = handler;
+ self->active_arg = arg;
}
void
-bstar_new_slave (bstar_t *self, zloop_fn handler, void *arg)
+bstar_new_passive (bstar_t *self, zloop_fn handler, void *arg)
{
- assert (!self->slave_fn);
- self->slave_fn = handler;
- self->slave_arg = arg;
+ assert (!self->passive_fn);
+ self->passive_fn = handler;
+ self->passive_arg = arg;
}
// .split enable/disable tracing
Oops, something went wrong.

0 comments on commit ea0339c

Please sign in to comment.