Пример построения высоконагруженной системы на основе открытого решения
Что предстоит сделать?
На основе открытого решения Openx нам предстоит реализовать масштабируемую систему управления рекламой, которая бы выдерживала нагрузку порядка 20М показов баннеров в сутки, что равнозначно более 40М запросов к web-серверу.
Для того, чтобы обеспечить масштабируемость и надежность системы, мы будем использовать несколько серверов под разные нужды.
В моем случае было использовано 5 серверов для создания http-кластера и 2 сервера для БД.
В статье будет показано, как с помощью минимальных усилий добиться максимального результата, с помощью каких инструментов находить узкие места, что нужно изменить для достижения поставленной цели.
Но, чтобы понять, как и на какие рычаги нам предстоит нажать, сначала необходимо разобраться, как устроенна система, выявить в ней слабые места и способы борьбы с ними, чтобы получить приемлемый результат.
Как это работает?
Изучив внутренности Openx, я задумался, почему все настолько сложно написано, ведь аналогичный функционал можно было реализовать гораздо проще. Хотя, наверное, такова судьба всех открытых проектов. Система управления рекламой состоит из двух частей:
Административной части (backoffice) – отвечает за управление рекламными материалами
Системы доставки рекламы (delivery engine) – отвечает за выборку нужного баннера, а также за ведение статистики.
Административная чать в оптимизации не нуждается, пока не нуждается ;-)), так как 99% нагрузки приходится на систему доставки рекламы.
Система доставки устроена так, что при показе одного баннера к серверу обращается минимум 3 запроса.
Первый запрос делается баннерным кодом для того, чтобы узнать, какой именно баннер показывать в зависимости от входящих параметров:
идентификатора рекламной зоны
IP-адреса
URL страницы и т.д.
Второй запрос, сформированный баннерным кодом, нужен для сбора данных (показы, клики).
Третий запрос к web-серверу - это запрос на получение самого баннера.
Вызов баннеров происходит с помощью JavaScript-кода, поскольку это наиболее безопасный способ доставки рекламы, при котором рекламная система на 100% отделена от основного ресурса и никак не может повлиять на его работоспособность.
Возникающие проблемы
На первый взгляд все прекрасно - установи и пользуйся, но в жизни все оказывается гораздо сложнее. Проведя ряд тестов, была выявлена следующая проблема - при достижении нагрузки в 150-300 запросов в секунду система начинала сильно тормозить, вследствие чего нагрузка на сервера увеличивалась и это могло привести к неспособности серверов отвечать на поступающие запросы и полной остановке системы управления рекламой.
Для того, чтобы было понятнее, с чем мы имеем дело, нам нужно также ознакомиться с архитектурой системы и понять, где могут быть узкие места.
Для распределения нагрузки между backend серверами (3) используется машина с установленным на нее бесплатным httpd-сервером Nginx, который выполняет роль балансировщика нагрузки (2) и реверсивного прокси-сервера.
Машины, на которых стоит nginx, настроены для обслуживания очень большого количества соединений.
Cистема строилась год назад, сейчас я бы рекомендовал использовать nginx+PHPFPM+xcache.
Три таких сервера обеспечивают более 6 000 000 показов рекламы в сутки. В критической ситуации один сервер может быть выведен из кластера без остановки предоставления сервиса в полном объеме.
Сервер базы данных
Типичная конфигурация сервера БД:
CPU Athlon 64x2
RAM 4Gb
HDD 2x750 Gb SATA 100 RAID 0
OS FeeBSD
MySQL:5.1
Два сервера используются только для увеличения надежности системы, зеркалирование данных осуществляется при помощи репликации.
Профайлинг - поиск узких мест
Обработка поступающих в систему запросов
Входящие запросы от пользователей (1) делятся на два типа:
Запросы на отдачу статических файлов: jpg, gif, swf
Для того, чтобы понять, как работает любая система вцелом, рекомендуется делать мониторинг системы - какой-то период времени собирать данные, а потом их анализировать.
Со временем по графикам вы научитесь определять, когда ваша система работает нормально, а когда у вас начинаются проблемы.
Для web-ресурсов нужно производить мониторинг:
http-сервера - для nginx это модуль stub_status, для Apache это mod_status
RAM - использование памяти (свободная память, память, выделенная приложению, и т.д.)
CPU - load average
HDD - загрузка диска, дисковые операции (особенно актуально для серверов баз данных)
Числовые данные сами по себе практически не несут никакой смысловой нагрузки - здесь важна динамика и изменение этих данных во времени.
Например, можно спрогнозировать, когда нам понадобится еще один сервер, обрабатывающий запросы, либо новый жесткий диск.
Вот графики, котрые были получены при мониторинге работы нашей системы
Что означают эти данные?
Reading - сколько соединений находится в состоянии чтения.
Writing - сколько соединений находится в состоянии записи.
Waiting - keep-alive соединения или же в состоянии обработки запроса.
На мой взгляд, смысл данных показателей заключается в следующем:
Writing - насколько хорошо клиент (application server) отдает данные проксирующему серверу. Если график начинает резко расти вверх, обычно это означает замедление работы сервера, который обслуживает поступающие запросы, либо сервер очень долго обрабатывает поступающие запросы.
Reading - насколько хорошо и быстро клиент (пользователь), пославший запрос, читает данные, которые ему отдает балансировщик. Если график с этим показателем резко растет вверх, это означает, что у нас возникают проблемы с каналом (канал слишком узкий). Обычно этот показатель сильно возрастает при DDOS-атаках.
Теперь посмотрим, что мы видим на графиках:
до 10:00 система работала нормально после 10:00 резко начинает расти writing где-то в 11:40 резко возрастает количество запросов (от 180 запросов в секунду до 260 запросов в секунду), application сервера напряглись, но выдержали пиковую нагрузку
После этого нагрузка начинает плавно увеличиваться, а вместе с ней начинает "тормозить" рекламный кластер.
В 15:45 нагрузка на рекламу максимальная, application сервера перегружены и уже почти не способны обрабатывать новые поступающие запросы - состояние "жуткий тормоз".
После этого нагрузка была снята и работа серверов вошла в штатный режим, но при увеличении нагрузки до более 200 запросов в секунду опять начинались жуткие проблемы.
Вот такой неутешительный диагноз был поставлен нами, используя данные системы мониторинга.
Ищем бутылочное горлышко
В своей работе раздатчик у нас использует два файла
ajs.php - формирует JavaScript-код баннера
lg.php - пишет лог запросов (показы, клики)
После профайлинга этих двух файов выяснилось, что php-код хоть и достаточно громоздкий, но выполняется доволно быстро.
Хороший прирост быстройдействия достигается использованием акселератора, а основным "тормозом" является работа с MySQL.
Самая болшая проблема Openx на больших нагрузках - это соединение и запись статистических данных в БД MySQL.
При нагрузке в 2 000 000 показов в сутки количество запросов к MySQL-серверу будет примерно 90 000 000. При этом стоит учесть, что сама операция записи данных в таблицу, а также соединение с сервером БД требует довольно много ресурсов.
Если при каких-то условиях БД не была доступна, то на серверах резко возрастало количество процессов Apache httpd и резко повышалась нагрузка на сервера, что могло привести к полной остановке системы раздачи рекламы.
Избавляемся от MySQL при логировании запросов
Как говорится, все гениальное - просто! Вместо того, чтобы писать данные в базу, мы будем писать данные в лог-файлы, которые периодически (например раз в 5 минут) будут вставляться в базу.
Этим действием решаем сразу две проблемы:
ускоряем работу наших application серверов;
разгружаем MySQL-сервер (ему теперь не нужно делать единичные вставки, вставки делаются большими пачками).
Процесс записи лог-файлов возложим на наш http-сервер, что даст нам максимальное быстродействие для выполнения этой операции.
Для того, чтобы реализовать такой функционал, надо подкорректировать файл
\lib\OA\Dal\Delivery\mysql.php (~690 строка) функция OA_Dal_Delivery_logAction
Удаляем там все, что связанно с SQL-запросом, и добавляем такой код:
Для ротирования логов используем стандартный Apache rotatelogs.
После ротации файлы можно брать и использовать.
Для того, чтобы автоматизировать процесс вставки лог-файлов, пишем простейший консольный скрипт, который будет проверять наличие доступных лог-файлов и импортировать данные в базу.
Запрос для импортирования будет иметь приблизительно такой вид
$sql="LOAD DATA LOCAL INFILE '".LOG_DIR.$file."' INTO TABLE ".$table." FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'";
Помимо этого, если в системе используется более одного backend сервера, нужно организовать мониторинг поступления лог-файлов с разных серверов.
Для этого создадим таблицу в базе и при каждом импорте файлов в БД будем обновлять в ней данные о том, с какого сервера и когда была произведена вставка лог-файлов.
Запрос на обновление данных в таблице
$sql="REPLACE INTO oa_parsers SET server='".$hostname."', update_time='".gmdate('U')."'";
Теперь по этим данным можно настроить систему мониторинга – Nagios, написав для нее нужный плагин.
Настройка MySQL
Для быстрой вставки и хранения логов используйте таблицы типа MyISAM.
Храните сырую статистику столько времени, сколько вам нужно, причем это надо продумать заранее, потому что удаление небольшой порции данных из очень большой таблицы может длиться очень долго (при наличии 180M записей в таблице удаление данных за неделю происходило более 72 часов).
Если вы планируете хранить много данных (объем таблицы более 4Gb), внесите изменения в структуру таблиц (выделено цветом)
При использовании обратного проксирования очень важно, чтобы приложение правильно определяло IP-адрес клиента. Для этого мы настроим nginx так, чтобы реальный IP-адрес передавался с помощью переменной HTTP_X_FORWARDED_FOR.
Для этого вносим в конфигурационный файл nginx следующие настройки
Далее модифицируем функцию MAX_Geo_GeoIP_getInfo, вставив в нее кусок, выделенный жирным шрифтом.
function MAX_Geo_GeoIP_getInfo()
{
$conf=$GLOBALS['_MAX']['CONF'];
if(isset($GLOBALS['_MAX']['GEO_IP'])){
$ip=$GLOBALS['_MAX']['GEO_IP'];
}elseif(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
$ip= Get_realIP();
}else{
$ip=$_SERVER['REMOTE_ADDR'];
}
Это все изменения, которые мы внесли в исходный код продукта.
Теперь осталось проверить, что же у нас получилось.
Подводим итоги
Итак, что мы получили в результате, покажет наша система мониторинга
День первый
Увеличение нагрузки (макс. 500 запросов в секунду) почти в три раза от того, что есть на исходном графике, - система работает стабильно.
День второй
780 запросов в секунду - все работает стабильно.
День третий
800 запросов в секунду - стабильная работа.
При такой нагрузке application сервера обрабатывают где-то 120-160 запросов в секунду, в сутки доставляется 13М баннеров.
Минимальное модифицирование исходных кодов базового продукта дало возможность получить довольно хорошую масштабируемую и отказоустойчивую систему, которая способна выдерживать большие нагрузки.
При увеличении нагрузки на систему необходимо просто добавить application сервер.
За год эксплуатации такой системы было показанно более 2,5 миллиарда баннеров и сделано более 15 миллионов кликов.
Никогда раньше не встречал подобной затеи, но выглядит очень красиво.