Модель с применением множества доверенных удостоверений сервиса.

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

Обычно о масштабировании начинают думать тогда, когда один
сервер не справляется с возложенной на него работой. С чем именно он не
справляется? Работа любого web-сервера по большому счету сводится к основному
занятию компьютеров - обработке данных. Ответ на HTTP (или любой другой) запрос
подразумевает проведение некоторых операций над некими данными. Соответственно,
у нас есть две основные сущности - это данные (характеризуемые своим объемом) и
вычисления (характеризуемые сложностью). Сервер может не справляться со своей
работой по причине большого объема данных (они могут физически не помещаться на
сервере), либо по причине большой вычислительной нагрузки. Речь здесь идет,
конечно, о суммарной нагрузке - сложность обработки одного запроса может быть
невелика, но большое их количество может «завалить» сервер.

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

Типичная архитектура сайта

Жизнь типичного сайта начинается с очень простой архитектуры
- это один web-сервер (обычно в его роли выступает Apache),
который занимается всей работой по обслуживанию HTTP-запросов,
поступающих от посетителей. Он отдает клиентам так называемую «статику», то
есть файлы, лежащие на диске сервера и не требующие обработки: картинки (gif,
jpg, png), листы стилей (css), клиентские скрипты (js, swf). Тот же сервер
отвечает на запросы, требующие вычислений - обычно это формирование
html-страниц, хотя иногда «на лету» создаются и изображения и другие документы.
Чаще всего ответы на такие запросы формируются скриптами, написанными на php,
perl или других языках.

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

Решение этой проблемы - распределение работы по обработке
запросов между двумя разными программами - т.е. разделение на frontend и
backend. Легкий frontend-сервер выполняет задачи по отдаче статики, а остальные
запросы перенаправляет (проксирует) на backend, где выполняется формирование
страниц. Ожидание медленных клиентов также берет на себя frontend, и если он использует
мультиплексирование (когда один процесс обслуживает нескольких клиентов - так
работают, например, nginx или lighttpd), то ожидание практически ничего не
стоит.

Из других компонент сайта следует отметить базу данных, в
которой обычно хранятся основные данные системы - тут наиболее популярны
бесплатные СУБД MySQL и PostgreSQL. Часто отдельно выделяется хранилище
бинарных файлов, где содержатся картинки (например, иллюстрации к статьям
сайта, аватары и фотографии пользователей) или другие файлы.

Таким образом, мы получили схему архитектуры, состоящую из
нескольких компонент.

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

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

Распределение вычислений

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

Важно, чтобы все backend-серверы были способны правильно
отвечать на запросы. Обычно для этого необходимо, чтобы каждый из них работал с
одним и тем же актуальным набором данных. Если мы храним всю информацию в единой
базе данных, то СУБД сама обеспечит совместный доступ и согласованность данных.
Если же некоторые данные хранятся локально на сервере (например, php-сессии
клиента), то стоит подумать о переносе их в общее хранилище, либо о более
сложном алгоритме распределения запросов.

Распределить по нескольким серверам можно не только работу
скриптов, но и вычисления, производимые базой данных. Если СУБД выполняет много
сложных запросов, занимая процессорное время сервера, можно создать несколько
копий базы данных на разных серверах. При этом возникает вопрос синхронизации
данных при изменениях, и здесь применимы несколько подходов.

  • Синхронизация на уровне приложения . В этом случае наши
    скрипты самостоятельно записывают изменения на все копии базы данных (и сами несут
    ответственность за правильность данных). Это не лучший вариант, поскольку он
    требует осторожности при реализации и весьма неустойчив к ошибкам.
  • Репликация - то есть автоматическое тиражирование
    изменений, сделанных на одном сервере, на все остальные сервера. Обычно при
    использовании репликации изменения записываются всегда на один и тот же сервер - его называют master, а остальные копии - slave. В большинстве СУБД есть
    встроенные или внешние средства для организации репликации. Различают
    синхронную репликацию - в этом случае запрос на изменение данных будет ожидать,
    пока данные будут скопированы на все сервера, и лишь потом завершится успешно - и асинхронную - в этом случае изменения копируются на slave-сервера с
    задержкой, зато запрос на запись завершается быстрее.
  • Multi-master репликация. Этот подход аналогичен
    предыдущему, однако тут мы можем производить изменение данных, обращаясь не к
    одному определенному серверу, а к любой копии базы. При этом изменения
    синхронно или асинхронно попадут на другие копии. Иногда такую схему называют
    термином «кластер базы данных».

Возможны разные варианты распределения системы по серверам.
Например, у нас может быть один сервер базы данных и несколько backend (весьма
типичная схема), или наоборот - один backend и несколько БД. А если мы масштабируем
и backend-сервера, и базу данных, то можно объединить backend и копию базы на
одной машине. В любом случае, как только у нас появляется несколько экземпляров
какого-либо сервера, возникает вопрос, как правильно распределить между ними
нагрузку.

Методы балансировки

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

  • Балансирующий узел . В этом случае клиент шлет запрос на один
    фиксированный, известный ему сервер, а тот уже перенаправляет запрос на один из
    рабочих серверов. Типичный пример - сайт с одним frontend и несколькими
    backend-серверами, на которые проксируются запросы. Однако «клиент» может
    находиться и внутри нашей системы - например, скрипт может слать запрос к
    прокси-серверу базы данных, который передаст запрос одному из серверов СУБД.
    Сам балансирующий узел может работать как на отдельном сервере, так и на одном
    из рабочих серверов.

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

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


Разумеется, существуют и комбинации этих подходов. Например,
такой известный способ распределения нагрузки, как DNS-балансировка, основан на
том, что при определении IP-адреса сайта клиенту выдается
адрес одного из нескольких одинаковых серверов. Таким образом, DNS выступает в
роли балансирующего узла, от которого клиент получает «распределение». Однако
сама структура DNS-серверов предполагает отсутствие точки отказа за счет
дублирования - то есть сочетаются достоинства двух подходов. Конечно, у такого
способа балансировки есть и минусы - например, такую систему сложно динамически
перестраивать.

Работа с сайтом обычно не ограничивается одним запросом.
Поэтому при проектировании важно понять, могут ли последовательные запросы
клиента быть корректно обработаны разными серверами, или клиент должен быть
привязан к одному серверу на время работы с сайтом. Это особенно важно, если на
сайте сохраняется временная информация о сессии работы пользователя (в этом
случае тоже возможно свободное распределение - однако тогда необходимо хранить
сессии в общем для всех серверов хранилище). «Привязать» посетителя к
конкретному серверу можно по его IP-адресу (который, однако, может меняться),
или по cookie (в которую заранее записан идентификатор сервера), или даже
просто перенаправив его на нужный домен.

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

Распределение данных

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

  • Вертикальное распределение (vertical partitioning) - в простейшем случае
    представляет собой вынесение отдельных таблиц базы данных на другой сервер. При
    этом нам потребуется изменить скрипты, чтобы обращаться к разным серверам за
    разными данными. В пределе мы можем хранить каждую таблицу на отдельном сервере
    (хотя на практике это вряд ли будет выгодно). Очевидно, что при таком
    распределении мы теряем возможность делать SQL-запросы, объединяющие данные из
    двух таблиц, находящихся на разных серверах. При необходимости можно реализовать
    логику объединения в приложении, но это будет не столь эффективно, как в СУБД.
    Поэтому при разбиении базы данных нужно проанализировать связи между таблицами,
    чтобы разносить максимально независимые таблицы.

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

  • Горизонтальное распределение (horizontal partitioning) - заключается в
    распределении данных одной таблицы по нескольким серверам. Фактически, на
    каждом сервере создается таблица такой же структуры, и в ней хранится
    определенная порция данных. Распределять данные по серверам можно по разным
    критериям: по диапазону (записи с id < 100000 идут на сервер А, остальные - на сервер Б), по списку значений (записи типа «ЗАО» и «ОАО» сохраняем на сервер
    А, остальные - на сервер Б) или по значению хэш-функции от некоторого поля
    записи. Горизонтальное разбиение данных позволяет хранить неограниченное
    количество записей, однако усложняет выборку. Наиболее эффективно можно выбирать
    записи только когда известно, на каком сервере они хранятся.

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

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

  • Работающие на уровне операционной системы . При этом для
    приложения работа с файлами в такой системе не отличается от обычной работы с
    файлами. Обмен информацией между серверами берет на себя операционная система.
    В качестве примеров таких файловых систем можно привести давно известное
    семейство NFS или менее известную, но более современную систему Lustre.
  • Реализованные на уровне приложения распределенные
    хранилища подразумевают, что работу по обмену информацией производит само
    приложение. Обычно функции работы с хранилищем для удобства вынесены в
    отдельную библиотеку. Один из ярких примеров такого хранилища - MogileFS, разработанная
    создателями LiveJournal. Другой распространенный пример - использование
    протокола WebDAV и поддерживающего его хранилища.

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

Выводы

Подводя итог сказанному, сформулируем выводы в виде кратких тезисов.

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

Ссылки

Продолжить изучение этой темы можно на интересных англоязычных сайтах и блогах.

) Здравствуйте! Я Александр Макаров, и вы можете меня знать по фреймворку «Yii» — я один из его разработчиков. У меня также есть full-time работа — и это уже не стартап — Stay.com, который занимается путешествиями.

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

Что такое масштабирование, вообще? Это возможность увеличить производительность проекта за минимальное время путем добавления ресурсов.

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

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

Самый классный вопрос, который задают, — а зачем оно надо, если у меня все и на одном сервере прекрасно работает? На самом-то деле, надо проверить, что будет. Т.е., сейчас оно работает, но что будет потом? Есть две замечательные утилиты — ab и siege, которые как бы нагоняют тучу пользователей конкурента, которые начинают долбить сервер, пытаются запросить странички, послать какие-то запросы. Вы должны указать, что им делать, а утилиты формируют такие вот отчеты:

Главные два параметра: n — количество запросов, которые надо сделать, с — количество одновременных запросов. Таким образом они проверяют конкурентность.

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

Есть еще один параметр — Response time — время ответа, за которое в среднем сервер отдал страничку. Оно бывает разное, но известно, что около 300 мс — это норма, а что выше — уже не очень хорошо, потому что эти 300 мс отрабатывает сервер, к этому прибавляются еще 300-600 мс, которые отрабатывает клиент, т.е. пока все загрузится — стили, картинки и остальное — тоже проходит время.

Бывает, что на самом деле пока и не надо заботиться о масштабировании — идем на сервер, обновляем PHP, получаем 40% прироста производительности и все круто. Далее настраиваем Opcache, тюним его. Opcache, кстати, тюнится так же, как и APC, скриптом, который можно найти в репозитории у Расмуса Лердорфа и который показывает хиты и мисы, где хиты — это сколько раз PHP пошел в кэш, а мисы — сколько раз он пошел в файловую систему доставать файлики. Если прогнать весь сайт, либо запустить туда какой-то краулер по ссылкам, либо вручную потыкать, то у нас будет статистика по этим хитам и мисам. Если хитов 100%, а мисов — 0%, значит, все нормально, а если есть мисы, то надо выделить больше памяти, чтобы весь наш код влез в Opcache. Это частая ошибка, которую допускают — вроде Opcache есть, но что-то не работает…

Еще часто начинают масштабировать, но не смотрят, вообще, из-за чего все работает медленно. Чаще всего лезем в базу, смотрим — индексов нет, ставим индексы — все сразу залетало, еще на 2 года хватит, красота!

Ну, еще надо включить кэш, заменить apache на nginx и php-fpm, чтобы сэкономить память. Будет все классно.

Все перечисленное достаточно просто и дает вам время. Время на то, что когда-то этого станет мало, и к этому уже сейчас надо готовиться.

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

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

Что может показать мониторинг? Мы можем упереться в диск, т.е. в файловую систему, в память, в процессор, в сеть… И может быть такое, что, вроде бы, все более-менее, но какие-то ошибки валятся. Все это разрешается по-разному. Можно проблему, допустим, с диском решить добавлением нового диска в тот же сервер, а можно поставить второй сервер, который будет заниматься только файлами.

На что нужно обращать внимание прямо сейчас при мониторинге? Это:

  1. доступность, т.е. жив сервер, вообще, или нет;
  2. нехватка ресурсов диска, процессора и т.д.;
  3. ошибки.
Как это все мониторить?

Вот список замечательных инструментов, которые позволяют мониторить ресурсы и показывать результаты в очень удобном виде:

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

Старьё! - скажите вы.
- Вечные ценности! - ответим мы.

  • highload junior
  • Добавить метки

    Возможность масштабирования информационной системы – как горизонтальное, так и вертикальное – является одним из самых важных факторов, на которые стоит обращать при выборе средства автоматизации деятельности любой организации. Если выбранное решение невозможно будет масштабировать, или каждая стадия роста бизнеса будет приводить к сложностям с сопровождением и развитием такого программного продукта, то не следует даже начинать его использовать. Мы разрабатывали СЭД ЛЕТОГРАФ с учетом высоких требований к масштабированию.

    Необходимость в горизонтальном или вертикальном масштабировании возникает в связи с созданием корпоративных высоконагруженных ИТ-систем, в которых работают тысячи или даже десятки тысяч пользователей. Однако поддерживать одновременную работу большого числа пользователей могут далеко не все СЭД. Только если в СЭД на уровне архитектуры заложены возможности по наращиванию количества пользователей без потери производительности – только в этом случае масштабирование будет успешным. Созданная нами система ЛЕТОГРАФ была разработана таким образом, чтобы идеально масштабироваться как горизонтально, так и вертикально. Это достигается как за счет архитектуры самой системы и того прикладного кода, который мы разработали, так и за счет функционала СУБД InterSystems Caché, на которой наша СЭД построена.

    СУБД Caché – это современная система управления базами данных и среда для быстрой разработки приложений. В основе этой СУБД лежит технология, которая обеспечивает быстродействие и высокую производительность, масштабируемость и надежность. При этом аппаратные требования системы остаются довольно скромными.

    СУБД Caché сохраняет высокую производительность даже при работе с огромными массивами данных и большим числом серверов в распределенных системах. При этом доступ к данным осуществляется через объекты, высокопроизводительные SQL-запросы и путем прямой обработки многомерных структур данных.

    Вертикальное масштабирование

    Вертикальное масштабирование предполагает наращивание мощности сервера и его возможностей, связанных с дисковой подсистемой. ЛЕТОГРАФ поддерживает современную процессорную архитектуру, что позволяет обрабатывать большие объемы данных в несколько потоков. При этом сами данные в СЭД организованы таким образом, чтобы их можно было легко разносить по СХД на разные диски. Такой подход позволяет равномерно распределить нагрузку на хранилища данных и минимизировать ее при чтении данных непосредственно из базы, а значит и падения производительности системы удастся избежать даже при одновременной работе большого количества пользователей.

    Еще на этапе разработки платформы мы понимали, что вертикальное масштабирование – одна из ключевых возможностей системы, потребность в которой со временем будет только увеличиваться. Мы разработали систему таким образом, чтобы процессы работы каждого пользователя были выделены в отдельные системные процессы, которые между собой не пересекаются благодаря тому, что базы данных эффективно делят доступ к информации. При этом количество блокировок данных в СЭД ЛЕТОГРАФ минимизировано и нет «узкого горла» ни при чтении данных, ни при их записи.

    Архитектура СЭД ЛЕТОГРАФ позволяет распределять данные на несколько физических или виртуальных серверов. Благодаря такому распределению каждый из пользователей работает в изолированном процессе, а требуемые данные эффективно кэшируются с использованием технологий СУБД Caché. Время блокировки данных минимизировано: все транзакции выстроены таким образом, чтобы переводить данные в эксклюзивный режим доступа лишь на очень короткое время. При этом даже такие высоконагруженные с точки зрения количества обращений к диску данные, как журналы, индексы, данные объектов, потоки, логи действий пользователей, распределены таким образом, что средняя нагрузка на подсистему остается равномерной и не приводит к задержкам. Такой подход позволяет эффективно вертикально масштабировать систему, распределяя нагрузку между серверами или виртуальными дисками.

    Горизонтальное масштабирование

    Горизонтальное масштабирование – это распределение сессий пользователей по разным серверам (равномерная загрузка серверов приложений и возможность подключать дополнительные сервера приложений), а также распределение данных по разным серверам БД, что обеспечивает высокую производительность системы, при этом не приводя к снижению отказоустойчивости. Для горизонтального масштабирования в системе ЛЕТОГРАФ предусмотрен целый ряд возможностей.

    Прежде всего, это масштабирование нагрузки благодаря Enterprise Cache Protocol (ECP, протокол распределенного кэша), протоколу, используемому в СУБД InterSystems Caché. Преимущество ECP заключается в инновационном подходе к кэшированию данных. В рамках данного протокола пользовательские процессы, которые работают на серверах приложений (или ECP-клиентах) СУБД и обслуживают запросы, получают доступ к локальному кэшу недавно использованных данных. И только если этих данных недостаточно, ECP-клиент обращается к базе данных. С помощью протокола ECP выполняется автоматическое управление кэшем: наиболее часто используемые данные сохраняются в кэше, часто обновляемые данные периодически реплицируются, обеспечивая постоянное целостность и корректность данных на всех ECP-клиентах. При этом внутренний алгоритм InterSystems Caché предполагает, что базы данных синхронизируются между ECP-клиентом и ECP-сервером.

    Фактически использование технологий СУБД Caché позволяет легко и быстро масштабировать нагрузку по серверам приложений, обеспечив таким образом подключение большого числа пользователей к одному серверу базы данных благодаря использованию ECP-протокола.

    Так как информация, которую затребовал тот или иной пользователь, может быть задействована на нескольких ECP-клиентах, необходимо блокировать данные на короткий период времени, быстро выполнять транзакции, не выполняя внутренних вычислений. И мы успешно это реализовали. Данная технология позволяет нам эффективно масштабировать систему в ситуации, когда используются один сервер базы данных и несколько серверов, на которых работают пользовательские процессы. Технологическая особенность СУБД Caché заключается в том, что она поддерживает корректность транзакций в рамках одного ECP-сервера вне зависимости от количества ECP-клиентов, которые к ней подключены. В случае, когда у нас один ECP-сервер и множество ECP-клиентов, эта задача великолепно решается, потому что все транзакции идут на одном сервере базы данных.

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

    В СЭД ЛЕТОГРАФ реализован механизм шардинга, благодаря которому мы на уровне настроек системы (без применения программирования), даем возможность описать правила и принципы разнесения самих данных по разным серверам БД. Несмотря на то, что с точки зрения структуры баз данных информация, хранящаяся на каждом сервере одинакова, сама информация отличается принципиально в зависимости от организации или каких-либо других признаков, которые являются значимыми для конкретной задачи. Используя технологию шардинга можно добиться, что в 95-99 % случаев пользователи будут работать только со своей «порцией данных», и не потребуется в рамках сессии обращаться к разным серверам БД.

    На возможности масштабирования СЭД ЛЕТОГРАФ влияет и то, данные могут по разному обрабатываться. Например, в документы (даже созданные несколько лет назад) могут вноситься изменения, а в журнал действий пользователей записи только добавляются (ни одна запись не может быть ни удалена, ни изменена). Механизмы, которые используются в СЭД ЛЕТОГРАФ, позволяют дополнительно повысить производительность системы и улучшить масштабирование за счет ведения таких журналов на отдельных серверах БД – причем, как в случае односерверной, так и многосерверной конфигурации. Такой подход ориентирован на снижение нагрузки на основные сервера БД.

    Аналогичная ситуация возникает и контентом (“информационным содержанием” СЭД). Так как система ЛЕТОГРАФ работает с большим объемом контента – это терабайты данных, миллионы файлов и документов – разумно предположить, что контент, который попадает в систему, ни при каких условиях не должен пострадать. Поэтому мы также выносим хранение файлов на отдельные сервера баз данных и обеспечиваем таким образом дополнительно горизонтальное масштабирование.

    Программное обеспечение фронт-энда

    В качестве фронт-энда в СЭД ЛЕТОГРАФ используются Apache и HAProxy. HAProxy отвечает за балансировку нагрузки между веб-серверами Apache. HAProxy, как показал опыт работы системы, зарекомендовал себя как наиболее эффективное решение, способное обеспечить поддержку работы большого числа пользователей и необходимый контроль за отказоустойчивостью.

    Когда пользователь открывает браузер и подключается к системе, HAProxy «распределяет» его на один из серверов приложений. Дальше все запросы, которые поступают от этого пользователя, будут отправляться на тот же сервер приложений в тот же процесс.

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

    Пример реализации проекта

    Архитектура ЛЕТОГРАФ позволяет добиться существенных результатов в сокращении времени отклика и повышении производительности системы. В рамках одного из наших проектов в СЭД хранится 23,5 Тбайт данных. Из них 14,7 Тбайт (63%) приходится на потоки (“прикрепленные к карточкам файлы”), 3,5 Тбайт (15%) – на отчетные формы, такие как таблицы отчетов, которые формируются в асинхронном режиме, могут запускаться как по расписанию, так и по требованию пользователя и представляют собой сводную таблицу, любые данные в которой можно детализировать до объекта. Еще 1,6 Тбайт (7%) – это протокол пользовательских операций, а все остальное (16%) – данные карточек и индексы.

    В данной системе работает более 11 тыс. пользователей, 2 тыс. из них работают одновременно, а в дни пиковой нагрузки число одновременно работающих в СЭД сотрудников превышает 3 тыс. Количество записей в журнале уже превысило 5,5 млрд, а учетных карточек – почти достигло полумиллиарда.

    В качестве сервера базы данных в данном проекте установлен отказоустойчивый кластер из двух физических серверов с тремя инсталляциями СУБД, а также резервный сервер. Десять серверов приложений (и один резервный) обрабатывают пользовательские сессии и обеспечивают формирование асинхронных отчетов. 2 сервера HAProxy выполняют функции балансировщиков. В случае проблем с одним из серверов, выполняется автоматическая передача его IP-адреса на другой сервер. Также предусмотрены сервер индексации файлов и сервер распознавания (обеспечивающий распознавание текста отсканированных бумажных документов при размещении электронных образов в систему).

    Резюме

    В СЭД ЛЕТОГРАФ предусмотрено большое количество разнообразных механизмов масштабирования. Мы предлагаем своеобразный пирог, в основе которого лежит сервер (физический или виртуальный), на который устанавливается операционная система. Поверх нее стоит СУБД InterSystems Caché, внутри которой располагается код платформы. А уже над ним – настройки системы ЛЕТОГРАФ, благодаря которым СЭД полностью конфигурируется. И такой пирог размещен на каждом сервере. Сервера между собой связаны определенным образом за счет выбранных конфигураций. И последний слой – это HAProxy, распределяющий между серверами запросы пользователей. Такая архитектура позволяет нам поддерживать масштабирование и обеспечивать все необходимые механизмы мониторинга. В результате конечные пользователи получают быстро работающую СЭД, а ИТ-специалисты – простую в управлении и обслуживании, унифицированную систему, без огромного числа составляющих, которые в случае высоконагруженных приложений приходится постоянно контролировать и администрировать. Кроме того, в зависимости от изменения потребностей организации СЭД ЛЕТОГРАФ легко переконфигурировать, добавив новые серверы или дисковые возможности.


    Данный материал является частной записью члена сообщества Club.CNews.
    Редакция CNews не несет ответственности за его содержание.

    Александр Макаров - разработчик популярного фреймворка Yii расскажет про масштабирование веб-проектов.

    Масштабирование - способность наращивать систему для обработки большего количества трафика, не теряя при этом пользовательские качества: скорость и отзывчивость.

    Масштабирование различают двух типов: вертикальное (больше памяти, диска, лучше процессор) и горизонтальное (больше серверов в кластере).

    • Зачем оно нужно, если и так всё работает?
    • Когда? Мониторинг, необдуманные решения, оптимизация и жизнь с одним сервером.
    • Типичная схема.
    • Балансировка нагрузки.
    • Какие, вообще, проблемы на стороне приложения?
    • Почему PHP так хорош для масштабирования.
    • Сессии.
    • База данных.
    • Файлы.
    • Как быть со статистикой?

    Александр Макаров (Yii, Stay.com)

    Здравствуйте! Я Александр Макаров, и вы можете меня знать по фреймворку «Yii» — я один из его разработчиков. У меня также есть full-time работа — и это уже не стартап — Stay.com, который занимается путешествиями.

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

    Что такое масштабирование, вообще? Это возможность увеличить производительность проекта за минимальное время путем добавления ресурсов.

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

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

    Самый классный вопрос, который задают, — а зачем оно надо, если у меня все и на одном сервере прекрасно работает? На самом-то деле, надо проверить, что будет. Т.е., сейчас оно работает, но что будет потом? Есть две замечательные утилиты — ab и siege, которые как бы нагоняют тучу пользователей конкурента, которые начинают долбить сервер, пытаются запросить странички, послать какие-то запросы. Вы должны указать, что им делать, а утилиты формируют такие вот отчеты:

    Главные два параметра: n — количество запросов, которые надо сделать, с — количество одновременных запросов. Таким образом они проверяют конкурентность.

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

    Есть еще один параметр — Response time — время ответа, за которое в среднем сервер отдал страничку. Оно бывает разное, но известно, что около 300 мс — это норма, а что выше — уже не очень хорошо, потому что эти 300 мс отрабатывает сервер, к этому прибавляются еще 300-600 мс, которые отрабатывает клиент, т.е. пока все загрузится — стили, картинки и остальное — тоже проходит время.

    Бывает, что на самом деле пока и не надо заботиться о масштабировании — идем на сервер, обновляем PHP, получаем 40% прироста производительности и все круто. Далее настраиваем Opcache, тюним его. Opcache, кстати, тюнится так же, как и APC, скриптом, который можно найти в репозитории у Расмуса Лердорфа и который показывает хиты и мисы, где хиты — это сколько раз PHP пошел в кэш, а мисы — сколько раз он пошел в файловую систему доставать файлики. Если прогнать весь сайт, либо запустить туда какой-то краулер по ссылкам, либо вручную потыкать, то у нас будет статистика по этим хитам и мисам. Если хитов 100%, а мисов — 0%, значит, все нормально, а если есть мисы, то надо выделить больше памяти, чтобы весь наш код влез в Opcache. Это частая ошибка, которую допускают — вроде Opcache есть, но что-то не работает...

    Еще часто начинают масштабировать, но не смотрят, вообще, из-за чего все работает медленно. Чаще всего лезем в базу, смотрим — индексов нет, ставим индексы — все сразу залетало, еще на 2 года хватит, красота!

    Ну, еще надо включить кэш, заменить apache на nginx и php-fpm, чтобы сэкономить память. Будет все классно.

    Все перечисленное достаточно просто и дает вам время. Время на то, что когда-то этого станет мало, и к этому уже сейчас надо готовиться.

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

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

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

    На что нужно обращать внимание прямо сейчас при мониторинге? Это:

    1. доступность, т.е. жив сервер, вообще, или нет;
    2. нехватка ресурсов диска, процессора и т.д.;
    3. ошибки.

    Как это все мониторить?

    Вот список замечательных инструментов, которые позволяют мониторить ресурсы и показывать результаты в очень удобном виде:

    • Monit — http://mmonit.com/monit/
    • Zabbix — http://www.zabbix.com/
    • Munin — http://munin-monitoring.org/
    • Nagios — http://www.nagios.org/
    • ServerDensity — https://www.serverdensity.com/

    Первые 4 инструмента можно поставить на сервер, они мощные, классные. А ServerDensity хостится у кого-то, т.е. мы за нее платим деньги, и она может собирать с серверов все данные и отображать их для анализа.

    Для мониторинга ошибок есть два хороших сервиса:

    • Rollbar — https://rollbar.com/
    • Sentry — https://getsentry.com/

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

    Sentry можно поставить к себе на сервер, есть исходник, а Rollbar — нет, но Rollbar лучше, потому что он берет деньги за количество ошибок, т.е. стимулирует их исправлять.

    Про нотификации повторю, что спамить не стоит, теряется внимание.

    Что, вообще, надо анализировать?

    RPS и Responce time — если у нас начинает время ответа падать, то надо что-то делать.

    Количество процессов, потоков и размеры очередей — если это все начинает плодиться, забиваться и т.д., то что-то здесь опять не так, надо анализировать более детально и как-то менять инфраструктуру.

    Также стоит смотреть на бизнес-анализ. Google Analytics для сайтовых типов отлично подходит, а mixpanel — для логирования ивентов, он работает на десктопных приложениях, на мобильных, на веб. Можно и на основе каких-то своих данных писать, но я бы советовал готовые сервисы. Смысл в том, что наш мониторинг может показывать, что сервис жив, что все работает, что общий Responce time нормальный, но когда мы, допустим, регистрацию в mixpanel"е начинаем трекать, он показывает, что их как-то маловато. В этом случае надо смотреть, насколько быстро отрабатывают определенные ивенты, страницы, и в чем состоят проблемы. Проект всегда должен быть "обвешан" анализом, чтобы всегда знать, что происходит, а не работать вслепую.

    Нагрузка, вообще, возникает или запланировано, или нет, может возникать постепенно, может не постепенно:

    Как бороться с нагрузкой? Решает все бизнес, и важна только цена вопроса. Важно:

    1. чтобы сервис работал,
    2. чтобы это было не сильно дорого, не разорило компанию.

    Остальное не очень важно.

    Если дешевле попрофайлить, оптимизировать, записать в кэш, поправить какие-то конфиги, то это и надо делать, не задумываясь пока о масштабировании и о том, чтобы докупать "железо" и т.д. Но бывает, что "железо" становится дешевле, чем работа программиста, особенно, если программисты очень подкованные. В этом случае уже начинается масштабирование.

    На рисунке синяя штука — это Интернет, из которого идут запросы. Ставится балансировщик, единственная задача которого — распределить запросы на отдельные фронтенды-сервера, принять от них ответы и отдать клиенту. Смысл тут в том, что 3 сервера могут обработать (в идеале) в 3 раза больше запросов, исключая какие-то накладные расходы на сеть и на саму работу балансировщика.

    Что это нам дает? Указанную выше возможность обработать больше запросов, а еще надежность. Если в традиционной схеме валится nginx или приложение, или в диск уперлись и т.п., то все встало. Здесь же, если у нас один фронтенд отвалился, то ничего страшного, балансировщик, скорее всего, это поймет и отправит запросы на оставшиеся 2 сервера. Может, будет чуть помедленнее, но это не страшно.

    Вообще, PHP — штука отличная для масштабирования, потому что он следует принципу Share nothing по умолчанию. Это означает, что если мы возьмем, допустим, Java для веба, то там приложение запускается, читает весь код, записывает по максимуму данных в память программы, все там крутится, работает, на request уходит очень мало времени, очень мало дополнительных ресурсов. Однако есть засада — т.к. приложение написано так, что оно должно на одном инстансе работать, кэшироваться, читать из своей же памяти, то ничего хорошего у нас при масштабировании не получится. А в PHP по умолчанию ничего общего нет, и это хорошо. Все, что мы хотим сделать общим, мы это помещаем в memcaсhed, а memcaсhed можно читать с нескольких серверов, поэтому все замечательно. Т.е. достигается слабая связанность для слоя серверов приложений. Это прекрасно.

    Чем, вообще, балансировать нагрузку?

    Чаще всего это делали Squid"ом или HAProxy, но это раньше. Сейчас же автор nginx взял и партировал из nginx+ балансировщик в nginx, так что теперь он может делать все то, что раньше делали Squid"ом или HAProxy. Если оно начинает не выдерживать, можно поставить какой-нибудь крутой дорогой аппаратный балансировщик.

    Проблемы, которые решает балансировщик — это как выбрать сервер и как хранить сессии? Вторая проблема — чисто PHP"шная, а сервер может выбираться либо по очереди из списка, либо по географии каких-то IP"шников, либо по какой-то статистике (nginx поддерживает least-connected, т.е. к какому серверу меньше коннектов, на него он и будет перекидывать). Можем написать для балансировщика какой-то код, который будет выбирать, как ему работать.

    Что, если мы упремся в балансировщик?

    Есть такая штука как DNS Round robin — это замечательный трюк, который позволяет нам не тратиться на аппаратный балансировщик. Что мы делаем? Берем DNS-сервер (обычно DNS-сервера у себя никто не хостит, это дорого, несильно надежно, если он выйдет из строя, то ничего хорошего не получится, все пользуются какими-то компаниями), в А-записи прописываем не один сервер, а несколько. Это будут А-записи разных балансировщиков. Когда браузер туда идет (гарантий, на самом деле, нет, но все современные браузеры так действуют), он выбирает по очереди какой-нибудь IP-адрес из А-записей и попадает либо на один балансировщик, либо на второй. Нагрузка, конечно, может размазываться не равномерно, но, по крайней мере, она размазывается, и балансировщик может выдержать немного больше.

    Что делать с сессиями?

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

    Возникает очевидное желание сделать общую файловую систему, подключить NFS. Но делать так не надо — она до жути медленная.

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

    Можно писать в memcached, но очень-очень осторожно, потому что memcached — это, все-таки, кэш и он имеет свойство вытираться, как только у него мало ресурсов, или некуда писать новые ключи — тогда он начинает терять старые без предупреждения, сессии начинают теряться. За этим надо либо следить, либо выбрать тот же Redis.

    Redis — нормальное решение. Смысл в том, что Redis у нас на отдельном сервере, и все наши фронтенды ломятся туда и начинают с Redis"а свои сессии считывать. Но Redis однопоточный и рано или поздно можем хорошенько упереться. Тогда делают sticky-сессии. Ставится тот же nginx и сообщается ему, что нужно сделать сессии так, чтобы юзер, когда он пришел на сервер и ему выдалась сессионная cookie, чтобы он впоследствии попадал только на этот сервер. Чаще всего это делают по IP-хэшу. Получается, что если Redis на каждом инстансе, соответственно, сессии там свои, и пропускная способность чтения-записи будет гораздо лучше.

    Как насчет cookies? Можно писать в cookies, никаких хранилищ не будет, все хорошо, но, во-первых, у нас все еще куда-то надо девать данные о сессии, а если мы начнем писать в cookies, она может разрастись и не влезть в хранилище, а, во-вторых, можно хранить в cookies только ID, и нам все равно придется обращаться к БД за какими-то сессионными данными. В принципе, это нормально, решает проблему.

    Есть классная штука — прокси для memcached и Redis:

    Они, вроде как, поддерживают распараллеливание из коробки, но делается это, я не сказал бы, что очень оптимально. А вот эта штука — twemproxy — она работает примерно как nginx с PHP, т.е. как только ответ получен, он сразу отправляет данные и в фоне закрывает соединение, получается быстрее, меньше ресурсов потребляет. Очень хорошая штука.

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

    В PHP есть такой механизм как session handler, т.е. мы можем поставить свой handler и писать в cookies, в БД, в Redis — куда угодно, и все это будет работать со стандартными session start и т.д.

    Сессии надо закрывать вот этим замечательным методом.

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

    Что делать с файлами?

    С ними можно справляться двумя способами:

    1. какое-то специализированное решение, которое дает абстракцию, и мы работаем с файлами как с файловой системой. Это что-то вроде NFS, но NFS не надо.
    2. "шардирование" средствами PHP.

    Специализированные решения из того, что действительно работает, — это GlusterFS. Это то, что можно поставить себе. Оно работает, оно быстрое, дает тот же интерфейс, что NFS, только работает с нормальной терпимой скоростью.

    И Amazon S3 — это, если вы в облаке Amazon"а, — тоже хорошая файловая система.

    Если вы реализуете со стороны PHP, есть замечательная библиотека Flysystem, покрытая отличными тестами, ее можно использовать для работы со всякими файловыми системами, что очень удобно. Если вы сразу напишете всю работу с файлами с этой библиотекой, то потом перенести с локальной файловой системы на Amazon S3 или др. будет просто — в конфиге строчку переписать.

    Как это работает? Пользователь из браузера загружает файл, тот может попадать либо на фронтенд и оттуда расползаться по файловым серверам, либо на каждом файловом сервере делается скрипт для аплоада и файл заливается сразу в файловую систему. Ну и, параллельно в базу пишется, какой файл на каком сервере лежит, и мы читать его можем непосредственно оттуда.

    Лучше всего раздавать файлы nginx"ом или Varnish"ем, но лучше все делать nginx"ом, т.к. мы все его любим и используем — он справится, он хороший.

    Что у нас происходит с базой данных?

    Если у вас все уперлось в код PHP, мы делаем кучу фронтендов и все еще обращаемся к одной БД — она справится достаточно долгое время. Если нагрузка не страшная, то БД живет хорошо. Например, мы делали JOIN"ы по 160 млн. строк в таблице, и все было замечательно, все бегало хорошо, но там, правда, оперативки надо больше выделить на буферы, на кэш...

    Что делать с БД, если мы уперлись в нее? Есть такие техники как репликация. Обычно делается репликация мастер-слэйв, есть репликация мастер-мастер. Можно делать репликацию вручную, можно делать шардирование и можно делать партицирование.

    Что такое мастер-слэйв?

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

    В случае типичного проекта большое количество слэйвов позволяет разгрузить как мастер, так и, вообще, увеличить скорость чтения.

    Также это дает отказоустойчивость — если упал один из слэйвов, то делать ничего не надо. Если упал мастер, мы можем достаточно быстро сделать один из слэйвов мастером. Правда, это обычно не делается автоматически, это внештатная ситуация, но возможность есть.

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

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

    Есть такая штука как read/write split.Делается 2 пула серверов — мастер, слэйв, соединение по требованию, и логика выбора соединения варьируется. Смысл в том, что если мы будем всегда читать со слэйвов, а писать всегда в мастер, то будет небольшая засада:

    т.е. репликация выполняется не немедленно, и нет гарантий, что она выполнилась, когда мы делаем какой-либо запрос.

    Есть два типа выборок:

    1. для чтения или для вывода;
    2. для записи, т.е., когда мы что-то выбрали и потом это что-то надо изменить и записать обратно.

    Если выборка для записи, то мы можем либо всегда читать с мастера и писать на мастер, либо мы можем выполнить "SHOW SLAVE STATUS" и посмотреть там Seconds_Behind_Master (для PostgreSQL тоже супер-запрос есть на картинке) — он покажет нам число. Если это 0 (нуль), значит, все у нас уже реплицировалось, можно смело читать со слэйва. Если число больше нуля, то надо смотреть значение — либо нам стоит подождать немного и тогда прочитать со слэйва, либо сразу читать с мастера. Если у нас NULL, значит еще не реплицировали, что-то застряло, и надо смотреть логи.

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

    Что такое мастер-мастер?

    Это ситуация, когда стоит несколько серверов, и везде и пишется, и читается. Плюс в том, что оно может быть быстрее, оно отказоустойчивое. В принципе, все то же, что и у слэйвов, но логика, вообще, простая — мы просто выбираем рандомное соединение и с ним работаем. Минусы: лаг репликации выше, есть шанс получить какие-то неконсистентные данные, и, если произошла какая-нибудь поломка, то она начинает раскидываться по всем мастерам, и никому уже неизвестно, какой мастер нормальный, какой поломался... Это все дело начинает реплицироваться по кругу, т.е. очень неслабо забивает сеть. Вообще, если пришлось делать мастер-мастер, надо 100 раз подумать. Скорее всего, можно обойтись мастер-слэйвом.

    Можно делать репликацию всегда руками, т.е. организовать пару соединений и писать сразу в 2, в 3, либо что-то делать в фоне.

    Что такое шардирование?

    Фактически это размазывание данных по нескольким серверам. Шардировать можно отдельные таблицы. Берем, допустим, таблицу фото, таблицу юзеров и др., растаскиваем их на отдельные сервера. Если таблицы были большие, то все становится меньше, памяти ест меньше, все хорошо, только нельзя JOIN"ить и приходится делать запросы типа WHERE IN, т.е. сначала выбираем кучу ID"шников, потом все эти ID"шники подставляем запросу, но уже к другому коннекту, к другому серверу.

    Можно шардировать часть одних и тех же данных, т.е., например, мы берем и делаем несколько БД с юзерами.

    Можно достаточно просто выбрать сервер — остаток от деления на количество серверов. Альтернатива — завести карту, т.е. для каждой записи держать в каком-нибудь Redis"е или т.п. ключ значения, т.е. где какая запись лежит.

    Есть вариант проще:

    Сложнее — это когда не удается сгруппировать данные. Надо знать ID данных, чтобы их достать. Никаких JOIN, ORDER и т.д. Фактически мы сводим наш MySQL или PostgreSQL к key-valuе хранилищу, потому что мы с ними ничего делать не можем.

    Обычные задачи становятся необычными:

    • Выбрать TOP 10.
    • Постраничная разбивка.
    • Выбрать с наименьшей стоимостью.
    • Выбрать посты юзера X.

    Если мы зашардировали так, что все разлетелось по всем серверам, это уже начинает решаться очень нетривиально. В этой ситуации возникает вопрос — а зачем нам, вообще SQL? Не писать ли нам в Redis сразу? А правильно ли мы выбрали хранилище?

    Из коробки шардинг поддерживается такими штуками как:

    • memcache;
    • Redis;
    • Cassandra (но она, говорят, с какого-то момента не справляется и начинает падать).

    Как быть со статистикой?

    Часто статистику любят считать с основного сервера — с единственного сервера БД. Это прекрасно, но запросы в статистике обычно жуткие, многостраничные и т.д., поэтому считать статистику по основным данным — это большая ошибка. Для статистики в большинстве случаев realtime не нужен, так что мы можем настроить мастер-слэйв репликацию и на слэйве эту статистику уже посчитать. Или мы можем взять что-нибудь готовое — Mixpanel, Google Analytics или подобное.

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

    Можно сразу взять Gearman. Это готовая штука для обработки в фоне. Есть под PHP библиотеки, драйвера... А можно использовать очереди, т.е. ActiveMQ, RabbitMQ, но очереди пересылают только сообщения, сами обработчики они не вызывают, не выполняют, и тогда придется что-то придумывать.

    Общий смысл всегда один — есть основное ПО, которое помещает в очереди какие-то данные (обычно это "что сделать?" и данные для этого), и какой-то сервис – он либо достает, либо ему прилетают (если очередь умеет активно себя вести) эти данные, он все обрабатывает в фоне.

    Перейдем к архитектуре.

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

    Связанность бывает в коде. SOLID, GRASP — это принципы, которые позволяют избежать связанности именно в коде. Но связанность в коде на разнос по серверам, конечно, влияет, но не настолько, насколько связанность доменного слоя с нашим окружением. Если мы в контроллере пишем много-много кода, получается, что в другом месте мы это использовать, скорее всего, не сможем. Нам непросто будет все это переносить из веб-контроллера в консоль и, соответственно, сложнее переносить на другие сервера и там обрабатывать по-другому.

    Service-oriented architecture.

    Есть 2 подхода разбиения систем на части:

      когда бьют на технические части, т.е., например, есть очередь, вынесли сервис очередей, есть обработка изображений, вынесли и этот сервис и т.д.

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

      правильное решение — бить на отдельные логические части, т.е. если в областях Sales и Customer используется модель user, то мы создаем 2 модели user. Они могут читать одни и те же данные, но представляют они их немного по-разному. Если разбить систему таким образом, то все гораздо лучше воспринимается и намного проще все это раскидать.

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

    Что с доменным слоем?

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

    Есть 2 книги про доменный слой, которые всем советую:

    • "Domain-Driven Design: Tackling Complexity in the Heart of Software" от Eric Evans,
    • "Implementing Domain-Driven Design, Implementing Domain-Driven Design".
    • про BoundedContext — http://martinfowler.com/bliki/BoundedContext.html (то, о чем было выше — если у вас две области вроде как пересекаются, но они разные, то стоит некоторые сущности продублировать, такие как модель user);
    • про DDD в общем — — ссылка еще на одну книгу.

    В архитектуре, опять же, стоит придерживаться принципа share nothing, т.е. если вы хотите что-то сделать общим, делайте это всегда сознательно. Логику предпочтительно закидывать на сторону приложения, но и в этом стоит знать меру. Никогда не стоит, допустим, делать хранимые процедуры в СУБД, потому что масштабировать это очень тяжело. Если это перенести на сторону приложения, то становится проще — сделаем несколько серверов и все будет выполняться там.

    Не стоит недооценивать браузерную оптимизацию. Как я уже говорил, из тех 300-600 мс, которые запросы выполняются на сервере, к ним прибавляется 300-600 мс, которые тратятся на клиенте. Клиенту все равно, сервер ли у нас быстрый, или это сайт так быстро отработал, поэтому советую использовать Google PageSpeed и т.д.

    Как обычно, абстракция и дробление совсем не бесплатны. Если мы раздробим сервис на много микросервисов, то мы больше не сможем работать с новичками и придется много-много платить нашей команде, которая будет во всем этом рыться, все слои перебирать, кроме этого сервис может начать медленнее работать. Если в компилируемых языках это не страшно, то в PHP, по крайней мере, до версии 7, это не очень...

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

    Еще немного ссылок полезных:

    Итак вы сделали сайт. Всегда интересно и волнительно наблюдать как счетчик посещений медленно, но верно ползет вверх, с каждым днем показывая все лучшие результаты. Но однажды, когда вы этого не ждете, кто-то запостит ссылку на ваш ресурс на каком-нибудь Reddit или Hacker News (или на Хабре - прим. пер.), и ваш сервер ляжет.

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

    Немного про оптимизацию

    Основные советы всем известны: обновитесь до последней версии PHP (в 5.5 теперь встроен OpCache), разберитесь с индексами в базе данных, кэшируйте статику (редко изменяемые страницы, такие как “О нас”, “FAQ” и т.д.).

    Также стоит упомянуть об одном особом аспекте оптимизации - обслуживании статического контента не-Apache сервером, таким как, например, Nginx, Настройте Nginx на обработку всего статического контента (*.jpg, *.png, *.mp4, *.html…), а файлы требующие серверной обработки пусть отсылает тяжелому Apache. Это называется reverse proxy .

    Масштабирование

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

    Вертикальное масштабирование.

    Представьте себе сервер, обслуживающий веб-приложение. У него 4ГБ RAM, i5 процессор и 1ТБ HDD. Он отлично выполняет свои функции, но, что бы лучше справляться с более высоким трафиком, вы решаете увеличить RAM до 16ГБ, поставить процессор i7, и раскошелиться на SSD диск. Теперь сервер гораздо мощнее, и справляется с высокими нагрузками. Это и есть вертикальное масштабирование.

    Горизонтальное масштабирование.

    Горизонтальное масштабирование - создание кластера из связанных между собой (часто не очень мощных) серверов, которые вместе обслуживают сайт. В этом случае, используется балансировщик нагрузки (aka load balancer ) - машина или программа, основная функция которой - определить на какой сервер послать запрос. Сервера в кластере делят между собой обслуживание приложения, ничего друг о друге не зная, таким образом значительно увеличивая пропускную способность и отказоустойчивость вашего сайта.

    Есть два типа балансировщиков - аппаратные и программные. Программный - устанавливается на обычный сервер и принимает весь трафик, передавая его соответствующим обработчикам. Таким балансировщиком может быть, например, Nginx. В разделе “Оптимизация” он перехватывал все запросы на статические файлы, и обслуживал эти запросы сам, не обременяя Apache. Другое популярное ПО для реализации балансировки нагрузки - Squid . Лично я всегда использую именно его, т.к. он предоставляет отличный дружественный интерфейс, для контроля за самыми глубокими аспектами балансировки.

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

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

    Постоянное соединение

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

    Обмен локальными данными.

    Разделить данные сессии пользователей между всеми нодами кластера - кажется неплохой идеей. И несмотря на то, что этот подход требует некоторых изменений в архитектуре вашего приложения, оно того стоит - разгружается балансировщик, и весь кластер становится более отказоустойчивым. Смерть одного из серверов совершенно не отражается на работе всей системы.
    Как мы знаем, данные сессии хранятся в суперглобальном массиве $_SESSION , который пишет и берет данные с файла на диске. Если этот диск находится на одном сервере, очевидно, что другие сервера не имеют к нему доступа. Как же нам сделать его доступным на нескольких машинах?
    Во первых, обратите внимание, что обработчик сессий в PHP можно переопределить . Вы можете реализовать свой собственный класс для работы с сессиями .

    Использование БД для хранения сессий

    Используя собственный обработчик сессий, мы можем хранить их в БД. База данных может быть на отдельном сервере (или даже кластере). Обычно этот метод отлично работает, но при действительно большом трафике, БД становится узким местом (а при потере БД мы полностью теряем работоспособность), ибо ей приходится обслуживать все сервера, каждый из которых пытается записать или прочитать данные сессии.

    Распределенная файловая система

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

    Memcached

    Вы также можете использовать memcached для хранения данных сессий в RAM. Однако это не безопасно, ибо данные в memcached перезаписываются, если заканчивается свободное место. Вы, наверное, задаетесь вопросом, разве RAM не разделен по машинам? Как он применяется на весь кластер? Memcached имеет возможность объединять доступную на разных машинах RAM в один пул .

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

    Для использования этого подхода, нужно немного подредактировать php.ini

    Session.save_handler = memcache session.save_path = "tcp://path.to.memcached.server:port"

    Redis кластер

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

    Другие решения

    Итого

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

    Надеюсь этот небольшой гайд поможет вам выбрать подход к масштабированию для вашего проекта.

    Во второй части статьи мы поговорим о масштабировании базы данных .