Thursday 16 October 2014

Distributed Session Management and Session Clustering

Distributed Session Management and Session Clustering

Introduction

Http is a stateless protocol, if there is a need to maintain the conversational state, session tracking is needed. For example, in a shopping cart application users keep on adding items into their cart using multiple requests. For every request, server should identify in which client’s cart the item is to be added. The standard ways to maintain http sessions are –
  •            User authorization
  •        Hidden fields
  •        URL rewriting
  •        Cookies
  •        Session tracking API

In a web based production environment we use load balancers to balance load across multiple servers. Managing sessions for such load balanced distributed environment is crucial specially to handle failovers.
We will be discussing some of the possible options to address the distributed session management and session clustering to overcome failovers. For simplicity we will be considering Apache web server and Tomcat as the app server only.

Load Balancing and Sticky Sessions

Sticky session is work on individual sessions. No session is shared with other tomcat servers. Server generated the session will keep that information of session, else no one know about that information. This can be done by Load balancer and mod_jk connectors at Apache level. If the underlying node is down sticky session don’t help, it only helps in load balancing.
To learn more about sticky session and load balancer setup we can the link below [http://www.ramkitech.com/2012/10/tomcat-clustering-series-part-2-session.html]

In Memory Session Broadcasting/Multicasting

Tomcat supports native in memory session clustering to handle load balancing, high performance and high availability. In this approach all the nodes participate in the cluster and every time a session is created the session information is shared to all the tomcat instances in the cluster.
To enable in memory session clustering in tomcat we need to configure a cluster in server.xml file available in ${CATALINA_HOME}/conf directory. The configuration snippet below will create a cluster in tomcat
<Engine name="Catalina" defaultHost="<some_host_name>" jvmRoute="<worker_name>”
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService" address="<228.0.0.4>" port="<45564>" frequency="500" dropTime="3000"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver" address="auto" port="4000" autoBind="100" selectorTimeout="5000" maxThreads="6"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
Note that we need to add this configuration to each Tomcat instance you wish to add to the cluster as a worker, and that each Engine element must have its own unique jvmRoute.
One possible constraint here is that the tomcat instances talk to each other creating a lot of communications among instances.
To learn more we can refer [http://www.mulesoft.com/tcat/tomcat-clustering]

Persisting Session State to Common Store

We can use common session storage like database, memcached, redis etc to store the session state all the tomcat instances will persist the session state to database or memcached and read session state from there.
  To use memcached as session storage we have to use tomcat version specific session storage manager. Open source contributions around the same are already available here [https://github.com/magro/memcached-session-manager].
 We have to perform the steps below to use memcached as session storage
i.                     put memcached-session-manager-x.y.z.jar, memcached-session-manager-tc<version>-x.y.z.jar, msm-kryo-serializer-x.y.z.jar and spymemcached-x.y.z.jar in ${CATALINA_HOME}/lib directory.
ii.                   edit tomcat/conf/context.xml, and add the following lines inside the <Context> tag (on all nodes):
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
      memcachedNodes="n1:localhost:11211"
      requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$" />
Or we can follow this link to complete the setup [http://wiki.alfresco.com/wiki/Tomcat_Session_Replication_with_Memcached]
For Redis we can follow this link [https://github.com/jcoleman/tomcat-redis-session-manager]


Session Clustering Using Hazelcast

We can use hazelcast to cluster sessions as well. To do so we must have the following -
·         Target application or web server should support Java 1.5+
·         Target application or web server should support Servlet 2.4+ spec.
·         Session objects that need to be clustered have to be Serializable.

Put the hazelcast and hazelcast-wm jars in your WEB-INF/lib directory.
Put the following xml into web.xml file. Make sure Hazelcast filter is placed before all the other filters if any; put it at the top for example
    <filter>
    <filter-name>hazelcast-filter</filter-name>
    <filter-class>com.hazelcast.web.WebFilter</filter-class>
    <init-param>
        <param-name>map-name</param-name>
        <param-value>[my-sessions]</param-value>
    </init-param>
    <init-param>
        <param-name>sticky-session</param-name>
        <param-value>[true]</param-value>
    </init-param>
    <init-param>
        <param-name>debug</param-name>
        <param-value>[true]</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>hazelcast-filter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>
<listener>
    <listener-class>com.hazelcast.web.SessionListener</listener-class>
</listener>
We need to change in hazelcast.xml file also like below –
join>
            <multicast enabled="[false/true]">
                <multicast-group>[224.2.2.3]</multicast-group>
                <multicast-port>[54327]</multicast-port>
            </multicast>
            <tcp-ip enabled="[true/false]">
                <interface>[127.0.0.1]</interface>
<interface>[127.0.0.1]</interface>
            </tcp-ip>
            <aws enabled="[false/true]">
                <access-key>[123]</access-key>
                <secret-key>[456]</secret-key>
                <tag-key>type</tag-key>
                <tag-value>hz-nodes</tag-value>
            </aws>
        </join>


Cross Platform Session Sharing

Some time we while developing enterprise applications we may end up developing a solution involving multiple technologies like Java, J2ee, PHP and Microsoft Technologies for different components and we may expect different components to work seamlessly forcing us to share session for cross platform. To support cross platform session sharing we have to take care of the following things –
i.                     Common session store
ii.                   Common session object structure (data structure)
iii.                 Common session serialization strategy
iv.                 Common deserialization strategy
 We will explore here session sharing strategy between a J2EE based application deployed in tomcat and PHP based application.

Tomcat and J2EE Session Management


Tomcat (J2EE containers) by default has its own session management and storage implementation. It generates a session cookie by the name JSESSIONID and has its own session storage data structure. We need to override this session cookie name as well as data structure and to do so we must have to write some container specific implementations.
Apache Shiro [http://shiro.apache.org/] gives us the facility to override the session management strategy out of the container.
A complete session management strategy including overriding the session cookie, session id generation and persisting the session in memcached and a custom session object (data structure ) can be found here – [https://github.com/badalb/shiro-session-management ]

Session Management from PHP /Zend

Implementing Memcached based session in Zend framework 2 :

/** configuration settings **/
/** File: module.config.php ; Location: <org/com/module/Api/ **/>

defining memcached service:

'service_manager'=>array(
        'factories' => array(
....
....
                   'doctrine.cache.my_memcache' => function ($sm) {
                        $cache = new \Doctrine\Common\Cache\MemcachedCache();                  
                        return $cache;
                    }
.....
)),.....

Defining Session configuration :

....
....
    'session' => array(
        'config' => array(
            'class' => 'Zend\Session\Config\SessionConfig',
            'options' => array(
                'name' => 'hu',
                'cookie_lifetime' => 0,
                'use_cookies' => true,
                'cookie_httponly' => true,
            ),
            'use_memcached'=> true
        ),
        'storage' => 'Zend\Session\Storage\SessionArrayStorage',
        'validators' => array(
            array(
                'Zend\Session\Validator\RemoteAddr',
                'Zend\Session\Validator\HttpUserAgent',
            ),
        ),
    ),
....
....
/** File: module.config.php - END **/

/** File: module.php  ; Location: <org/com/module/Api/config/ **/>

Setting memcached as Storage adapter:
NOTE: Storage adapters come with tons of methods to read, write and modify stored items and to get information about stored items and the storage.

         $cache = StorageFactory::factory(array(
                    'adapter' =>array( 'name' => 'memcached',
                       'options' => array(
                       'servers' => ['localhost'],
                           )
                ),
              ));
        $saveHandler = new \Api\Common\HUCache($cache);
        ini_set('session.serialize_handler', 'php_serialize');

       /**Session config and storage options **/
        $sessionConfig = new SessionConfig();
        $sessionConfig->setOptions($config['session']['config']['options']);

        $sessionManager = new SessionManager($sessionConfig);
        $sessionManager->setStorage(new \Zend\Session\Storage\SessionStorage());

        $sessionManager->setSaveHandler($saveHandler);

/** File: module.php - END **/

/** New File: HUCache.php ; Location : <org/com/module/Api/src/Api/Common/ **/>

/** Storage adapter with read, write and delete methods **/

class HUCache extends \Zend\Session\SaveHandler\Cache
{
    public function read($id)
    {
        $data = Decoder::decode($this->getCacheStorage()->getItem($id), Json::TYPE_ARRAY);
        return serialize($data);
    }

    /**
     * Write session data
     *
     */
    public function write($id, $data)
    {  
        $new_data = (array) unserialize($data);

        $data = Decoder::decode($this->getCacheStorage()->getItem($id));
        $final = $new_data;
        if (null !== $data && !empty($data))
        {
        //Merge the data
            $final = array_merge((array)$data ,$new_data);
        }
        $a = Encoder::encode($final);
        return $this->getCacheStorage()->setItem($id, $a);
    }
        /**
        * Destroy session
        *
        * @param string $id
        * @return bool
        */
    public function destroy($id)
    {
        return $this->getCacheStorage()->removeItem($id);
    }
}
/** File: HUCache.php - END **/


No comments:

Post a Comment