Grabduck

Хранение и шаринг сессий между приложениями

:

Идея статьи появилась вместе с задачей поставленной. В принципе была задача попробовать реализовать это и посмотреть, чем это будет сделать удобнее. В качестве языка был выбран Groovy, а точнее фреймворк Grails. Но конечная реализация будет такая же как и для Java, просто на Grails сделать это быстрее.

Задача звучала так: сделать прототип архитектуры на связке Grails + Jetty, web сессии должны шарится между всеми запущенными нодами. Хранение и шаринг только сессий.

Terracotta

Первым смотрел именно ее. После пары дней серфинга по их сайту остался негативный осадок :) Ну правда, очень все не структурировано и не понятно, создается ощущение, что они пытаются спрятать правду. Подробно описывать, что получилось не буду, так как этот вариант не подошел. Необходимо было, чтобы на разных серверах крутился сервер Terracota, и это сделать в принципе можно, проблема в том, что лишь одна нода является активной (**coordinate active Terracotta server**), все остальные лишь репликами, который в случае загибания выбирают нового вождя и слушаются его. При этом, на этих же серверах стояли бы экземпляры приложения и задумывалось, что каждый экземпляр будет использовать именно тот сервер Terracota, который установлен на той же машине. Но так не получилось, потому что можно подключится лишь к активному серверу. С помощью Terracota, это можно сделать, для этого необходимо в конфиге группы зеркал, можно посмотреть тут раздел Scaling the Terracotta Server Arraу. Реализовать это в старых версиях нельзя, но в продуктах BigMemory это есть. Лицензию они на почту присылают после скачивания.

Hazelcast

После неудачи с терракотой начал пробовать этот продукт, версия 3.1. Собственно оказалось все очень просто. Далее опишу несколько шагов для создания проекта. В принципе все просто и IDE не понадобится, все делалось из консоли.

Создание Grails приложения

Grails используется версии 2.3.1.Тут все просто.

grails create-app grails-hazelcast

После инициализации приложение нужно поправить скрипт для сборки приложения (я пользуюсь vim, вы редактируйте чем угодно):
cd grails-hazelcast
vim grails-app/conf/BuildConfig.groovy

Для начала удаляем плагин для томката и добавляем для jetty. Так же добавляем зависимость для Hazelcast:
dependencies {
        ...
        compile "com.hazelcast:hazelcast-all:3.1"
        ...
    }

plugins {
       ...
        build ":jetty:2.0.3"
       ...
}

Добавим наш пакет в конфиг для логгера:
vim grails-app/conf/Config.groovy
log4j = {
   ...
    info   'grails.hazelcast'
   ...
}

Также необходимо установить шаблоны в него, так как нам понадобится изменить web.xml
grails install-templates
vim src/templates/war/web.xml

что нужно прописывать описано в документации на сайте, раздел Http Session Clustering with HazelcastWM (смотрите нужную версию документации).
таким образом добавляем описанную там структуру в наш web.xml, единственно что изменил, это параметр указывающий конфигурационный файл config-location. И параметр map-name указывает имя коллекции, куда будут сохранятся сессии.
<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>cookie-name</param-name>
            <param-value>hazelcast.sessionId</param-value>
	</init-param>


    
        <init-param>
          <param-name>cookie-secure</param-name>
          <param-value>false</param-value>
	</init-param>
 
        <init-param>
          <param-name>cookie-http-only</param-name>
          <param-value>false</param-value>
        </init-param>
       
       <init-param>
            <param-name>debug</param-name>
            <param-value>true</param-value>
        </init-param>
 
        <init-param>
          <param-name>config-location</param-name>
          <param-value>/WEB-INF/hazelcast.xml</param-value>
        </init-param>
 
        <init-param>
          <param-name>instance-name</param-name>
          <param-value>default</param-value>
       </init-param>

        <init-param>
        <param-name>use-client</param-name>
        <param-value>false</param-value>
       </init-param>

       <init-param>
         <param-name>shutdown-on-destroy</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>

Теперь собственно нужно создать этот конфиг Hazelcast (откуда брал не помню, или из доки или из примеров):

vim web-app/WEB-INF/hazelcast.xml
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config
    http://www.hazelcast.com/schema/config/hazelcast-config-3.1.xsd"
    xmlns="http://www.hazelcast.com/schema/config"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <group>
        <name>dev</name>
        <password>dev-pass</password>
    </group>
    <network>
        <port auto-increment="true">5701</port>
        <join>
            <multicast enabled="true">
                <multicast-group>224.2.2.3</multicast-group>
                <multicast-port>54327</multicast-port>
            </multicast>
            <tcp-ip enabled="false">
                <interface>127.0.0.1</interface>
            </tcp-ip>
            <aws enabled="false">
                <access-key>my-access-key</access-key>
                <secret-key>my-secret-key</secret-key>
                <region>us-west-1</region>
                <security-group-name>hazelcast-sg</security-group-name>
                <tag-key>type</tag-key>
                <tag-value>hz-nodes</tag-value>
            </aws>
        </join>
        <interfaces enabled="false">
            <interface>10.56.10.*</interface>
        </interfaces>
        <ssl enabled="false" />
        <socket-interceptor enabled="false" />
        <symmetric-encryption enabled="false">
            <algorithm>PBEWithMD5AndDES</algorithm>
            <salt>thesalt</salt>
            <password>thepass</password>
            <iteration-count>19</iteration-count>
        </symmetric-encryption>
        <asymmetric-encryption enabled="false">
            <algorithm>RSA/NONE/PKCS1PADDING</algorithm>
            <keyPassword>thekeypass</keyPassword>
            <keyAlias>local</keyAlias>
            <storeType>JKS</storeType>
            <storePassword>thestorepass</storePassword>
            <storePath>keystore</storePath>
        </asymmetric-encryption>
    </network>
    <partition-group enabled="false"/>
    <management-center enabled="false" update-interval="3" >http://127.0.0.1:8080/mancenter</management-center>
    <executor-service>
        <core-pool-size>16</core-pool-size>
        <max-pool-size>64</max-pool-size>
        <keep-alive-seconds>60</keep-alive-seconds>
    </executor-service>
    <queue name="default">
        <max-size-per-jvm>0</max-size-per-jvm>
        <backing-map-ref>default</backing-map-ref>
    </queue>
    <map name="default">
        <backup-count>1</backup-count>
        <time-to-live-seconds>0</time-to-live-seconds>
        <max-idle-seconds>0</max-idle-seconds>
        <eviction-policy>NONE</eviction-policy>
        <eviction-percentage>25</eviction-percentage>
        <merge-policy>hz.ADD_NEW_ENTRY</merge-policy>
    </map>
    <properties>
        <property name="hazelcast.logging.type">log4j</property>
    </properties>
</hazelcast>

Менялись параметры <interface>10.56.10.*</interface> тут указать свою сеть. И еще возможно следует отметить параметр management-center, там указывается ссылка на консоль для мониторинга. Проблема правда, что в бесплатной версии она работает с максимум двумя экземплярами. Скачать можно тут, в папке bin есть скрипт для запуска.

Теперь создадим контроллер для тестирования:

grails create-controller TestSessions
vim grails-app/controllers/grails/hazelcast/TestSessionsController.groovy
package grails.hazelcast

import com.hazelcast.client.*
import com.hazelcast.config.*
import com.hazelcast.core.*
import groovy.util.logging.*

@Log
class TestSessionsController {

    def index() {
        session.setAttribute("testAttr","testVal");
        Config cfg = new Config();
        HazelcastInstance hz = Hazelcast.newHazelcastInstance(cfg);
        IMap map = hz.getMap("my-sessions");
        log.info "SIZE========"+map.size();
    }
}

Собственно все, при каждом обращении он будет создавать новый экземпляр Hazecast (который будет взаимодействовать с уже созданными, если таковые имеются в указанной в конфиге сети). В сессию кладем аттрибут, чтобы там что либо лежало, так как сохраняются в мапу, только сериализуемые объекты.
Собираем варку и кладем ее в jetty (или в несколько экземпляров jetty, так же возможно на разные машины):

grails war

Кому лень все делать, ссылка на GitHub
Запускаем и радуемся.