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.
- 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 |
- 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.
- 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.
- 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/ |
- 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.
- Add the following element:
as the first element, right after “<web-app>”. This element marks the web application to be distributable across multiple JVMs in a cluster.
- 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 .
|
- 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.
- 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