Главная > AGP Game Platform, Java - язык и технологии, MMOG, MMORPG игры, Open Source, PHP, Высокопроизводительная архитектура, Разное, Стартапы > AGPsource Session Server — первая бета-версия компонента из состава игровой платформы

AGPsource Session Server — первая бета-версия компонента из состава игровой платформы

3 февраля 2009

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

Предпосылки к созданию

В случае разработки масштабных РНР-приложений и распределённых систем, перед программистами встает задача обеспечения корректной обработки пользовательских запросов, в случае, если приложение работает на нескольких серверах. Обычные РНР-приложения используют механизм сессий для хранения идентификационных данных и сессии задействованы при обработке каждого запроса от клиента. В обычном режиме сессии хранятся в файловой системе и, таким образом, доступны только на том же сервере, где запущено приложение. Если у нас стоит балансировщик нагрузки, он не может, в случае необходимости, перенаправить запросы на другой, свободный в этот момент сервер, так как для корректной обработки нам необходима сессия, а она хранится на том сервере, где пользователь начала работу. Для этого есть даже специальный термин, «Sticky session» - по сути, это стратегия работы балансировщика, который на основе сессионного идентификатора определяет, куда посылать все запросы.

Однако в этом и кроется основная сложность - что делать, если сервер, который начал обслуживать клиента, в середине дела взял и упал? На нем остались сессионные данные, но они локальные, поэтому все запросы клиентов к этому серверу будут некорректны и не могут быть обработаны. Так как обычно в сессии не хранят важных данных, это выльется в необходимость повторной авторизации в приложении, а также, возможно, начало работы с некоторой позиции (если в сессии хранились промежуточные данные работы пользователя, которые еще не попали в базу данных).

Но для многих приложений, для он-лайновых игр в частности, такое поведение не может быть допустимым и мы должны проектировать инфраструктуру таким образом, чтобы избежать перерыва в обслуживании клиента, даже если сервер, обрабатывающий данные, не работает. Для сессий мы выбираем подход под названием «persistent distribution session» - постоянные сесиии, которые хранятся в распределенной системе.

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

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

Как же это работает?

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

Для связи с сервером используется сокет-соединение и простой текстовый протокол поверх TCP, описание протокола дано в документации и примерах.

Серверная часть реализована на основе сетевого фреймворка JBoss Netty, который обеспечивает масштабируемую сетевую инфраструктуру для многопоточного сокет-сервера. Для каждого входящего соединения создается свой pipeline (по сути, конвейер обработки), команды декодируются и обрабатываются. Сервер может одновременно обслуживать несколько тысяч соединений (в зависимости от задержек сети, конфигурации распределенного кеша, использования персистентности, в максимальных настройках около 10 тыс. соединений в секунду).

За кеширование отвечает система кеша EHcache, который вы можете конфигурировать так, как вам необходимо. По умолчанию, кеш создается только на локальной машине, имеет размер в 10 тыс элементов и настроен так, что если в памяти не хватает места, наиболее старые объекты могут вытеснятся на диск (если настроена опция) или удалятся (это будет значить, что сессия устарела, по сути, сервер автоматически реализует Session Garbage Collector, поэтому вы можете отключить команду _gc в клиентской РНР библиотеке, таким образом, немного ускорив работу, избавившись от одного соединения). Также используется дублирование данных на диск, однако, для повышения быстродействия вы можете отключить использование диска (особенно, если на сервере много памяти, мы рекомендуем выделять в среднем 512 МБ - 1Гб на кеш и запускать сервер в собственной JVM). Для ещё большей оптимизации будет доступна отдельная версия сервера, в частности, с измененным алгоритмом работы, отключенной статистикой и т.п.

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

Учитывайте, что текущая реализация имеет ограничение в 16 Кб на размер принимаемых данных, однако в следующих версиях это будет изменено. Максимальный объем данных сессии ограничен 1 Мб, включая размер сериализированных данных + 37 байт служебных заголовков. Исходя из среднего размера сессии вы сможете определить необходимый объем памяти на сервере сессий.

Примечание. В отличие от сервера Memcached, EHcache работает внутри виртуальной машины и объем доступной ему памяти для кеша, ограничен 2 Гб (судя по всему, это как раз ограничение 32-битной виртуальной машины Java), также в сети мы встречали разные обсуждения по возможности работы в 64-битной JVM и работу с большими объемами данных. Существуют и годами работают систему на базе EHCache, где используется 4 - 15 Гб RAM для кеша, однако такая конфигурация требует отдельной настройки и не является для нас дефолтной (требует также изменения логики работы приложения, отключения встроенного GC и т.п., в частности см. здесь). Как альтернатива, мы предлагаем использовать на одном физическом сервере несколько отдельных 32-битных JVM, в которых будет запущен кеш, таким образом образуя некоторый виртуальный кластер кешей в пределах одного физического сервера.

Алгоритм работы

  1. Подключение РНР-библиотеки, где реализована поддержка сессионного сервера
  2. Инициализация и регистрация новых обработчиков
  3. Старт сессии - session_start();
  4. Запрос данных сессии
    • Инициализация сокета, открытие соединения с сервером
    • Проверка данных, формирование команды и отсылка ее на сервер
    • Ожидание и прием ответа от сервера
    • Если сервер вернул данные сессии, они преобразуются (перекодировка модулем mb_string, удаление экранирующих слешей)
    • Возврат механизму сессий в РНР строки с сериализированными данными
    • Данные сессии декодируются РНР и сессия доступна скрипту
    • Если сервер вернул отрицательный ответ (false), стартует новая сессия с пустым массивом данных.
  5. … обычная работа с сессией, через $_SESSION или session_register …
  6. Запись сессии на сервер при завершении работы скрипта или вызов записи сессии вручную.
    • Подготовка команды, проверка идентификатора, перекодировка данных (через mb_string и установка экранирующих слешей)
    • Сокетное соединение уже открыто, поэтому команда сразу отсылается на сервер. Важно! В случае использования сессий в скриптах, которые выполняются длительное время, можно отключить кеширование сокета и открывать его только в момент запроса данных. Это будет реализовано в следующей версии библиотеки.
    • Ожидаем ответа от сервера и завершаем работу. Для большей оптимизации мы можем закрыть соединение сразу после записи данных, не ожидая подтверждения о сохранении, однако это снижает надежность (но увеличивает производительность).

Сервер работает следующим образом:

  1. Инициализация сетевой подсистемы сервера, слушает 10003 порт. Инициализация кеша, получения данных с дискового кеша или синхронизация с другими кешами кластера.
  2. Прием входящего соединения, создание отдельного потока и конвейера для обработки.
  3. Декодирование данных на сетевом уровне.
  4. После сборки всего сообщения создается экземпляр обработчика команд
  5. Лексический анализ сообщения, выделение кода команды и идентификатора сессии.
  6. Вызов обработчика принятой команды или возврат стандартного ответа про ошибку (false)
  7. Обработка команды (получения данных с кеша или запись), формирование ответа или сообщения про статус выполнения (true/false)
  8. Возвращение клиенту ответа (в случае, если соединение ещё открыто, и клиент ожидает ответа).

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

Настройки и оптимизации

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

  • Оптимизация памяти. Если вам критична занимаемая кешем сессий память, то вы можете уменьшить ее потребление за счет того, что часть данных будет расположена на диске, и только самые часто запрашиваемые данные будут в RAM. Для этого стоит изменить конфигурацию по-умолчанию кеша в файле ehcache.xml (детальные инструкции и примеры конфигурации для различных случаев будут позже), также есть возможность (пока не исследованная) использовать gzip компрессию перед сохранением данных. Также можно просто уменьшить максимальное количество хранимых объектов, если у вас не будет столько одновременных сессий, установить меньшее время жизни данных и периодически чистить кеш.
  • Оптимизация сети. Сетевые соединения используются как для получения команд и отправки данных, так и при репликации в случае кластера. Для этого мы рекомендуем использовать сервер, у которого есть несколько физических сетевых интерфейсов, то есть, соединить сервера в кластере максимально возможным по быстродействию (и с минимальной латентностью) сетью. Для увеличения производительности сетевой подсистемы можно увеличить количество потоков в пуле, который обслуживает входящие подключения, а также подобрать размер буфера для приема данных, исходя из максимального объема сессий. Более подробно о настройке механизма репликации кеша читайте в документации
  • Оптимизация числа сетевых соединений. Хотя серверная часть может очень быстро обработать большое количество запросов, но оптимизация может дать существенный выигрыш и уменьшить время ожидания ответа сервера. Для этого надо оптимизировать клиентскую библиотеку на РНР, вынеся инициализацию соединения на самые последние этапы. Исходя из логики работы механизма сессий, соединение с сервером нам надо в двух моментах - при инициализации скрипта для получения данных сессии и в конце для их сохранения. Особенно следует оптимизировать, если у вас сессии используются в скриптах, долго работающих или зависящих от внешних ресурсов, либо сервер загружен, тогда на все время выполнения скрипта библиотека будет держать открытое соединения, а значит и потреблять ресурсы сервера сессий. Можно пожертвовать скорость, произведя два отдельных соединения с сервером - одно для получения данных, другое - для их сохранения. Следующая стадия оптимизации - вычисление и хранение хеша сессии для определения необходимости сохранения данных, ведь если скрипт не изменял данные, нет необходимости их перезаписывать на сервере (но это может потребовать более длительного времени жизни сессии). Далее может быть убрана поддержка управления сборкой мусора (удаления устаревших сессий) путем вызова специального метода механизма сессий РНР - этим может заниматься сервер сессий и его подсистема кеша самостоятельно.
  • Увеличение размера кеша - если вам необходимо хранить больше данных в кеше сессий, более 1.5 - 2 Гб, и на сервере имеется достаточно памяти, можно использовать либо 64-битную JVM, либо развернуть локальный кластер из нескольких экземпляров JVM, настроив репликацию кеша между ними в пределах одного сервера. Но этот метод стоит применять только если кеш в памяти обязателен, лучшим способом будет разделение на дисковый и RAM-кеш, которым будет управлять сам сервер. Кроме этого, если у вас размер кеша более 1 Гб, очень желательно отключить или существенно увеличить период срабатывания системного сборщика мусора JVM, для устранения блокировки кеша на период его очистки.
  • Распределённый кеш - исходя из некоторых исследований быстродействия распределенных систем кеширования, в случае, если в кластере более 2-х узлов, лучшим вариантом будет переход на использование JBoss Cache.
  • Прочее. На быстродействие сервера влияет множество параметров. Если вы не используете статистику, ее можно отключить или ограничить точность - это увеличит скорость сохранения или чтения данных.

Где скачать/посмотреть?

Пока проект имеет статус бета-версии, и мы выложили только код в вики-систему для оценки архитектуры, сам исходный код в виде уже скомпилированного сервера и клиентской библиотеки, а также тесты и, возможно, результаты тестирования и быстродействия, будут в скором времени. Буквально после опубликования обнаружились некоторые моменты, которые мы решили устранить прежде, чем выпустить первую версию, поэтому текущий вариант ТОЛЬКО ДЛЯ ОЗНАКОМЛЕНИЯ.
Developers.org.ua