Теория и практика Java: Репликация состояния на уровне Web

Существует более одного способа кластеризации Web-приложений

Большинство нетривиальных Web-приложений требуют поддержки сессионного состояния, например, содержание корзины пользователя. Реализация управления и репликации состояния в кластерном серверном приложении, оказывает значительное влияние на масштабируемость самого приложения. Большинство приложений Java хранят состояние в HttpSession, предоставленном сервлетом прикладного программного интерфейса (Servlet API). В данной статье приведен анализ некоторых вариантов репликации HttpSession и описание наиболее эффективного использования HttpSession, которое гарантирует хорошую масштабируемость и производительность.

Брайан Гетц, главный консультант, Quiotix

Брайан Гетц (Brian Goetz) - консультант по ПО и последние 15 лет работал профессиональными разработчиком ПО. Сейчас он является главным консультантом в фирме Quiotix, занимающейся разработкой ПО и консалтингом и находящейся в Лос-Альтос, Калифорния. Следите за публикациями Брайана в популярных промышленных изданиях. Вы можете связаться с Брайаном по адресу brian@quiotix.com



08.02.2007

Если Вы разрабатываете серверное приложение J2EE или J2SE, то всегда есть шанс, что Вы в той или иной форме используете сервлеты Java - либо явно через уровень представления, например, JSP, Velocity или WebMacro, либо через Web-службы на базе сервлетов, например, Axis или Glue. Одной из наиболее важных функций, предоставляемых интерфейсом Servlet API, является управление сессиями - подтверждение права на доступ, прекращение срока действия и поддержка состояний сессий пользователей через интерфейс HttpSession.

Состояния сессий

Практически у каждого Web-приложения есть какое-либо состояние сессии, которое может быть простым, например запоминание начала сеанса, или более подробная история Ваших сеансов, например, содержание корзины покупателя, результаты оплаты предыдущих запросов или полная история ответов на 20 страницах динамического опросника. Так как сам протокол HTTP не использует информацию о состоянии, то необходимо где-либо хранить состояния сессий и связать их с Вашей сессией просмотра так, чтобы можно было легко их извлечь при следующем запросе страницы того же Web-приложения. К счастью, J2EE предоставляет несколько средств для управления состоянием сессий - состояние может быть сохранено на уровне данных, на Web-уровне с помощью интерфейса HttpSession из Servlet API, на уровне Enterprise JavaBeans (EJB) с использованием сессионных компонентов с диалоговым состоянием или даже на уровне клиента cookie-файлов или скрытых полей формы. К сожалению, неумелое управление состоянием сессий может стать причиной серьезных проблем с точки зрения производительности.

Лучше всего если Ваше приложение пригодно для хранения пользовательского состояния в HttpSession. Сохранение состояния сессии на стороне клиента с помощью HTTP cookie-файлов или скрытых полей формы подвергает риску всю безопасность, так как открывает доступ к части внутренних свойств Вашего приложения. (Один из первых сайтов электронной коммерции хранил содержимое корзины товаров, включая цену, в скрытых полях формы, и стал жертвой относительно простой наживы, так как такой прием позволял каждому догадливому HTML- и HTTP-пользователю покупать любой товар за $0.01. Вот так.). Кроме того, использование cookie-файлов или скрытых полей формы не совсем удобно, так как подвержено ошибкам и довольно нестабильно (данный подход не будет работать у тех пользователей, которые запретили использование cookie-файлов в браузере).

Другие способы хранения состояния сервера в J2EE-приложениях заключаются в использовании сессионных компонентов с диалоговым состоянием, либо в хранении диалогового состояния в базе данных. Несмотря на то, что сессионные компоненты с диалоговым состоянием предоставляют значительную гибкость в управлении состоянием сессии, все еще существуют преимущества хранения состояния сессии на Web-уровне. Если бизнес-объекты не имеют состояния, то приложение всегда можно расширить посредством увеличения числа Web-серверов, а не увеличением числа Web-серверов и EJB-контейнеров, что, как правило, легче реализовать при меньших затратах. Другое преимущество использования HttpSession для хранения диалогового состояния заключается в том, что интерфейс Servlet API предлагает простой способ оповещения о прекращении сессии. Хранение диалогового состояния в базе данных может быть чрезмерно затратным.

В спецификации сервлета не указано то, что контейнер сервлета реализует репликацию и длительное хранение любой сессии, но оговорено, что репликация сессии в первую очередь является важной составляющей смысла существования сервлетов, и налагает некоторые требования на те контейнеры, которые реализуют репликацию сессии. Репликация сессии предоставляет множество преимуществ - распределение нагрузки, масштабируемость, отказоустойчивость и высокий уровень работоспособности. Следовательно, большинство сервлетов поддерживают некоторую форму HttpSession-репликации, однако, механизм, конфигурация и временные соотношения репликации зависят от реализации.


Интерфейс HttpSession API

Проще говоря, интерфейс HttpSession поддерживает некоторые методы, используемые сервлетом, JSP-страницей или другим компонентом уровня представления данных для сопровождения информации сессий нескольких HTTP-запросов. Сессия связана с определенным пользователем, но коллективно используется всеми сервлетами Web-приложения - что не свойственно отдельному сервлету. Для удобства сессию можно представлять, как Map (таблицу), которая хранит объекты во время сессии - Вы можете хранить атрибуты сессии по названию с помощью setAttribute и извлекать их с помощью getAttribute. Интерфейс HttpSession также содержит методы времени существования сессии, например,invalidate() (оповещает контейнер о том, что сессия должна быть отклонена). В листинге 1 показаны наиболее часто используемые элементы интерфейса HttpSession:

Листинг 1. Интерфейс HttpSession API
public interface HttpSession {
    Object getAttribute(String s);
    Enumeration getAttributeNames();
    void setAttribute(String s, Object o);
    void removeAttribute(String s);

    boolean isNew();
    void invalidate();
    void setMaxInactiveInterval(int i);
    int getMaxInactiveInterval();
    ...
}

Теоретически, состояние сессии можно полностью реплицировать в кластере, таким образом, что любой узел кластера может обслуживать любой запрос, а неинтеллектуальный распределитель нагрузки будет просто направлять запрос по кругу, избегая неработающих хостов. Однако, такая жесткая репликация обладает слишком высокими затратами с точки зрения производительности и сложна в реализации и может привести к возникновению проблем с масштабируемостью, так как кластер ограничен определенными размерами.

Более простой подход заключается в сочетании распределения нагрузки с аффинным подобием сессии - распределитель нагрузки способен связывать соединения с сессиями и внутри сессии маршрутизировать последующие запросы к тому же серверу. Такая функциональная возможность поддерживается многими аппаратными и программными распределителями нагрузки, и означает, что информация о репликациях сессий доступна только тогда, когда перестает работать главный хост и необходимо переключить сессии на другой сервер.


Методики репликации

У репликации есть ряд таких потенциальных преимуществ, как надежность, отказоустойчивость и масштабируемость. Кроме того, доступно множество методов репликации сессии, а выбор метода будет зависеть от размера кластера приложения, цели репликации, и механизмов репликации, поддерживаемых ваших контейнером сервлета. Репликация затратна с точки зрения производительности, включая циклы центрального процессора (для сериализации объектов, хранимых в сессии), пропускную способность сети (для распространения обновлений) и, для схем, находящихся на дисках, затраты на запись на диск или в базу данных.

Почти все сервлет-контейнеры выполняют репликацию HttpSessionпосредством сериализации объектов, хранящихся в HttpSession, поэтому, если Вы хотите создать приложение, которое можно распространить, то Вы должны убедиться в том, что в сессии присутствуют только сериализованные объекты. (В некоторых контейнерах есть особое управление для таких элементов, как EJB-ссылки, среды транзакций и других типов J2EE-объектов, которые не поддаются сериализации.)

Репликация на базе JDBC

Один из способов репликации сессии заключается в простой сериализации содержания сессии и записи его в базу данных. Такой способ достаточно прямолинеен и обладает преимуществом, которое не только позволяет переживать остановку различных хостов, но и то, что данные сессии могут пережить отказ целого кластера. Недостаток репликации, поддерживающей базу данных, заключается в больших потерях производительности - транзакции баз данных забирают много ресурсов. В то время как она хорошо масштабируется на уровне Web, на уровне данных может произойти ошибка масштабирования - если кластер становиться довольно большим, то масштабирование уровня данных может быть затруднено или чрезмерно затратно, с точки зрения ресурсов, для размещения всего объема данных сессии.

Репликация на базе файлов

Репликация на базе файлов подобна использованию базы данных для хранения сериализованных сессий, за исключением того, что файловый сервер коллективного доступа используется для хранения данных сессий, а не баз данных. Данный подход отличается от использования баз данных низкими затратами (аппаратные затраты, лицензии на программное обеспечение, и непроизводительные затраты на вычисления), ценой надежности (базы данных гарантируют более длительное хранение информации, чем файловые системы).

Репликация на базе памяти

Еще один способ репликации заключается в открытии доступа к копиям данных сериализованной сессии одному или более серверам в кластере. Репликация всех сессий на все хосты обеспечивает максимальную надежность и легка для обработки распределителем нагрузки, но в итоге приводит к ограничению объема кластера из-за требований потребления памяти каждым узлом и использования пропускной способности сети сообщениями репликации. Некоторые серверы приложений поддерживают репликацию на базе памяти на "дружественные" узлы, где каждая сессия располагается на одном первичном сервере и на одном (или нескольких) резервных серверах. Такие схемы лучше масштабируются, чем репликация всех сессий на все серверы, но усложняют работу распределителя нагрузки, когда ему необходимо переместить сессию на другой сервер, так как ему нужно рассчитать у какого сервера(ов) та же самая сессия.

Расчеты времени

Помимо принятия решения о том, как хранить реплицированные данные сессии, также необходимо решить, когда реплицировать эти данные. Наиболее надежный, но вместе с тем и наиболее ресурсоемкий, подход заключается в том, чтобы реплицировать данные при каждом их изменении (например, в конце каждой активации сервлета). Менее ресурсоемкий, но более рискованный с точки зрения потери некоторых данных при восстановлении системы после отказа, подход заключается в том, чтобы проводить репликацию данных не чаще, чем каждые N секунд.

Вопрос о времени заключается в том, что либо реплицировать всю сессию, либо попробовать реплицировать только те атрибуты в сессии, которые изменились (которые могут содержать значительно меньшее количество данных). Это обычный компромисс между надежностью и производительностью, который зависит от Вашего приложения. Разработчики сервлетов должны понимать, что в случае восстановления после отказа состояние сессии может быть "устаревшим" (из-за копий, сделанных несколько запросов назад), и должны быть готовы работать с сессией с не самыми последними данными. (Например, если шаг 3 опроса создает атрибут сессии, и когда пользователь находится на шаге 4, а запрос из-за отказа переадресуется на систему, у которой копия состояния сессии снята два шага (запроса) назад, то код сервлета для шага 4 должен быть готов к тому, что не найдет данного атрибута в сессии и соответственно принять меры, например переадресовать, а не предполагать, что данный атрибут есть в сессии и при его отсутствии выдавать сообщение NullPointerException.)


Поддержка контейнера

Контейнеры сервлета различаются по опциям репликации HttpSession и принципам конфигурирования этих опций. IBM WebSphere® предлагает огромнейший выбор опций репликации, включая различные репликации в оперативной памяти или репликации в базах данных, выбор времени репликации - по окончанию работы сервлета или по времени, а также выбор между копированием "моментальных снимков" всей сессии или только измененных атрибутов. Репликация на базе памяти основана на публикации и подписке JMS, которая может реплицировать по всем аналогам, одиночную "дружественную" копию, или сервер, предназначенный для репликаций.

WebLogic также предлагает большой выбор: на базе оперативной памяти (используя одиночную дружественную копию), на базе файла или базы данных. JBoss, при использовании контейнеров сервлетов Tomcat или Jetty, выполняет репликацию на базе памяти, предоставляя выбор времени репликации - по окончанию работы сервлета или по времени, и опцию (в JBoss 3.2 и более поздних версиях) для копирования "моментальных снимков" только измененных атрибутов. Tomcat 5.0 предлагает репликацию на базе памяти для всех узлов кластеров. Кроме того, при использовании таких проектов, как WADI, репликация сессии может добавляться к контейнерам сервлетов, например, Tomcat или Jetty, при помощи механизма фильтрации сервлета.


Улучшение производительности в распределенных Web-приложениях

Вне зависимости от механизма, который Вы выберете для репликации сессии, Вы можете улучшить производительность и масштабируемость вашего Web-приложения несколькими способами. Прежде всего, помните, чтобы получить пользу от репликации сессии, Вам надо отметить в дескрипторе размещения Ваше Web-приложение, как распределенное, и убедиться в том, что все, что помещено в сессии, можно сериализовать.

Оставляйте сессию минимальной

Так как репликация сессий отличается потреблением ресурсов, которое растет с увеличением размеров графов объекта, хранимого в сессии, то Вам необходимо стараться поместить как можно меньше данных. Данный прием уменьшает издержки сериализации, требования к пропускной способности сети, а также требования к жесткому диску. В частности, хранение в сессии общедоступных объектов считается дурным тоном, так как они будут реплицироваться для каждой сессии, к которой они принадлежат.

Не пропускайте setAttribute

При изменении атрибутов в сессии, остерегайтесь того, что, если контейнер сервлета пытается выполнить минимальное обновление (только уже измененные копируемые атрибуты), то он может не заметить, что Вы изменили данный атрибут, если Вы не вызываете setAttribute. (Представьте, что у Вас есть Vector в сессии, который представляет собой элементы в корзине покупок - если Вы просто вызовете getAttribute() для получения Vector, а затем добавите что-то к нему без последующего вызова setAttribute, то контейнер может не заметить, что Vector был изменен.)

Используйте мелкоструктурные атрибуты сессии

Для контейнеров, которые поддерживают минимальное обновление, Вы можете уменьшить затраты ресурсов на репликацию сессии, используя в сессии множественные, мелкоструктурные объекты, вместо одного большого монолитного объекта. Таким образом, изменения быстро изменяемых данных не будут вынуждать контейнер сериализовать и копировать медленно изменяемые данные.

Деактивация после завершения сессии

Если вы знаете, что пользователь завершил работу с сессией (например, пользователь выбрал выход из данной системы), обязательно вызовите HttpSession.invalidate(). Иначе, сессия будет продолжать работать, пока не истечет срок, из-за чего будет продолжено потребление памяти, возможно в течение достаточно большого количества времени, в зависимости от времени работы сессии. Многие контейнеры сессий устанавливают предел используемой всеми сессиями памяти, и при достижении предела, производят сериализацию и запись на диск сессии с наиболее давним использованием. Если Вы знаете, что пользователь завершил работу с сессией, сохраните контейнер, его работу, и объявите его неактивным.

Поддерживайте сессию чистой

Если какие-либо большие элементы в сессии используются только в одной из частей сессии, удалите их сразу же, как только они не будут нужны. Их удаление уменьшит потребление ресурсов репликацией сессии. (Это похоже на применение явного обнуления для того, чтобы помочь сборщику мусора, хотя постоянные читатели знают, что я не рекомендую таких операций, но в данном случае из-за репликации затраты на хранение мусора в сессии настолько велики, что контейнеру необходимо помочь даже таким способом.)


Заключение

Контейнеры сервлетов с помощью репликации HttpSession могут выполнить трудную работу при создании реплицированного доступного Web-приложения. Однако существуют некоторые опции конфигураций для репликации, которые различаются по контейнерам, а выбор способа репликации оказывает положительное влияние на отказоустойчивость, производительность и масштабируемость приложения. Способ репликации не должен выбираться слишком поздно - Вы должны принять решение при создании Вашего Web-приложения. И, конечно, не забывайте проводить тестирование загрузки для определения масштабируемости приложения - до того, как это за Вас сделают пользователи.

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=194627
ArticleTitle=Теория и практика Java: Репликация состояния на уровне Web
publish-date=02082007