Node Life Cycle sample
What does the sample do?
This sample illustrates how node life cycle events can be used to control distributed transactions within a node.
Our goal is to handle the use case where each JPPF task in a job inserts a row in a database.
In this scenario, we want to avoid the creation of duplicate records when the node crashes or is disconnected, due to the JPPF recovery mechanism that will cause the tasks to be resubmitted to another node.
To achieve this, it is natural to use database transactions so we can roll back any database update when the node crashes.
One requirement is to know when a transaction should be started and when it should be committed or rolled back.
This information will be provided by a listener that receives notifications of the node's life cycle events, as follows:
- when the node is starting: this event triggers a recovery mechanism that rolls back any transaction that was active at the time of a previous node crash. If the node didn't crash, there is no need to recover anything
- when the node is terminated: here we are talking about a "clean" termination of the node, via the management APIs or the JPPF admin console. In this case, any active transaction is immediately rolled back before the node terminates
- when the node starts processing a job: this is when we start a new transaction
- when the node completes the processing of a job: this is when we commit the current transaction
Some constraints are imposed by the transaction management mechanism and the JTA specfications: due to the association of each transaction with a single thread, all database-related operations must be performed in the same thread as the transaction's.
This means that we will need to sequentialize and synchronize these operations in order to achieve our goals. To do this, we use a simple worker thread
(see Executors.newSingleThreadExecutor()), and each database or transaction related operation will be submitted
as a Callable task to this worker thread.
A consequence of this is that we will lose some degree of parallelism, since it will limit the node's ability to execute multiple tasks in parallel.
For this demonstration's purpose, we chose to use the following components:
- H2 Database Engine: a pure Java, open source, XA-compliant database with a very small footprint
- Atomikos TransactionsEssentials®: an open source transaction management system supporting JDBC/XA pools, JMS/XA pools and JTA-compliant transactions management
Related source files
- NodeListener.java : A listener to the node's life cycle events, provides transaction management for the JPPF tasks
- DBTask.java : this is the code for the tasks.
Each task waits for a configurable time after inserting a database row, to give the applicatione enough time to simulate a node crash and restart.
- DBRunner.java : this is the code for the client application.
It submits a job with a configurable number of tasks, waits for a configurable delay, then restarts the node, and finally gets the execution results and displays the rows inserted in the database
- jppf-client.properties : the jppf configuration files, which also allows you to modify some parameters of the sample application
Sample directory structure
- root folder: contains the build.xml Ant script and the generated jar file to deploy to the JPPF driver
- /atomikos: this is where the Atomikos transaction manager keeps its work files, including the transactions log used for crash recovery
- /classes: contains the compiled java classes
- /config: contains the JPPF and Log4j configuration files
- /db: contains the database and the Windows and Linux scripts to start and stop the database server
- /lib: contains the Atomikos, H2 and JTA libraries
- /src: this is where all the sources of this sample are
How do I run it?
Before running this sample application, you need to install a JPPF server and at least one node.
For information on how to set up a node and server, please refer to the JPPF documentation
Once you have installed a server and node, perform the following steps:
- open a command prompt in JPPF-x.y-samples-pack/NodeLifeCycle
- build the sample: type "ant jar" or simply "ant"; this will create a file named NodeLifeCycle.jar
- copy NodeLifeCycle.jar in the "lib" folder of the JPPF driver installation, as well as all the *.jar files in NodeLifeCycle/lib, to add them to the driver's classpath. This is enough to deploy the add-on.
- start the database server: open a command prompt in NodeNodeLifeCycle/db and type "startH2.bat" (on Windows) or "./startH2.sh" (on Linux). Alternatively you can run an Ant target instead: "ant start.db.server"
- start the server and node
- run the sample application: open a command prompt in JPPF-x.y-samples-pack/NodeNodeLifeCycle and type "ant run"
- you should see a display of the tasks execution results, followed by a display of all the rows inserted in the database table.
Additionally, the node's console will show the sequence of events that took place, including the node shutdown and restart events
- to stop the database server: open a command prompt in NodeNodeLifeCycle/db and type "stopH2.bat" (on Windows) or "./stopH2.sh" (on Linux). Alternatively you can run an Ant target instead: "ant stop.db.server"
- to reset the database: open a command prompt in NodeNodeLifeCycle and run the Ant target: "ant reset.db". This will re-create the database with an empty table
What features of JPPF are demonstrated?
I have additional questions and comments, where can I go?
If you need more insight into the code of this demo, you can consult the Java source files located in the CustomMBeans/src folder.
In addition, There are 2 privileged places you can go to: