Идея статьи появилась вместе с задачей поставленной. В принципе была задача попробовать реализовать это и посмотреть, чем это будет сделать удобнее. В качестве языка был выбран 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
Запускаем и радуемся.