Добрый день, дорогой читатель! Продолжая цикл статей о реализации стека RTB нашей компанией, предлагаю вам ознакомиться с реализацией нашей SSP — VOX Ad Exchange.
Нагрузка здесь не такая большая, как, например, в нашей DSP, а основное время обработки запроса тратится на ожидание ответов от подключенных DSP. Написана она в виде асинхронного ASP.NET MVC-приложения.
Итак, давайте для начала разберёмся, как же происходит обработка запросов.
Запрос на получение рекламы начинает формироваться ещё на стороне клиента. Здесь посредством javascript мы стараемся собрать как можно больше информации о посетителе сайта, на котором установлен наш скрипт. В основном это техническая информация: включен ли в браузере flash player, размеры окна браузера, размеры экрана, получаем адрес страницы и реферер на страницу, с которой пришёл пользователь и прочее. Далее информация собирается в JSON строку, кодируется и отправляется запрос на получение рекламы. Теперь за дело берётся back-end.
Получив запрос на показ рекламы, так же получаем из базы всю известную нам информацию о данном посетителе (историю его интересов, просмотров рекламы и кликов). По user-agent'у мы получаем данные об используемом браузере, его версии и операционной системе посетителя, а по ip-адресу определяем его текущее местоположение. Всю собранную о пользователе информацию мы отправляем в нашу DMP для её дальнейшего анализа.
Собрав данные о пользователе, мы переходим к площадке. Получаем информацию о стоимости рекламного места, CTR площадки, категориях контента, наличию запрещённого контента и т.д., на основе этих данных будет сформирована минимальная пороговая цена аукциона. Закончив с площадками, переходим к самой длительной задаче SSP, это опрос подключенных DSP. На основе полученных выше описанным способом данных формируется запрос к DSP. Далее происходит паралельный опрос DSP, на ответ каждой из них отводиться 120 ms, в которые входит и время на доставку запроса, и получение ответа (ping до серверов, на которых расположена DSP). Так что месторасположение DSP тоже играет важную роль. Получив ответ от всех DSP, остаётся лишь отсортировать их по размеру ставки и взять ответ с самой большой. Ответ в виде JSON'а, для показа рекламы мы возвращаемся на front-end.
Фронтенду остаётся самая малость — вывести код полученного объявления. Так как уследить за качеством кода DSP и его совместимостью с площадками нет никакой возможности, наш код для вывода объявления на страницу создаёт отдельный iframe, загружает в него код, и вот его-то мы и выводим на страницу. На этом и заканчивается обработка запроса на рекламу.
Давайте теперь разберёмся с какими трудностями пришлось столкнуться в ходе реализации SSP, и как мы с ними справились. Одной из основных проблем в ходе реализации SSP стало забивание пула IIS. Причиной этому было ожидание ответа от DSP в течении 100 и более миллисекунд. В то время, когда SSP ждала ответа от DSP, остальные запросы ждали, пока SSP обработает текущий. А решением данной проблемы стало использование асинхронных контроллеров, вот здесь нам и пригодились модификаторы async/await. Также не лишним будет упомянуть, что информация о площадке и DSP кешируется в памяти приложения и ежеминутно обновляется. Следующим основным местом, на которое стоит обратить внимание, является опрос DSP. Первоначально встал вопрос, как распараллелить опрос DSP. В ходе разработки SSP было опробовано несколько вариантов решения этой задачи, таких как: Parallel.ForEach, Task.WaitAll и Task.WhenAll. Особых различий в производительности обнаружено не было, каждый способ исправно выполнял свою задачу, но в связи с переходом на асинхронную модель выбор пал на последний вариант. Сам запрос к DSP осуществляется посредством HttpWebRequest. Самое главное, что следует сделать при работе с HttpWebRequest, это включить поддержку KeepAlive, потому что запросы к DSP идут постоянно друг за другом, и постоянные обрывы и установка соединения начнут заметно тормозить получение ответа. Приведу пример получившегося класса для опроса DSP:
Клиентская часть нашей SSP. Это личный кабинет веб-издателя (площадки), который содержит статистику по всем его рекламным местам. VOX — это self-service платформа, позволяющая создавать как баннерные рекламные места (5 самых популярных в RTB размеров), так и Rich Media баннеры (fullscreen). На каждое рекламное место можно повесить заглушку (HTML или Javascript), а также добавить счетчик показов сторонней системы.
Вот на этом, пожалуй, мы и закончим обзор работы нашей SSP.
Нагрузка здесь не такая большая, как, например, в нашей DSP, а основное время обработки запроса тратится на ожидание ответов от подключенных DSP. Написана она в виде асинхронного ASP.NET MVC-приложения.
Итак, давайте для начала разберёмся, как же происходит обработка запросов.
Запрос на получение рекламы начинает формироваться ещё на стороне клиента. Здесь посредством javascript мы стараемся собрать как можно больше информации о посетителе сайта, на котором установлен наш скрипт. В основном это техническая информация: включен ли в браузере flash player, размеры окна браузера, размеры экрана, получаем адрес страницы и реферер на страницу, с которой пришёл пользователь и прочее. Далее информация собирается в JSON строку, кодируется и отправляется запрос на получение рекламы. Теперь за дело берётся back-end.
Получив запрос на показ рекламы, так же получаем из базы всю известную нам информацию о данном посетителе (историю его интересов, просмотров рекламы и кликов). По user-agent'у мы получаем данные об используемом браузере, его версии и операционной системе посетителя, а по ip-адресу определяем его текущее местоположение. Всю собранную о пользователе информацию мы отправляем в нашу DMP для её дальнейшего анализа.
Собрав данные о пользователе, мы переходим к площадке. Получаем информацию о стоимости рекламного места, CTR площадки, категориях контента, наличию запрещённого контента и т.д., на основе этих данных будет сформирована минимальная пороговая цена аукциона. Закончив с площадками, переходим к самой длительной задаче SSP, это опрос подключенных DSP. На основе полученных выше описанным способом данных формируется запрос к DSP. Далее происходит паралельный опрос DSP, на ответ каждой из них отводиться 120 ms, в которые входит и время на доставку запроса, и получение ответа (ping до серверов, на которых расположена DSP). Так что месторасположение DSP тоже играет важную роль. Получив ответ от всех DSP, остаётся лишь отсортировать их по размеру ставки и взять ответ с самой большой. Ответ в виде JSON'а, для показа рекламы мы возвращаемся на front-end.
Фронтенду остаётся самая малость — вывести код полученного объявления. Так как уследить за качеством кода DSP и его совместимостью с площадками нет никакой возможности, наш код для вывода объявления на страницу создаёт отдельный iframe, загружает в него код, и вот его-то мы и выводим на страницу. На этом и заканчивается обработка запроса на рекламу.
Давайте теперь разберёмся с какими трудностями пришлось столкнуться в ходе реализации SSP, и как мы с ними справились. Одной из основных проблем в ходе реализации SSP стало забивание пула IIS. Причиной этому было ожидание ответа от DSP в течении 100 и более миллисекунд. В то время, когда SSP ждала ответа от DSP, остальные запросы ждали, пока SSP обработает текущий. А решением данной проблемы стало использование асинхронных контроллеров, вот здесь нам и пригодились модификаторы async/await. Также не лишним будет упомянуть, что информация о площадке и DSP кешируется в памяти приложения и ежеминутно обновляется. Следующим основным местом, на которое стоит обратить внимание, является опрос DSP. Первоначально встал вопрос, как распараллелить опрос DSP. В ходе разработки SSP было опробовано несколько вариантов решения этой задачи, таких как: Parallel.ForEach, Task.WaitAll и Task.WhenAll. Особых различий в производительности обнаружено не было, каждый способ исправно выполнял свою задачу, но в связи с переходом на асинхронную модель выбор пал на последний вариант. Сам запрос к DSP осуществляется посредством HttpWebRequest. Самое главное, что следует сделать при работе с HttpWebRequest, это включить поддержку KeepAlive, потому что запросы к DSP идут постоянно друг за другом, и постоянные обрывы и установка соединения начнут заметно тормозить получение ответа. Приведу пример получившегося класса для опроса DSP:
Код
public class DspRequests
{
private const string _method = "POST";
private const string _contentType = "application/json";
private static List<string> _headers = new List<string> { "x-openrtb-version: 2.0" };
private List<string> _dspUrls { get; set; }
public List<string> DspUrls {
get
{
returns _dspUrls;
}
set
{
_dspUrls = value;
}
}
public async Task<List<string>> PollDspAsync(string request)
{
List<string> responses = null;
var tasks = _dspUrls.DspRecords.Select(el => PollAsync(el, request));
responses = (await Task.WhenAll(tasks)).Where(el => el != null).ToList();
return responses;
}
private async Task<string> PollAsync(string url, string bidRequest)
{
string response = null;
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
httpWebRequest.Method = _method;
httpWebRequest.ContentType = _contentType;
httpWebRequest.KeepAlive = true;
foreach (string header in _headers)
httpWebRequest.Headers.Add(header);
httpWebRequest.Timeout = 120;
using (StreamWriter streamWriter = new StreamWriter(await httpWebRequest.GetRequestStreamAsync()))
{
streamWriter.Write(bidRequest);
}
using (HttpWebResponse webResponse = (HttpWebResponse) await httpWebRequest.GetResponseAsync())
{
if (webResponse.StatusCode == HttpStatusCode.OK)
{
using (StreamReader streamReader = new StreamReader(webResponse.GetResponseStream()))
{
response = streamReader.ReadToEnd();
}
}
}
return response;
}
}
Клиентская часть нашей SSP. Это личный кабинет веб-издателя (площадки), который содержит статистику по всем его рекламным местам. VOX — это self-service платформа, позволяющая создавать как баннерные рекламные места (5 самых популярных в RTB размеров), так и Rich Media баннеры (fullscreen). На каждое рекламное место можно повесить заглушку (HTML или Javascript), а также добавить счетчик показов сторонней системы.
Вот на этом, пожалуй, мы и закончим обзор работы нашей SSP.
Комментарии (6)
vlsergey
03.08.2015 15:45Понятно, что существенная часть кода опущена ( «dspUrls.DspRecords» на это намекает ). Но ещё интересно, куда и как вы статистику пишете.
mdorokhin
03.08.2015 16:34В сыром виде статистика пишется в Aerospike. Далее, каждые 5 минут, она агрегируется и перекочёвывает в PostgreSQL. С ним уже работает клиентская часть.
vlsergey
На второй картинке, похоже, ошибка — вместо второго «опрос DSP» должен быть «ответ DSP».
Не ясно, зачем на графике две отправки данных в DMP и ни одного получения. Если мы рассматриваем «свою» DSP, то там должно быть получение. Если же «чужую» — то там снова и отправка, и получение данных (если SSP не скрывает информацию о посещении).
И хочется цифр: сколько запросов в секунду обрабатывает один SSP-сервер, сколько запросов в секунду с него уходит на DSP, и сколько максимум времени обрабатывается запрос от браузера.
mdorokhin
Благодарим за внимательность, схему мы поправили.
По поводу производительности. В среднем входящий трафик составляет 150 — 200 запросов в секунду, бывают пики до 1000 запросов в секунду, всё это обрабатывают два сервера с 6 экземплярами приложения в пуле каждый. Таким образом нагрузка на один экземпляр приложения 12 — 16 запросов в секунду. На DSP уходит весь этот трафик. Максимальное время обработки запроса складывается из времени ответа DSP (максимум мы ждём 120 миллисекунд) плюс 10 — 20 миллисекунд время работы самой SSP, в итоге максимум 140 миллисекунд.
vlsergey
«в итоге максимум 140 миллисекунд» — то есть никакого жёсткого лимита со стороны front proxy вы не задаёте, так?
«нагрузка на один экземпляр приложения 12 — 16 запросов в секунду» — возможно, я чего-то не понимаю в .NET, но это какие-то очень маленькие цифры. Один эксземпляр обрабатывает один запрос одновременно? Что будет с сервером, когда придёт нормальная нагрузка в 50к запросов в секунду?
SSP на netty (Pure Java NIO), обрабатывает по 10к запросов / секунду на инстанс, по инстансу на сервер, загружая пару CPU.
mdorokhin
Совершенно верно, жёсткого лимита нет.
Да, действительно нагрузки здесь очень маленькие. Параллельность обработки запросов обеспечивается за счёт асинхронной работы приложения. Само приложение работает на IIS и особых чудес в производительности от него ждать не приходиться, но с текущей нагрузкой он справляется. Нагрузка растёт не мгновенно, а в случае нехватки мощностей конечно будем разбираться, например можно посмотреть в сторону проекта Katana, тем более что у нас уже есть опыт работы с ним.