Collaborative Fault Tolerance Using JGroups
By Jerry Overton, OCI Senior Software Engineer
September 2007
Introduction
Unlike in other systems, failures in collaborative systems are the norm rather than the exception. Any viable collaborative system must have fault tolerance mechanisms that allow for both the detection and mitigation of individual node failures. These mechanisms can be complicated and very expensive to build.
The JGroups API gives developers easy access to reliable group communication capabilities. This article gives an overview of how to build collaborative fault tolerance by using JGroups to implement portions of the Object Group pattern. The information is based on the experience of writing a Java application that simulates collaborative system failures. Excerpts from that application are used to illustrate key concepts.
Collaborative Systems
In collaborative systems, otherwise autonomous computing nodes cooperate to achieve a common task that would not be possible with any individual node acting alone. Examples include intelligent transport systems, military command and control systems and autonomous manufacturing systems. Although the exact definition of a collaborative system can vary depending on context, this article will focus on three defining characteristics:
- Nodes in collaborative systems are autonomous and spatially distributed.
- Task-execution responsibilities are distributed across multiple nodes.
- The communication links between nodes are decentralized and dynamic.
Figure 1 is an example of a collaborative system. It shows a network of environmental sensor stations. The nodes in the network are autonomous and spatially distributed across the region shown. Each sensor is capable of recording and reporting its local environmental conditions without the help of any of the other sensor stations.
The task-execution responsibilities are distributed across multiple sensor stations. The system is designed to report the environmental condition of a given region. Each sensor is capable of recording and reporting its local conditions, but to record and report the condition of the entire region requires that all sensor stations cooperate.
The communication links between the sensor stations are decentralized and dynamic. Sensors can enter and leave the network at anytime. Every station is wirelessly connected to every other station, so no single sensor failure can disrupt the overall network connectivity.
Figure 1: Example Collaborative System.
Collaborative systems have special failure characteristics beyond the typical failures of other software systems. In systems with large numbers of nodes, node failures are common. Internal exceptions will cause nodes to become unresponsive in the network. Dynamic network connections, especially wireless links, are prone to temporal or permanent interruptions. These interruptions not only decrease the functionality of the collaborative system, but may also lead to the corruption of individual node states.
For example, sensor stations are exposed to adverse weather, are knocked over and broken easily, and can be expected to run out of power. Anything (people, cars, animals) passing between the line of sight of two sensor stations can cause a temporary loss of communication. If this happens at the right time, a controller in a region may miss a sensor update and become out of touch with the current conditions in the region.
A successful set of fault tolerance strategies for collaborative systems will allow nodes to both detect and mitigate distributed failures. Each node must be capable of detecting when other nodes become unresponsive; each node must be capable of performing in degraded mode when disconnected from the network; and the network must be capable of using node redundancy to compensate for the loss of any particular node.
Simulating a Collaborative System
This section describes the design of a Java application that simulates fault tolerance in collaborative systems using JGroups. Code excerpts from the simulator are used in subsequent sections to illustrate how to use the JGroups API to build failure detection and mitigation in collaborative systems.
Overview of JGroups
JGroups is toolkit of Java APIs for reliable group communication. Developers can use JGroups to join a group, send messages to group members, and receive messages from group members. JGroups communication infrastructure keeps track of the members in every group and updates group members when changes occur -- new members join, existing members leave or crash.
As shown in Figure 2, the JGroups architecture consists of three main parts: (1) the Channel API used to establish communication links, (2) building blocks that group channel functionality into higher-level abstractions and (3) the protocol stack that handles network communication for a given channel.
Figure 2: JGroups Architecture
A process has to create a channel to join a group. The channel is the handle to the group. Clients that want to connect to multiple groups have to create multiple channels. The programming interface for channels is simple and primitive. Building blocks (a more sophisticated interface into a channel) saves the application programmer from having to write tedious, repetitive code. The protocol stack manages the details of sending and receiving messages over the IP network.
A Network Simulator
Figure 3 shows the logical structure of software developed to simulate the behavior of nodes in a collaborative system. A simulation engine (not shown here) launches GroupNodes
, each with its own threads of execution. Each node gets its ability to collaborate through an association with a CommStrategy
object. The CommStrategy
has an association back to its GroupNode
in case the GroupNode
needs to be notified of events from the CommStrategy
.
Figure 3: Logical Structure of Collaborative System Simulator.
In this specific example, the PushPullNode
extends the functionality of the standard GroupNode
. The PushPullNode
gets its ability to collaborate through an association with a PushPullStrategy
that extends the functionality of the CommStrategy
using JGroup-specific functions defined by the JGroupListener
object. The remainder of this article gives an overview of how JGroups was used to add collaborative fault tolerance to the PushPullNode
.
Detecting Failures
Participants in a collaborative network need the ability to let other nodes know when they fail and to detect the failures of the other nodes. For example, suppose that, in the collaborative system shown in Figure 1, rain water leaks into the waterproofing of one of the sensors. The sensor shorts and is unable to perform normally.
In this scenario, controllers that rely on that sensor for regular updates will have to be notified of the failure. The sensor is responsible for letting controllers know that it has failed, but if the sensor is sufficiently damaged, the controllers may have to find out about the error by some other means.
The following code is an excerpt from a Java application that simulates collaborative system failures. It shows how the PushPullNode
uses the JGroups API to detect distributed failures. For the sake of brevity, the error handling code is not shown. In some cases, the details of a function are replaced with general comments.
- public class CommStrategy {
- protected JChannel channel;
-
- public CommStrategy(){
- this.channel = new JChannel();
- this.channel.setOpt(channel.SUSPECT, Boolean.TRUE);
- }
- public void joinGroup(String groupName) throws Exception {
- this.channel.connect(groupName);
- }
- public void sendMessage (String message) throws Exception {
- this.channel.send(null, null, message.getBytes());
- }
- }
- public class PushPullStrategy extends CommStrategy implements Receiver {
-
- public void joinGroup (String groupName) throws Exception {
- super.channel.connect(groupName);
- super.channel.setReceiver(this);
- }
- public void receive(Message message){
- String msg = new String(message.getBuffer());
- //write code to process the message;
- }
- public void suspect(View view){
- //check the view for its content and handle appropriately
- }
- }
The PushPullStrategy
extends the CommStrategy
. When the PushPullNode
creates a PushPullStrategy
, the CommStrategy
constructor creates a new channel and sets the channel.SUSPECT
property to TRUE
. The channel.SUSPECT
property allows the channel to automatically notify the PushPullNode
when any member of its group is suspected of failing. Nodes in a group are suspect when, after a period of time, they fail to respond to health monitoring messages. Both the suspect notification and the node monitoring are provided by the JGroups infrastructure and are transparent to the application developer.
For the network shown in Figure 1, fault tolerant communication between two sensors (A and B) can be simulated using two PushPullNodes
. Both sensor PushPullNodes
join the same group using the same groupName
. If sensor A fails, but is still able to notify other members of that failure, it calls this.CommStrategy.sendMessage()
. JGroups calls sensor B's PushPullStrategy.receive()
, alerting sensor B of the failure.
If sensor A's error is so severe that it becomes completely unresponsive, JGroups calls the suspect
function of sensor B. Sensor B's suspect
function checks the view
for details about which member is suspected and the nature of the failure. See the JGroups User Manual for more details about the information available in a view
.
Mitigating Failures
The most basic strategy for mitigating failures is to keep back-up copies of data or services and use them when a failure occurs. An Object Group uses replicated state to ensure continuity in the event of a failure. Although Object Groups can provide failure times much faster than simply using backups, programming them from scratch can be a daunting task.
The JGroups API makes it very easy to implement the Object Group pattern. The following code extends the CommStrategy
and PushPullStrategy
to include support for Object Groups.
- public class CommStrategy {
- protected JChannel channel;
-
- public CommStrategy(){...}
- public void joinGroup(String groupName) throws Exception {...}
- public void sendMessage (String message) throws Exception {...}
-
- public void groupOperation(){
- //domain-specific processing here
- this.channel.getState(null, 5000);
- }
- }
- public class PushPullStrategy extends CommStrategy implements Receiver {
- private byte[] state;
-
- public void joinGroup (String groupName) throws Exception {...}
- public void receive(Message message){...}
- public void suspect(View view){...}
-
- public byte[] getState(){
- //retrieve state appropriately
- return this.state;
- }
- public void setState(byte[] state){
- //process and store state appropriately
- }
- }
Adding Object-Group style support to the PushPullNode
using JGroups took only a very small amount of additional code. The new operations added are CommStrategy.groupOperation()
, PushPullStrategy.getState()
and PushPullStrategy.setState()
. The CommStrategy.groupOperation()
is a general representation of what would normally be domain-specific functionality. For example, in the sensor network shown in figure 1, the group operation might be getZoneTemp()
instead.
The CommStrategy.groupOperation()
requests the state of the current group, the PushPullStrategy.getState()
retrieves the state and the PushPullStrategy.setState()
operation processes and persistently stores the state.
For the network shown in Figure 1, a controller, sensor A and sensor B could be simulated using PushPullNodes
. Sensors A and B join the same group representing a physical zone. The controller relies on both sensor A and sensor B to report temperature for a given region. The controller doesn't care which sensor it uses as long as at least one of them is always available. Figure 4 shows the interactions that occur during a group operation.
Figure 4: Collaborations in a Group Operation.
When the controller wants a temperature reading from the zone, it joins the zone's group and calls this.commstrategy.groupOperation()
. JGroups elects a leader (by default based on membership length) within the group and calls getState()
on that node (let's assume that sensor A was chosen).
The getState()
operation of sensor A takes a temperature reading and sets the reading as the operation's return value. JGroups then calls setState()
on the controller, passing it the temperature reading from sensor A. In subsequent requests for the zone temperature, if sensor A becomes unresponsive, JGroups will failover to sensor B.
Complicated group management functions were provided to the programmer implicitly by the JGroups API, and the leadership election and failover is completely transparent to the controller's programmer.
Summary
JGroups actually works. It was a straightforward task to use APIs for the multicasting and group management functions needed for collaborative fault tolerance. JGroups has several extensibility features, not mentioned in this article, that allow you to do things like write your own communication protocol or overwrite the default settings of the original protocol.
Unfortunately, some of the examples in the API manual were outdated, and several of the API operations were deprecated. In the course of writing the simulation software, it took some trial and error to compensate for information missing from the manual.
At the time this article was written, JGroups was used in a number of commercial applications. JBoss uses JGroups to provide clustering features for its Java application servers. JGroups is also used by FactoryPMI to provide connection redundancy and load balancing for industrial control systems.
References
- [1] JGroups
The JGroups Project - [2] Object Group
The Object Group Pattern - [3] Adoption of JGroups
JGroups Success Stories