Русские документы
Ежедневные компьютерные новости RSS rusdoc.ru  Найти :
Новости
Последние поступления
Книжный магазин
  Hardware:
Видеоустройства
Системные платы
Процессоры
Мобильные устройства
Аудиосистема
Охлаждение системы
Накопители информации
КПК и ноутбуки
Телефоны и связь
Периферия
Система
Сети
Разные устройства
 
  Programming:
Web-разработка
Языки программирования
Технологии и теория
Разработка игр
Программная инженерия
 
  Software:
Операционные системы
Windows 7
Базы данных
Обзоры программ
Графика и дизайн
   
  Life:
Компьютерная жизнь
Разные материалы
   
Партнеры
Публикация
Правовая информация
Реклама на сайте
Обратная связь
Экспорт в RSS Экспорт в RSS2.0
    Читать в Яндекс.Ленте



Обслуживание тысяч запросов в секунду на примере XBT Tracker

Раздел: Programming / Теория разработки @ 03.03.2009 | Ключевые слова: highload comet actors mysql xbtt xbt tracker оптимизация нагрузка версия для печати

Автор: alex14n
Источник: habrahabr

Недавно проводили тест, результаты которого показали, что одно приложение обрабатывает 2000 запросов в секунду на скромном сервере, где это было не единственной нагрузкой. При этом результат каждого запроса записывается в 3-5 таблиц в MySQL. Честно говоря, меня такой результат удивил, поэтому решил поделиться с хабрасообществом описанием архитектуры этого приложения. Подобный подход применим от баннерных показов до чатов и микроблогов, надеюсь кому-нибудь покажется интересным.

Во-первых, это приложение однопоточное. Всё делается одним процессом, работа с сокетами — неблокирующими epoll/select, никаких ожидающих ввода/вывода потоков (threads). С развитием HTTP, сначала появлением Keep-Alive, затем AJAX и набирающим популярность COMET, количество постоянных соединений с веб-сервером растёт, на нагруженных проектах измеряется тысячами и даже десятками тысяч, и если для каждого создавать свой поток (thread) со своим стеком и постоянно переключаться между ними — ресурсов сервера очень быстро не хватит.

Второй ключевой момент — что один SELECT… WHERE pk in (k1, k2, ..., kN) выполняется быстрее, чем несколько SELECT… WHERE pk=… Выполняя работу с базой данных большими пачками можно уменьшить не только число запросов в секунду, но и общую нагрузку.

Предметная область


XBT Tracker (XBTT) — битторрент трекер. Просьба воздержаться от темы авторских прав, ибо торрент официально используется, например, для распространения дистрибутивов линукса и патчей к World of Warcraft. В отличии от ed2k и DC++ есть возможность поместить в один торрент несколько файлов, не пакуя их в архив, и в любой момент проверить целостность файла, а при необходимости восстановить его скачав битые куски.

При скачивании клиент периодически обращается к трекеру, сообщая статистику трафика и получая адреса других раздающих и качающих. Чем чаще такие обращения, тем точнее учёт трафика (если это закрытый трекер) и тем быстрее новые участники раздачи узнают друг о друге.

XBT Tracker, о котором этот пост, написан на си++ и используется на многих зарубежных трекерах, как открытых, так и закрытых, и даже на паре-тройке российских. Другой высокопроизводительный трекер, OpenTracker, закрытых трекеров с учётом трафика не поддерживает, поэтому ему не нужно писать результаты запросов в базу данных, поэтому в данном контексте он менее интересен.

Неблокирующий ввод-вывод


В 90-х годах при работе с сокетами использовался блокирующий ввод-вывод, когда при вызове методов recv и send текущий поток «зависал» до ожидания результатов. Для каждого принятого соединения создавался (fork) отдельный процесс, в котором шла обработка его запроса. Но каждый процесс требует памяти под стэк и процессорного времени на переключение контекста между процессами. На небольших нагрузках это не страшно, да и веб тогда не был интерактивным, полностью в режиме запрос-ответ, динамического контекста (CGI) было мало, в основном — счётчики посещений страницы и примитивные недо-форумы. Apache до сих пор работает таким образом. В apache2 есть возможность использование более лёгких потоков (threads) вместо процессов, но суть осталась прежней.

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

В дальнейшем появились улучшения, например, TCP_DEFER_ACCEPT, позволяющее «откладывать» приём соединения до тех пор, пока по нему не пришли данные, SO_ACCEPTFILTER, откладывающий соединение пока не пришёл полноценный HTTP-запрос. Появилась возможность увеличить длину очереди непринятых соединений (по умолчанию их всего 128) при помощи sysctl kern.ipc.somaxconn в BSD и sysctl net.core.somaxconn в Linux, что особенно актуально если возникают паузы в обработке сокетов.

Обслуживание запросов


Запросы в XBTT очень простые, их обработка не требует особых вычислительных ресурсов, все необходимые данных он держит в памяти, поэтому нет проблем выполнять их в том же процессе, что и работу с сокетами. В случае более серьёзных задач по-прежнему требуется создавать отдельные потоки для их обслуживания.

Один из выходов — создание пула потоков (thread pool), которым передаётся запрос для обработки, после чего поток возвращается обратно в пул. Если свободных потоков нет — запрос ждёт в очереди. Такой подход позволяет уменьшить общее число используемых потоков, и каждый раз не приходиться создавать новый и убивать после завершения обработки запроса.

Ещё лучший механизм под названием «актёры» (actors) есть в языках эрланг (erlang) и скала (scala), возможно — в виде библиотек — и для других языков. Обработка выполняется посредством асинхронной передачи сообщений между актёрами, что можно себе представить как пересылку е-мейлов с входящим почтовым ящиком у каждого, но эта тема выходит за рамки данного поста (например, вот свежий пост об этом).

Пакетная работа с базой данных


Результат каждого обращения к трекеру XBTT записывает в несколько таблиц. У пользователя увеличивается его скачанный и залитый трафик. Увеличивается статистика у торрента. Заполняется таблица текущих участников раздачи. Плюс пара служебных таблиц с историей закачек.

При традиционном способе обработки на каждый запрос к трекеру выполнялось бы как минимум 3 отдельных INSERT или UPDATE, клиент ждал бы их исполнения, таким образом серверу базы данных пришлось бы выполнять по 3 запроса на каждое обращение к трекеру.

XBTT выполняет их не сразу, а накапливает большую пачку INSERT… VALUES (...), (...). ..., (...) ON DUPLICATE KEY UPDATE f1=VALUES(f1), ..., fN=VALUES(fN), и исполняет раз в несколько секунд. За счёт чего число запросов в базу уменьшается с нескольких на запрос к трекеру до нескольких в минуту. Также периодически он перечитывает необходимые данные, которые могли измениться извне (веб-интерфейс независим от трекера).

Критичность откладывания записи


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

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

Но и в таком случае блочная обработка данных возможна. Организуется конвейер (pipeline; для его реализации отлично подойдут actors) из нескольких стадий, на каждой происходит сборка группы данных для запроса, как только набралось достаточное количество (естественно, настраиваемое) или прошло некоторое время (например, 10-100 миллисекунд), за которое нужного количества не набралось, — делается групповой запрос к базе, где вместо «ключ=значение» ставится условие «ключ IN (накопленный список)».

Если необходимо заблокировать (lock) эти записи, то к запросу можно добавить FOR UPDATE SKIP LOCKED (естественно, запись результатов нужно будет выполнять в том же соединении к базе, той же транзакции). Можно использовать Prepared Statement в тех базах данных, которые это поддерживают, чтобы один раз выполнить анализ запроса (parse), выбрать оптимальный план его выполнения, а потом каждый раз только подставлять в него данные. Чтобы уменьшить количество таких подготовленных запросов, число параметров к нему можно брать только степенями двойки: 1, 2, 4, 8, 16, 32… Также можно группировать (batch) запросы, сначала не исполняя каждый, а только добавляя в пачку, а потом выполнить все сразу.

Это интересно:








версия для печатиРаспечатать статью


Вернуться в раздел: Programming / Теория разработки


Реклама:
Читать наc на:

Add to Google
Читать в Яндекс.Ленте






Rambler's Top100
© Copyright 1998-2012 Александр Томов. All rights reserved.