Недавно мы провели тест, результаты которого показали, что одно приложение обрабатывает 2000 запросов в секунду на скромном сервере, где это была не единственная нагрузка.
При этом результат каждого запроса записывается в 3-5 таблиц MySQL. Честно говоря, такой результат меня удивил, поэтому я решил поделиться с сообществом Хабра описанием архитектуры этого приложения.
Этот подход применим от баннерных показов до чатов и микроблогов, надеюсь, кому-то он будет интересен.
Во-первых, это приложение однопоточное.
Все делает один процесс, работающий с сокетами - неблокирующий epoll/select, никаких потоков, ожидающих ввода/вывода.
С развитием HTTP, сначала появлением Keep-Alive, затем AJAX и все более популярной COMET, число постоянных подключений к веб-серверу растет, измеряется тысячами и даже десятками тысяч на загруженных проектах, а если для каждого вы создаете свой поток со своим стеком и постоянно переключаетесь между ними — ресурсы сервера закончатся очень быстро.
Второй ключевой момент заключается в том, что один SELECT. WHERE pk in (k1, k2, .
, kN) работает быстрее, чем несколько SELECT. WHERE pk=.
Работая с базой данных большими пакетами, вы можете уменьшить не только количество запросов в секунду, но и общую нагрузку.
Предметная область
XBT-трекер (XBTT) — битторрент-трекер.Прошу воздержаться от темы авторских прав, поскольку торрент официально используется, например, для распространения дистрибутивов Linux и патчей для World of Warcraft. В отличие от ed2k и DC++, в один торрент можно поместить несколько файлов, не упаковывая их в архив, и в любой момент проверить целостность файла, а при необходимости восстановить его, скачав битые куски.
При скачивании клиент периодически связывается с трекером, сообщая статистику трафика и получая адреса других распространителей и загрузчиков.
Чем чаще такие запросы, тем точнее учет трафика (если это закрытый трекер) и тем быстрее новые участники раздачи узнают друг о друге.
XBT Tracker, о котором этот пост, написан на C++ и используется на многих зарубежных трекерах, как открытых, так и закрытых, и даже на парочке российских.
Еще один высокопроизводительный трекер, ОпенТрекер , не поддерживает закрытые трекеры по трафику, поэтому ему не нужно записывать результаты запросов в базу данных, поэтому в этом контексте он менее интересен.
Неблокирующий ввод-вывод
В 90-е годы при работе с сокетами использовалась блокировка ввода-вывода, при вызове методов Recv и send текущий поток «зависал» в ожидании результатов.Для каждого принятого соединения создавался отдельный процесс (форк), в котором обрабатывался его запрос.
Но каждый процесс требует памяти стека и процессорного времени для переключения контекста между процессами.
При небольших нагрузках это не страшно, да и веб тогда не был интерактивным, полностью в режиме запрос-ответ, динамического контекста (CGI) было мало, в основном счетчики посещений страниц и примитивные подфорумы.
Apache до сих пор работает таким образом.
В Apache2 есть возможность использовать более легкие потоки вместо процессов, но суть остается той же.
В качестве альтернативы этому появился неблокирующий ввод-вывод, когда один процесс мог открывать множество сокетов, периодически опрашивать их статус и при появлении каких-либо событий, например, прихода нового соединения или поступления данных на чтение, обслуживать их.
Вот как это работает, например: nginx .
В Java версии 1.4 и выше для этого есть NIO. Позже появились улучшения, например TCP_DEFER_ACCEPT, позволяющий «отложить» принятие соединения до тех пор, пока на него не поступят данные, SO_ACCEPTFILTER, который откладывает соединение до тех пор, пока не поступит полноценный HTTP-запрос.
Появилась возможность увеличить длину очереди непринятых подключений (по умолчанию их всего 128) с помощью sysctl kern.ipc.somaxconn в BSD и sysctl net.core.somaxconn в Linux, что особенно важно при наличии пауз в обработка сокетов.
Запросы на обслуживание
Запросы в XBTT очень просты, их обработка не требует специальных вычислительных ресурсов, все необходимые данные хранятся в памяти, поэтому нет проблем выполнить их в том же процессе, что и работу с сокетами.В случае более серьезных задач все равно придется создавать отдельные потоки для их обслуживания.
Один из выходов — создать пул потоков, в который запрос передается на обработку, после чего поток возвращается обратно в пул.
Если свободных потоков нет, запрос ожидает в очереди.
Такой подход позволяет сократить общее количество используемых потоков, и вам не придется каждый раз создавать новый и убивать его после обработки запроса.
Еще лучший механизм, называемый «акторами», существует в языках Erlang и Scala, возможно, в виде библиотек для других языков.
Обработка осуществляется посредством асинхронной передачи сообщений между участниками, что можно рассматривать как отправку электронных писем на почтовый ящик каждого, но эта тема выходит за рамки этой статьи (например, Здесь новый пост об этом).
Пакетная работа с базой данных
Результат каждого обращения к трекеру XBTT фиксируется в нескольких таблицах.Скачиваемый и выгруженный трафик пользователя увеличивается.
Статистика торрента увеличивается.
Заполнена таблица текущих участников раздачи.
Плюс пара сервисных таблиц с историей загрузок.
При традиционном методе обработки для каждого запроса к трекеру выполнялось бы как минимум 3 отдельных INSERT или UPDATE, клиент ждал бы их выполнения, поэтому серверу базы данных пришлось бы выполнять 3 запроса на каждый запрос к трекеру.
XBTT не выполняет их сразу, а накапливает большую партию INSERT. VALUES (.
), (.
).
.
, (.
) ПРИ ОБНОВЛЕНИИ ДУБЛИКАЦИОННОГО КЛЮЧА f1=VALUES(f1), .
, fN=VALUES(fN) и выполняется раз в несколько секунд. За счет этого количество запросов к базе данных сокращается с нескольких на запрос к трекеру до нескольких в минуту.
Также он периодически перечитывает необходимые данные, которые могли измениться извне (веб-интерфейс независим от трекера).
Критичность отсрочки записи
В этом приложении потеря некоторых данных совершенно не критична.Если статистика торрент-трафика не запишется в базу данных через несколько секунд, ничего страшного не произойдет. Хоть он и записывает накопленные буферы в базу данных при сбое, на сервере может быть ИБП на случай отключения электроэнергии и т. д. — нет никакой гарантии, что все данные, переданные клиентом, будут записаны на диск.
Для баннерной сети это тоже не имеет большого значения, но есть задачи, где сохранение всех данных критично.
Аналогично, не все приложения имеют возможность хранить все данные в памяти.
Обработка запроса клиента может потребовать получения данных из базы данных.
Но даже в этом случае возможна блочная обработка данных.
Конвейер (pipeline; для его реализации идеально подходят акторы) организован из нескольких этапов, на каждом этапе собирается группа данных для запроса, как только собрано достаточное количество (настраиваемое, конечно) или прошло какое-то время (например, 10-100 миллисекунд), в течение которых необходимая сумма не достигается, делается групповой запрос к базе данных, где вместо «ключ=значение» задается условие «ключ IN (накопленный список)».
Если вам нужно заблокировать эти записи, то вы можете добавить в запрос FOR UPDATE SKIP LOCKED (естественно, запись результатов нужно будет делать в том же подключении к базе данных, в той же транзакции).
Вы можете использовать подготовленный оператор в тех базах данных, которые его поддерживают, чтобы один раз разобрать запрос, выбрать оптимальный план его выполнения, а затем каждый раз просто вставлять в него данные.
Чтобы уменьшить количество таких подготовленных запросов, количество параметров к нему можно брать только в степенях двойки: 1, 2, 4, 8, 16, 32. Также можно группировать (пакетно) запросы, сначала без выполнения каждый, а только добавляю их в пакет, а потом делаю все сразу.
Теги: #Оптимизация сервера #highload #scala #erlang #scalability #scalability #high load #comet #actors #actors #actors #c10k #xbtt #xbt tracker
-
Пасхалки Олимпиады 2010 От Яндекса
19 Oct, 24 -
Двойники Русской Википедии
19 Oct, 24 -
Времена! – Деловая Социальная Сеть
19 Oct, 24