Miles to go …

August 12, 2009

TOTD #92: Session Failover for Rails applications running on GlassFish

Filed under: glassfish, rails, totd — Tags: , — arungupta @ 3:00 am

The GlassFish High Availability allows to setup a cluster of GlassFish instances and achieve highly scalable architecture using in-memory session state replication. This cluster can be very easily created and tested using the “clusterjsp” sample bundled with GlassFish. Here are some clustering related entries published on this blog so far:

  • TOTD #84 shows how to setup Apache + mod_proxy balancer for Ruby-on-Rails load balancing
  • TOTD #81 shows how to use nginx to front end a cluster of GlassFish Gems
  • TOTD #69 explains how a GlassFish cluster can be front-ended using Sun Web Server and Load Balancer Plugin
  • TOTD #67 shows the same thing using Apache httpd + mod_jk

#67 & #69 uses a web application “clusterjsp” (bundled with GlassFish) that uses JSP to demonstrate in-memory session replication state replication. This blog creates a similar application “clusterrails” – this time using Ruby-on-Rails and deploy it on GlassFish v2.1.1. The idea is to demonstrate how Rails applications can leverage the in-memory session replication feature of GlassFish.

Rails applications can be easily deployed as a WAR file on GlassFish v2 as explained in TOTD #73. This blog will guide through the steps of creating the Controller and View to mimic “clusterjsp” and configuring the Rails application for session replication.

  1. Create a template Rails application and create/migrate the database. Add a Controller/View as:
    ~/samples/jruby/session >~/tools/jruby/bin/jruby script/generate controller home index
    JRuby limited openssl loaded. gem install jruby-openssl for full support.

    http://wiki.jruby.org/wiki/JRuby_Builtin_OpenSSL

    exists  app/controllers/
    exists  app/helpers/
    create  app/views/home
    exists  test/functional/
    create  test/unit/helpers/
    create  app/controllers/home_controller.rb
    create  test/functional/home_controller_test.rb
    create  app/helpers/home_helper.rb
    create  test/unit/helpers/home_helper_test.rb
    create  app/views/home/index.html.erb

  2. Edit the controller in “app/controllers/home_controller.rb” and change the code to (explained below):
    class HomeController < ApplicationController
    include Java

    def index
    @server_served = servlet_request.get_server_name
    @port = servlet_request.get_server_port
    @instance = java.lang.System.get_property “com.sun.aas.instanceName”
    @server_executed = java.net.InetAddress.get_local_host().get_host_name()
    @ip = java.net.InetAddress.get_local_host().get_host_address
    @session_id = servlet_request.session.get_id
    @session_created = servlet_request.session.get_creation_time
    @session_last_accessed = servlet_request.session.get_last_accessed_time
    @session_inactive = servlet_request.session.get_max_inactive_interval

    if (params[:name] != nil)
    servlet_request.session[params[:name]] = params[:value]
    end

    @session_values = “”
    value_names = servlet_request.session.get_attribute_names
    unless (value_names.has_more_elements)
    @session_values = “<br>No parameter entered for this request”
    else
    @session_values << “<UL>”
    while (value_names.has_more_elements)
    param = value_names.next_element
    unless (param.starts_with?(“__”))
    value = servlet_request.session.get_attribute(param)
    @session_values << “<LI>” + param + ” = ” + value + “</LI>”
    end
    end
    @session_values << “</UL>”
    end

    end

    def adddata
    servlet_request.session.set_attribute(params[:name], params[:value])
    render :action => “index”
    end

    def cleardata
    servlet_request.session.invalidate
    render :action => “index”
    end
    end

    The “index” action initializes some instance variables using the “servlet_request” variable mapped from “javax.servlet.http.ServletRequest” class. The “servlet_request” provides access to different properties of the request received such as server name/port, host name/address and others. It also uses an application server specific property ”com.sun.aas.instanceName” to fetch the name of particular instance serving the request. In this blog we’ll create a cluster with 2 instances. The action then prints the servlet session attributes name/value pairs entered so far.

    The “adddata” action takes the name/value pair entered on the page and stores them in the servlet request. The “cleardata” action clears any data that is storied in the session.

  3. Edit the view in “app/views/home/index.html.erb” and change to (explained below):
    <h1>Home#index</h1>
    <p>Find me in app/views/home/index.html.erb</p>
    <B>HttpSession Information:</B>
    <UL>
    <LI>Served From Server:   <b><%= @server_served %></b></LI>
    <LI>Server Port Number:   <b><%= @port %></b></LI>
    <LI>Executed From Server: <b><%= @server_executed %></b></LI>
    <LI>Served From Server instance: <b><%= @instance %></b></LI>
    <LI>Executed Server IP Address: <b><%= @ip %></b></LI>
    <LI>Session ID:    <b><%=
    @session_id %></b></LI>
    <LI>Session Created:  <%= @session_created %></LI>
    <LI>Last Accessed:    <%= @session_last_accessed %></LI>
    <LI>Session will go inactive in  <b><%= @session_inactive %> seconds</b></LI>
    </UL>
    <BR>
    <% form_tag “/session/home/index” do %>
    <label for=”name”>Name of Session Attribute:</label>
    <%= text_field_tag :name, params[:name] %><br>

    <label for=”value”>Value of Session Attribute:</label>
    <%= text_field_tag :value, params[:value] %><br>

    <%= submit_tag “Add Session Data” %>
    <% end  %>
    <% form_tag “/session/home/cleardata” do %>
    <%= submit_tag “Clear Session Data” %>
    <% end %>
    <% form_tag “/session/home/index” do %>
    <%= submit_tag “Reload Page” %>
    <% end %>
    <BR>
    <B>Data retrieved from the HttpSession: </B>
    <%= @session_values %>

    The view dumps the property value retrieved from the servlet context in the action. Then it consists of some forms to enter the session name/value pairs, clear the session and reload the page. The application is now ready, lets configure it for WAR packaging.

  4. Generate a template “web.xml” and copy it to “config” directory as:
    ~/samples/jruby/session >~/tools/jruby/bin/jruby -S warble war:webxml
    mkdir -p tmp/war/WEB-INF
    ~/samples/jruby/session >cp tmp/war/WEB-INF/web.xml config/
    1. Edit “tmp/war/WEB-INF/web.xml” and change the first few lines from:
      <!DOCTYPE web-app PUBLIC
      “-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
      “http://java.sun.com/dtd/web-app_2_3.dtd”>
      <web-app>

      to

      <web-app version=”2.4″ xmlns=”http://java.sun.com/xml/ns/j2ee” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xsi:schemaLocation=”http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd”>

      This is required because the element to be added next is introduced in the Servlet 2.4 specification.

    2. Add the following element:
      <distributable/>

      as the first element, right after “<web-app>”. This element marks the web application to be distributable across multiple JVMs in a cluster.

  5. Generate and configure “warble/config.rb” as described in TOTD #87. This configuration is an important step otherwise you’ll encounter JRUBY-3789. Create a WAR file as:
    ~/samples/jruby/session >~/tools/jruby/bin/jruby -S warble
    mkdir -p tmp/war/WEB-INF/gems/specifications
    cp /Users/arungupta/tools/jruby-1.3.0/lib/ruby/gems/1.8/specifications/rails-2.3.2.gemspec tmp/war/WEB-INF/gems/specifications/rails-2.3.2.gemspec

    . . .

    mkdir -p tmp/war/WEB-INF
    cp config/web.xml tmp/war/WEB-INF
    jar cf session.war  -C tmp/war .

  6. Download latest GlassFish v2.1.1, install/configure GlassFish and create/configure/start a cluster using the script described here. Make sure to change the download location and filename in the script. This script creates a cluster “wines” with two instances – “cabernet” runing on the port 58080 and “merlot” running on the port 58081.
  7. Deploy the application using the command:
    ~/samples/jruby/session >asadmin deploy –target wines –port 5048 –availabilityenabled=true session.war

Now, the screenshots from the two instances are shown and explained below. The two (or more) instances are front-ended by a load balancer so none of this is typically visible to the user but it helps to understand.
Here is a snapshot of this application deployed on “cabernet”:

The instance name and the session id is highlighted in the red box. It also shows the time when the session was created in “Session Created” field.

And now the same application form “merlot”:

Notice, the session id exactly matches the one from the “cabernet” instance. Similarly “Session Created” matches but “Last Accessed” does not because the same session session is accessed from a different instance.

Lets enter some session data in the “cabernet” instance and click on “Add Session Data” button as shown below:

The session attribute is “aaa” and value is “111″. Also the “Last Accessed” time is updated. In the “merlot” page, click on the “Reload Page” button and the same session name/value pairs are retrieved as shown below:

Notice, the “Last Accessed” time is after the time showed in “cabernet” instance. The session information added in “cabernet” is automatically replicated to the “merlot” instance.

Now, lets add a new session name/value pair in “merlot” instance as shown below:

The “Last Accessed” is updated and the session name/value pair (“bbb”/”222″) is shown in the page. Click on “Reload page” in “cabernet” instance as shown below:

This time the session information added to “merl
ot” is replicated to “cabernet”.

So any session information added in “cabernet” is replicated to “merlot” and vice versa.

Now, lets stop “cabernet” instance as shown below:

and click on “Reload Page” in “merlot” instance to see the following:

Even though one instance from which the session data was added is stopped, the replicating instance continues to serve both the session values.

As explained earlier, these two instances are front-ended by a load-balancer typically running at port 80. So the user makes a request to port 80 and the correct session values are served even if one of the instance goes down and there by providing in-memory session replication.

Please leave suggestions on other TOTD that you’d like to see. A complete archive of all the tips is available here.

Technorati: totd glassfish clustering rubyonrails jruby highavailability loadbalancer

Share and Enjoy:
  • Print
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • DZone
  • StumbleUpon
  • Technorati
  • Twitter
  • Slashdot
Related posts:
  1. TOTD #84: Using Apache + mod_proxy_balancer to load balance Ruby-on-Rails running on GlassFish
  2. TOTD #117: Invoke a JAX-WS Web service from a Rails app deployed in GlassFish
  3. TOTD #104: Popular Ruby-on-Rails applications on GlassFish v3 – Redmine, Typo, Substruct
  4. TOTD #11: Setup Mongrel cluster for JRuby-on-Rails applications on Unix
  5. TOTD #63: jmx4r gem – How to manage/monitor your Rails/Merb applications on JRuby/GlassFish ?

3 Comments »

  1. Awesome Post Arun, just what I was looking for! :)

    Many Thanks Grant

    Comment by Grant McLaren — August 19, 2009 @ 6:35 pm

  2. Thanks Grant!

    Are you using Rails/GlassFish ? How do you plan to use this feature ?

    Comment by Arun Gupta — August 19, 2009 @ 7:08 pm

  3. Hi! I was surfing and found your blog post… nice! I love your blog. :) Cheers! Sandra. R.

    Comment by sandraraven15 — September 9, 2009 @ 4:36 am

RSS feed for comments on this post. TrackBack URL

Leave a comment

The views expressed on this blog are my own and do not necessarily reflect the views of Oracle.
Powered by WordPress