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

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

Это состояние создается с помощью проверенного объекта Java HttpSession. Этот объект зависает в веб-контейнере, ожидая прихода соответствующего файла cookie, чтобы его можно было привязать к входящему запросу для чтения и записи данных. Этот шаблон отлично подходит при построении системы, потому что он создает удобное место для хранения информации о пользователе и очищает себя, когда пользователя больше нет. Это удобство можно улучшить, поместив его в локальный поток и предоставив общий способ доступа к нему кода за пределами слоев сервлета. Затем можно создавать статические методы, которые могут извлекать и назначать определенные типы объектов пользовательскому сеансу и вуаля! Теперь у вас есть возможность связать мегабайты данных с сеансом, не задумываясь об этом в потенциально сотнях классов. Мы быстро пришли к выводу, что это не обязательно хорошо.

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

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

- Сеанс содержит объект, который нельзя сериализовать, поэтому сеанс не может быть отправлен на другой сервер.
- Сеанс содержит объект, который определен по-разному между двумя серверами, и он не может загружаться на втором сервере.
- Сеанс огромен и часто видоизменяется, поэтому наблюдается заметное влияние на производительность.
- Необходима значительная инфраструктура для поддержки отправки запроса на правильный сервер, когда один из них выходит из строя.

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

Первым шагом на этом новом пути было рассмотрение того, действительно ли мы хотим следовать этому подходу. Мы начали задавать себе следующие вопросы:

  • Хотим ли мы перейти к подходу без сохранения состояния на стороне сервера? Мы все согласились с тем, что хранение объектов в сеансе не является долгосрочным решением, которое мы хотели бы для нашей платформы. Это не означает, что сеанс будет полностью без сохранения состояния, это просто означало, что мы хотели свести к минимуму использование сеанса и не использовать его в качестве свалки данных.
  • Что для нас важнее: простая конфигурация балансировщика нагрузки или простой план репликации сеанса? Некоторым из нас в прошлом приходилось иметь дело с балансировщиками нагрузки, логика которых со временем раздувалась до чудовищных уровней, которые, как известно, было сложно исправить и отладить. Допущение дополнительной сложности репликации сессий никого в команде не беспокоило.
  • На каком пути есть инструменты, которые мы хотим использовать? Большинство облачных сервисов имеют программные балансировщики нагрузки, которые можно относительно легко настроить и настроить. Существует множество вариантов репликации сеансов, встроенных в несколько веб-контейнеров, и дополнительные стратегии репликации с открытым исходным кодом, такие как memcached-session-manager, которые можно настроить без особых усилий.
  • Готовы ли мы выполнить работу, необходимую для того, чтобы программное обеспечение находилось в месте, где не липкие сеансы работали бы хорошо, или это время лучше потратить на другие проекты?

Мы решили, что отправимся в это путешествие, зная, что это не будет чем-то, что мы сможем решить за неделю или даже за месяц. Мы осмотрелись и решили попробовать memcached-session-manager, чтобы мы могли хранить наши сеансы в memcached. Это позволит нам запускать новые экземпляры нашего веб-приложения, не беспокоясь о необходимости настраивать правила межсерверной репликации в среде без многоадресной рассылки. Следующим шагом было определение того, какое поведение нам нужно изменить, чтобы инструмент работал правильно.

  • Минимизация размера сеанса: требуется время, чтобы копировать сеансы туда и обратно между сервером приложений и кластером memcached. Оптимально работающее соединение 1 ГБ добавило бы ~ 10 мс к запросу на 1,25 МБ размера, поэтому меньшие сеансы будут более отзывчивыми.
  • Минимизация мутаций сеанса: у нас был ряд функций в базе кода, которые обновляли сеанс почти при каждом запросе. Это было нормально, когда сеанс был строго локальным, но теперь его нужно было удалить.
  • Обеспечение устойчивости кода к потере обновлений сеанса: соединение с memcached время от времени отключалось по таймауту без видимой причины, хотя фреймворк действительно пытается сохранить информацию во втором узле. Нам нужно было измерить масштаб проблемы потери обновлений из-за тайм-аутов или затирания и убедиться, что мы готовы принять случайную потерю. Платформа предоставляет механизмы блокировки, если это является проблемой для определенных вызовов.

Проблема была определена с набором целей, поэтому теперь мы должны были приступить к конкретной реализации для достижения этих целей.

  • Используйте заполнители вместо объектов. Многие из объектов, которые мы имеем в сеансах, являются объектами модели, на которые можно легко ссылаться с помощью заполнителя с идентификатором. Это позволяет удалить из системы графы больших объектов за счет необходимости выполнять поиск при каждом взаимодействии с объектом и обрабатывать нулевые значения, если объект каким-то образом перестал существовать в середине сеанса. Цена стоила затраченных усилий и сократила размер сессии на пару порядков.
  • Не изменяйте сеансы, если устанавливаемое значение уже является значением сеанса. У нас был код, который вслепую устанавливал значение в сеансе независимо от существующего значения. Мы написали базовую оболочку для сеанса, чтобы обнаружить это и не допустить распространения мутации до системы репликации.
  • Считайте объекты в сеансе неизменяемыми. Мутация объектов, хранящихся в сеансе, без вызова установщика не вызовет репликацию, потому что система репликации не распознает что-то мутировавшее. Мы выследили и удалили любой код, который пытался это сделать.
  • Исправьте хитрые методы, которые привели к мутации сеанса. Время от времени кто-то создает метод, который очень старается дать вам ответ на вопрос. Эти методы имеют тенденцию развиваться таким образом, что их сложность возрастает при привязке к различным частям системы. У нас был один из них, запутавшийся в объекте сеанса, среди других мест, и нам пришлось его реорганизовать, чтобы не использовать сеанс так агрессивно.
  • Сохраняйте возможность доступа к хранилищу сеансов. Если у вас есть система с сотнями обращений к объекту сеанса в запросе, вам будет чрезвычайно сложно добавить эту информацию в сигнатуры методов, чтобы вы могли передать ее вниз по стеку. Мы превратили старый удобный способ использования сеанса в локальный поток, чтобы поток мог передавать информацию вокруг себя без необходимости выполнять серьезный рефакторинг.
  • Сделайте четкое разделение между уровнем сервлета и уровнем приложения. Упомянутый выше локальный поток позволил системе сделать некоторое разделение между входящим запросом и объектом хранилища сеанса. Это важно, если вам нужно повторно использовать код вне веб-приложения и не хотите, чтобы объекты сервлета загрязняли ваш код. Это также сводит к минимуму странные типы мутации и доступа.

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

Сессионные сессии: история человека, некоторого кода и мечта о нелипких веб-сессиях.

Первоначально опубликовано на сайте tech.smartling.com 13 августа 2015 г.