For a replicated JE application, read requests can be serviced by any electable or secondary node in the replication group, but write requests can only be serviced by the Master node. For this reason, your application must be prepared to deal with the difference in operating behavior between read-only Replicas and read-write Masters.
It is possible to be quite sophisticated in terms of tracking
which node is the Master and so which node can service write
requests. You can even route write requests to the Master node
by writing a special router process. For an example of an
application that does this, see RouterDrivenStockQuotes and
HARouter, both of which are available in your JE
distribution in the
<JE HOME>/examples/je/rep/quote
directory.
However, for our purposes here, we simply want to make sure our Replica nodes can gracefully handle a situation where they receive a write request. The write request should be rejected by the node, with some notification being returned to the requester that the write activity is rejected. While not the most robust solution, this is the simplest thing your JE replicated application can do if it receives a write request at a Replica node.
There are two ways to determine whether a write request can be handled at the local node:
Use a monitor node to implement request routing. Monitor nodes are described in Writing Monitor Nodes.
Use the StateChangeListener to detect when the local node becomes a Master. Otherwise, forward the write request to the Master node instead of attempting to service it locally.
Either way, any code that attempts database writes for an HA application should always be prepared to handle a ReplicaWriteException.
You use the StateChangeListener interface to implement a class that is capable of notifying your node when it has changed state. In this way, you can track whether a node is in the Master, Replica or Unknown state, and so know whether the node is capable of handling write requests.
To do this, you must implement StateChangeListener.stateChange(), which receives a StateChangeEvent object whenever it is called.
If the node is not in the Master state, then the node can either reject write requests outright or, more usefully, forward write requests to the Master. For an example of an HA application that forwards write requests and uses the StateChangeListener, see the UpdateForwardingStockQuotes example.
Alternatively, you can write a router based on an HA Monitor. See Writing Monitor Nodes for more information.
Briefly, you can implement
StateChangeListener as follows. Notice that this partial
implementation relies on StateChangeEvent.getState() to
determine the state that the node has just transitioned to.
It then uses StateChangeEvent.getMasterNodeName() to
determine where write requests should be forwarded to in
the event that the new state is not
MASTER
.
private class Listener implements StateChangeListener { private String currentMaster = null; public void stateChange(StateChangeEvent se) throws RuntimeException { switch (stateChangeEvent.getState()) { case MASTER: // Do whatever your code needs you to do when the // current node is the MASTER. For example, // set a flag to indicate that the local node // is in the MASTER state. Here, we just fall // through and do the same thing as if we // transitioned to the REPLICA state. case REPLICA: // Again, do whatever your code needs done when // a node is in the REPLICA state. At a minimum, // you should probably capture which node is the // current Master. currentMaster = se.getMasterNodeName(); break; // We get here if we have transitioned to the UNKNOWN // state. default: currentmasterName = null; break; } } public String getCurrentMasterName() { return currentMaster; } }
In order to make use of the new listener, the application must call ReplicatedEnvironment.setStateChangeListener(). Note that this method can be called at any time after the ReplicatedEnvironment handle has been created. Also, the listener is set per environment, not per handle. So if you set different listeners for different ReplicatedEnvironment handles, the last listener configured is used environment-wide.
EnvironmentConfig envConfig = new EnvironmentConfig(); envConfig.setAllowCreate(true); envConfig.setTransactional(true); // Identify the node ReplicationConfig repConfig = new ReplicationConfig(); repConfig.setGroupName("PlanetaryRepGroup"); repConfig.setNodeName("Saturn"); repConfig.setNodeHostPort("saturn.example.com:5001"); // Use the node at mars.example.com:5002 as a helper to find // the rest of the group. repConfig.setHelperHosts("mars.example.com:5002"); ReplicatedEnvironment repEnv = new ReplicatedEnvironment(home, repConfig, envConfig); StateChangeListener listener = new Listener(); repEnv.setStateChangeListener(listener);
If you perform a Database write operation on a node that is not in the Master state, a ReplicaWriteException is thrown when you attempt to commit the transaction. Therefore, whenever performing database write operations in an HA application, you should catch and handle ReplicaWriteException.
For example:
Transaction txn = null; try { txn = env.beginTransaction(null, null); /* * Perform your write operations under the protection * of the transaction handle here. */ txn.commit(); } catch (ReplicaWriteException replicaWrite) { /* * Perform whatever reporting (logging) activies you want * to do in order to acknowledge that the write operation(s) * failed. Then abort the transaction. */ if (txn != null) { txn.abort(); } }