Skip to main content

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

Posted by arungupta on August 12, 2009 at 6:10 AM PDT



The href="http://developers.sun.com/appserver/reference/techart/glassfishcluster/">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 href="http://blogs.sun.com/jclingan/entry/glassfish_clustering_in_under_10">very
easily created and tested using the "clusterjsp" sample
bundled with GlassFish. Here are some clustering related entries
published on this blog so far:

  • href="http://blogs.sun.com/arungupta/entry/totd_84_using_apache_mod">TOTD
    #84 shows how to setup Apache + mod_proxy balancer for
    Ruby-on-Rails load balancing
  • href="http://blogs.sun.com/arungupta/entry/totd_81_how_to_use">TOTD
    #81 shows how to use nginx to front end a cluster
    of GlassFish Gems
  • href="http://blogs.sun.com/arungupta/entry/totd_69_glassfish_high_availability">TOTD
    #69 explains how a GlassFish cluster can be front-ended using
    Sun
    Web Server
    and Load Balancer Plugin
  • href="http://blogs.sun.com/arungupta/entry/totd_67_how_to_front">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 href="http://blogs.sun.com/arungupta/entry/totd_73_jruby_and_glassfish">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:


    style="text-align: left; background-color: rgb(204, 204, 255); width: 100%;"
    cellpadding="2" cellspacing="2">
    ~/samples/jruby/session > style="font-weight: bold;">~/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):


    style="text-align: left; background-color: rgb(204, 204, 255); width: 100%;"
    cellpadding="2" cellspacing="2">
    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):


    style="text-align: left; background-color: rgb(204, 204, 255); width: 100%;"
    cellpadding="2" cellspacing="2">
    <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:


    style="text-align: left; background-color: rgb(204, 204, 255); width: 100%;"
    cellpadding="2" cellspacing="2">
    ~/samples/jruby/session > style="font-weight: bold;">~/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:


      style="text-align: left; background-color: rgb(204, 204, 255); width: 100%;"
      cellpadding="2" cellspacing="2">
      <!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


      style="text-align: left; background-color: rgb(204, 204, 255); width: 100%;"
      cellpadding="2" cellspacing="2">
      <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:


      style="text-align: left; background-color: rgb(204, 204, 255); width: 100%;"
      cellpadding="2" cellspacing="2">
      <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 href="http://blogs.sun.com/arungupta/entry/totd_87_how_to_fix">TOTD
    #87. This configuration is an important step otherwise you'll
    encounter JRUBY-3789.
    Create a WAR file as:


    style="text-align: left; background-color: rgb(204, 204, 255); width: 100%;"
    cellpadding="2" cellspacing="2">
    ~/samples/jruby/session > style="font-weight: bold;">~/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 href="http://download.java.net/javaee5/v2.1.1/promoted/">GlassFish
    v2.1.1, install/configure GlassFish and
    create/configure/start a cluster using the script href="http://blogs.sun.com/arungupta/entry/glassfish_asadmin_cli_driven_cluster">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:


    style="text-align: left; background-color: rgb(204, 204, 255); width: 100%;"
    cellpadding="2" cellspacing="2">
    ~/samples/jruby/session > style="font-weight: bold;">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":



alt=""
src="http://blogs.sun.com/arungupta/resource/ror/rails-ha-gf211-cabernet-blank-page.png">



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":



alt=""
src="http://blogs.sun.com/arungupta/resource/ror/rails-ha-gf211-merlot-blank-page.png">



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:



alt=""
src="http://blogs.sun.com/arungupta/resource/ror/rails-ha-gf211-cabernet-aaa.png">



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:



alt=""
src="http://blogs.sun.com/arungupta/resource/ror/rails-ha-gf211-merlot-aaa.png">



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:



alt=""
src="http://blogs.sun.com/arungupta/resource/ror/rails-ha-gf211-merlot-bbb.png">



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:



alt=""
src="http://blogs.sun.com/arungupta/resource/ror/rails-ha-gf211-cabernet-bbb.png">



This time the session information added to "merlot" 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:



alt=""
src="http://blogs.sun.com/arungupta/resource/ror/rails-ha-gf211-cabernet-stopped.png">



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



alt=""
src="http://blogs.sun.com/arungupta/resource/ror/rails-ha-gf211-merlot-only.png">



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 href="http://blogs.sun.com/arungupta/tags/totd">here.




Technorati: totd
glassfish
clustering
rubyonrails
jruby
highavailability
loadbalancer

Related Topics >>