15 May 2006

The InstanceTrackingFilter -- watching for session failovers

Working on a demo the other day to demonstrate application state replication and clustering, I recognized (belatedly) that every time I built a demo in this area, I was always implementing a similar pattern to expose the details of which OC4J instance was servicing the request, whether a session failover had occured, etc.

There are two ways in which I have typically used this information -- for one, a session failover detection can be logged or an event fired at some form of system level; and two it can be injected into the HttpSession itself so that the application *could* make use of it if desired.

Since my demos usually focus on the most common place where this state replication is used -- the Web tier -- a ServletFilter provided a good mechanism to implement the session tracking of client requests. The benefit of using a ServletFilter to do this is that it doesn't need to be tightly integrated into the code of the Web application itself. A ServletFilter can be packaged into a JAR file and then applied to any Web application by putting the JAR file into the WEB-INF/lib directory of the Web application and adding a few tags to the web.xml file to enable the ServletFilter.

Having worked out the loose requirements and a possible approach with which to implement it, the next task was to determine how to get the information from which decisions can be made as to the failover activity.

The InstanceIdentifier

The first thing which needed to be done was to create some identifier of the instance which is servicing the current request. A fairly simplistic but workable approach is to use a combination of the System properties the OracleAS and OC4J components set when they are running, with the use of the InetAddress class from the Java API.
  private String oracleHome = null;
private String j2eeHome = null;
private String oc4jInstance = null;
private String processId = null;
private String hostName = null;

public InstanceIdentifier() {
oracleHome = System.getProperty("oracle.home");
j2eeHome = System.getProperty("oracle.j2ee.home");
try {
hostName = InetAddress.getLocalHost().getHostName();
} catch(Exception e) {
hostName = "N/A";
}

if(System.getProperty("OPMN","false").equals("true")) {
oc4jInstance = System.getProperty("oracle.oc4j.instancename");
processId = System.getProperty("oracle.ons.indexid");
processId = processId.substring(processId.lastIndexOf(".")+1);
} else {
processId="standalone";
oc4jInstance = j2eeHome.substring(j2eeHome.lastIndexOf(File.separator)+1);
}
}

Important to note in here is the applied knowledge of what makes an OC4J instance unique. In an OracleAS environment, it's possible to run multiple OC4J instances from the same OracleAS installation -- and further, its possible to run multiple JVM processes from the same OC4J instance configuration. The InstanceIdentifier attempts to determine/construct the required set of properties to uniquely identifer the specific OC4J instance.

Detecting a Failover
Once an identifier of the current instance servicing the request has been created, its possible to then calculate if a failover has occured by comparing the current instance with the previous instance. To do this, an equals method is added to the InstanceIdentifier class.
  public boolean equals(Object obj) {
if((obj==null) || !(obj instanceof InstanceIdentifier)) {
return false;
}

InstanceIdentifier id = (InstanceIdentifier)obj;

if(id.getOracleHome().equals(this.getOracleHome()) &&
id.getProcessId().equals(this.getProcessId()) &&
id.getJ2eeHome().equals(this.getJ2eeHome()) &&
id.getOc4jInstance().equals(this.getOc4jInstance()) &&
id.getHostName().equals(this.getHostName())) {
return true;
} else {
return false;
}
}


By using a ServletFilter that gets invoked on every request to the Web application which stores an instance of the InstanceIdentifier for the previous request in the HttpSession object, its possible to determine if a failover has occured by comparing it with the InstanceIdentifier from the current request.

public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException,
ServletException {
/*
* The broad goal of this filter is to detect some details about the
* instance which is servicing the request and insert it into the
* HttpSession. Then on subsequent requests, compare the Instance
* with the old instance and log a note if a failover has occurred.
*/

if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse)) {
HttpSession session = ((HttpServletRequest)request).getSession();

InstanceIdentifier currentInstance = new InstanceIdentifier();
InstanceIdentifier previousInstance =
(InstanceIdentifier)session.getAttribute(SESSION_IDENTIFIER);

if (previousInstance == null) {
// This is the first request so set the InstanceIdentifier
debug("First request received for: " + session.getId());
debug("Session isNew: " + session.isNew());
session.setAttribute(SESSION_IDENTIFIER, currentInstance);
session.setAttribute(SESSION_FAILOVER, Boolean.FALSE);
} else if(previousInstance.equals(currentInstance)) {
session.setAttribute(SESSION_FAILOVER, Boolean.FALSE);
} else {
// Is not the same host so probable is a failover
// Upate the InstanceIdentifier in the session
// Log a message, put notification into session

session.setAttribute(SESSION_FAILOVER, Boolean.TRUE);
session.setAttribute(SESSION_IDENTIFIER, currentInstance);

System.out.println("SessionId: " + session.getId() +
" switched from: " + previousInstance +
" to: " + currentInstance + "");
notifier.notifyFailover(session.getId(),
previousInstance,
currentInstance);
}
}
chain.doFilter(request, response);
}

Just to recap, this ServletFilter method will be invoked on each request when it is configured to run in a Web application. It compares the InstanceIdenfitier of the current request with the InstanceIdentifier from the previous request. If they are the same then its probable a failover has not occurred and the SESSION_FAILOVER is set to FALSE. If the two InstanceIdentifiers are not the same then its probable a failover has occured and the ServletFilter method will set the SESSION_FAILOVER attribute of the HttpSession to TRUE and update the SESSION_IDENTIFIER with the new InstanceIdentifier object.

It's all in the Packaging

That's the crux of the implementation. The rest of it is basically all about how to package and employ the ServletFilter.

Once the ServletFilter has been coded and compiled, it gets packaged into a JAR file. To make use of the instance comparison, the JAR file is put into the WEB-INF/lib directory of a Web application and configured to listen to requests using the WEB-INF/web.xml file.
    <filter>
<filter-name>InstanceTrackingFilter</filter-name>
<filter-class>sab.demo.cluster.InstanceTrackingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>InstanceTrackingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

With the ServletFilter enabled in this way, it tracks each request received by the Web application and does its 'thang.

This basic implementation will result in messages being printed to the System.out printstream when a hostname switch is detected.
steve~red~steve~1:06/05/12 01:04:14 
SessionId: 8c570a1e30d8c83b2772d4044562bf70c3813e5b1952 switched from:
stadp57.us.oracle.com:blue[1]@/scratch/sbutton/SOA_M1 to:
stadp57.us.oracle.com:red[1]@/scratch/sbutton/SOA_M1


Making use of it in an application
With the data injected into the HttpSession by the ServletFilter, an application can retrieve it and use it to make decisions. For the usual sorts of demo purposes, this usually entails simply displaying the current Instance serviving the request and whether a failover has occurred.

For example in a JSP it can be minimally used as:
    
Last request: <%=session.getAttribute("XXX_INSTANCE_IDENTIFIER")%>

Failover? <%=session.getAttribute("XXX_FAILOVER")%>


In closing and that's not all folks
That's about it as a first uyp basic exploration of this idea. We can uniquely identify an OC4J instance servicing requests and can be determine if its likely that a failover has occurred.

As a further path of exploration of this idea, I'll next detail how the InstanceTrackingFilter can utilize the notification support in JMX to broadcast notifications when session failovers occur. By exposing and broadcasting JMX notifications, any JMX compatible management client can subscribe to and receive the notifications, enabling it to provide information to administrators when session failovers occur.

No comments: