Table of Contents
Every environment participating in a replicated application must know whether it is a master or replica. The reason for this is because, simply, the master can modify the database while replicas cannot. As a result, not only will you open databases differently depended on whether the environment is running as a master, but the environment will frequently behave quite a bit differently depending on whether it thinks it is operating as the read/write interface for your database.
Moreover, an environment must also be capable of gracefully switching between master and replica states. This means that the environment must be able to detect when it has switched states.
Not surprisingly, a large part of your application's code will be tied up in knowing which state a given environment is in and then in the logic of how to behave depending on its state.
As an alternative, write forwarding simplifies adding replication to an application. Once configured, there is no need to confine write operations to the master or even to know which site is the master. For more information, see Configuring for Write Forwarding.
This chapter shows you how to determine your environment's state, and it then shows you some sample code on how an application might behave depending on whether it is a master or a replica in a replicated application.
In order to determine whether your code is running as a
master or a replica, you implement an event handling
callback, which we initially describe in
Event Handling.
When the current environment becomes a client —
including at application startup — the
DB_EVENT_REP_CLIENT
event is raised.
When an election is held and a replica is elected to be a
master, the DB_EVENT_REP_MASTER
event is
raised on the newly elected master and the
DB_EVENT_REP_NEWMASTER
is raised on the
other replicas.
The EventHandler
implementation is
fairly simple. First you detect the event, and then you
record the state change in some data member maintained in a
location that is convenient to you.
For example:
package db.repquote; // We make our main class an EventHandler implementation ... import com.sleepycat.db.EventHandler; ... public class MyReplicationClass implements EventHandler { ... // Somewhere we provide a data member that is used to track // whether we are a master server. This could be in our main // class, or it could be part of a supporting class. private boolean isMaster; ... isMaster = false; ... // In the code where we open our environment and start replication, // we must identify the class that is the event handler. In this // example, we are performing this from within the class that // implements com.sleepycat.db.EventHandler so we identify // "this" class as the event handler envConfig.setEventHandler(this);
That done, we still need to implement the methods required for handling replication events. For a simple application like this one, these implementations can be trivial.
public void handleRepClientEvent() { dbenv.setIsMaster(false); } public void handleRepConnectBrokenEvent() { // Ignored for now. } public void handleRepConnectEstablishedEvent() { // Ignored for now. } public void handleRepConnectTryFailedEvent() { // Ignored for now. } public void handleRepMasterEvent() { dbenv.setIsMaster(true); } public void handleRepNewMasterEvent(int envId) { // Ignored for now } public void handleWriteFailedEvent(int errorCode) { System.err.println("Write to stable storage failed!" + "Operating system error code:" + errorCode); System.err.println("Continuing...."); } public void handleRepStartupDoneEvent() { System.out.println("Replication startup is completed."); } public void handleRepPermFailedEvent() { System.out.println("This application failed to receive enough" + "acks for a permanent message. The transaction is flushed" + "to disk on this master host."); } public void handleRepLocalSiteRemovedEvent() { // Ignored for now. } public void handleRepSiteAddedEvent() { // Ignored for now. } public void handleRepSiteRemovedEvent() { // Ignored for now. } public void handleRepElectedEvent() { // Safely ignored for Replication Manager applications. } public void handleRepElectionFailedEvent() { // Safely ignored for Replication Manager applications that do // not manage their own master selection. } public void handleRepJoinFailureEvent() { // Safely ignored since this application did not turn off AUTOINIT. } public void handleRepMasterFailureEvent() { // Safely ignored for Replication Manager applications that do // not manage their own master selection. } public void handleRepDupmasterEvent() { // Safely ignored for Replication Manager applications that do // not manage their own master selection. } public void handlePanicEvent() { System.err.println("Panic encountered!"); System.err.println("Shutting down."); System.err.println("You should restart, running recovery."); try { terminate(); } catch (DatabaseException dbe) { System.err.println("Caught an exception during " + "termination in handlePanicEvent: " + dbe.toString()); } System.exit(-1); }
Of course, this only gives us the current state of the environment. We still need the code that determines what to do when the environment changes state and how to behave depending on the state (described in the next section).