Борис Каплуновский, Aviasales.ru
description
Transcript of Борис Каплуновский, Aviasales.ru
Каплуновский Борис[email protected]@bskaploufacebook.com/boris.kaplounovsky
Дизайн поискового движка aviasales.ru
http://avs.io/highload2013
Agenda
● Задачи метопоисковика авиабилетов● Предыдущек решение и его недостатки● Требования к новой системе● DSL “Ясеня” Юниты и цепочки● Отказоустойчивость и базы данных● Организация кластера
Что мы делаем?
Получаем запрос от пользвателяПосылаем 60 запросов по 2 килобайтаПолучаем 40 ответов по ~1 одному мегабайту
20 000 запросов в час13 Гигабайт в минуту~6000 проданных билетов в сутки (15 x Boeing 747)
КонфигурированиеРазные наборы гейтов в зависимости от:● Геоположения пользователя● Пунктов вылета и назначения● Набора пассажиров● Источника трафика● Локали пользователя● Работоспособности гейтов● Фазы луны● А также комбинаций всех вычеперечисленных параметров
Новые гейты подключаются по несколько раз в неделюПравила выбора набора гейтов меняются по несколько раз в час
Как это было
MySQLMaster
MySQLSlave
Ruby Passenger
Ruby Passenger
Ruby Passenger
Машинные ресурсы● Рабочий процесс занимает 300mb памяти● Занимающий ресурсы процесс 20 секунд
ничего не делает
Человеческие ресурсы● Запуск процесса RoR ~5-10 секунд● Высокая сложность системы, на введение в
проект нового программиста требовалось несколько дней
● Некоторые участки кода были “сложными для модификации”. Этот код обычно источал баги и тормоза
Функциональная декомпозиция вместо обьектной
Строим систему из независимых компонент которые:● Имеют один входной аргумент и один выходной аргумент● Изолированы друг от друга● Не имеют зависимостей от среды исполнения● Могут быть легко протестированы как внутри так и вне системы
params_validator
ozon_gate
eviterra_gate
airbaltic_gate
merge
{"s": [ "params_validator", {"p": [ "ozon_gate", "eviterra_gate", "airbaltic_gate" ]}, "merge"]}
Требования к системе
● Простота разработки и низкий порог вхождения
● Высокая отказоустойчивость
● Хорошая масштабируемость
● Простота конфигурирования
Примеры юнитов
class CurrencyRatesExtender: def __init__(self): self.config = {}
def __call__(self, request): request['currency_rates'] = self.config request['currency_rates']['rub'] = 1 return request
from random import random
class Throttler: def __init__(self): self.config = 1
def __call__(self, request): if random() <= self.config: return request else: return None
Юниты — рабочие лошадки системы
● Обращаемся к любому юниту системы через http● Для кодирования данных используем json● Каждый юнит имеет 3 url для
● Вызова юнита● Загрузки конфигурации юнита● Выгрузки конфигурации юнита
● Обращаемся к любому юниту системы через http● Для кодирования данных используем json● Каждый юнит имеет 3 url для
● Вызова юнита● Загрузки конфигурации юнита● Выгрузки конфигурации юнита
CurrencyConverterUnit
{“price”: 100, “currency”: “usd”}
call
{“usd”: 31.93,“eur”: 43.66}
set_config
{“price”: 3193, “currency”: “rub”}
Какие бывают юниты?● Запрос билетов у гейтов● Добавление в результат поисков информации об аэропортах● Удаление дубликатов предложений от разных агентств● Отправка данных в RabbitMQ● Расчёт цены в рублях для гейтов отдающих билеты в другой
валюте
Sequential
Обьединяем юниты в цепочки
Последовательные Цепочки● Входной аргумента цепочки подаётся в первый юнит цепочки● Если юнит возвращает NIL последующие юниты не вызываются и NIL обьявляется
результатом работы цепочки● Результат работы первого юнита передаётся во второй юнит● Результат работа второго юнита передаётся в третий юнит● …● Результатом работы цепочки является значение возвращаемое последним юнитом
цеопчки
eviterra_gate add_airports add_airlines
input
output
Что будет если?
0 +1 +5 ?
Что будет если?
0 +1 +5 6
Parallel
Обьединяем юниты в цепочки
Параллельные Цепочки● Входной аргумента цепочки подаётся во все
юниты цепочки● Результаты работы всех юнитов цепочки
собираются в массив● Массив с результатами работы всех юнитов
цепочки является результатом работы цепочки
eviterra_gate
input
output
ozon_gate
clickavia_gate
Что будет если?
0
+1
+5[? , ?]
Что будет если?
0
+1
+5[1 , 5]
Delayed
Обьединяем юниты в цепочки
Отложенные Цепочки● Выходным аргументом цепочки является входной аргумент цепочки● Входной аргумент цепочки подаётся в первый юнит цепочки● Результат работы первого юнита цепочки подаётся во второй юнит цепочки● And so on …
send_to_rabbitmq
input
output
Что будет если?
0 +1 +5
?
Что будет если?
0 +1 +5
0
Пример сложного workflow
Sequential
Delayed
send_to_queueparams_validator
Parallel
eviterra_gate
airbaltic_gate
ozon_gate
clickavia_gate
merge
input
output
Что нам даёт DSL
● Простота локальноый и удалённой отладки приложения● Контроль за скоростью выполнения юнитов и цепочек● Свобода менять модель выполнения цепочек
● В одном процессе● В несколькких процессах● На нескольких машинах
У нас есть DSL для описания workflow! Что дальше? Базы данных!
Типы данных● Справочники – часто читаются, редко обновляются
● Курсы валют● Аэропорты● Авиакомпании
● Логи – часто пишутся, редко читаются● Поиски● Клики● Информация о поведении пользователей
● Динамические данные – часто читаются, часто пишутся● Ссылки для переходов на страницу покупки● Результаты поиска
Справочники● Небольшие справочники храним в памяти
каждого рабочего процесса● Если справочник большой >1mb складываем
его в файловую базу данных (kyotocabinet). Файл базы данных mmapится в адресное пространство всех рабочих процессов ноды
● Информация для справочников хранится на файловой системе, при обновлении файлов через inotify данные закидываются в рабоче процессы
● Между нодами файлы со справочниками раскидываются lsyncd (inotify)
Логи
● Рабочие процессы не могут писать данные в глобальное хранилище непосредственно – в этом случае отказ хранилища повредит работоспособности приложения или приведёт к потере данных
● Пусть рабочие процессы складывают данные в локальное хранилище в пределах ноды, и перетаскивают данные в общее хранилище по мере возможности
Динамическе данные
● Скорость доступа и на чтение и на запись критичны
● Данные должны быть одинаково доступны со всех нод кластера
● In-memory key-value хранилище – ничего быстрее быть не может!
● Избыточность для обеспечения отказоустойчивости
( )
Детали Реализации и производительность
● Язык программирования Python 3● Цепочки и юниты внутри цепочек исполняются асинхронно с помощью Tornado● Парсер xml - lxml● Файловая база данных kyotocabinet● Локальное хранилище данных redis
● Рабочий процесс занимает 60mb ram● Виртуальная машина 8 ядер 16gb обрабатывает до 150 поисковых запросов
одновременно
Рабочие процессы
LocalStorage
MySQLWrite Only
Storage
Redis Redis
RabbitMQ
На ноде живут● Пчёлы
● Обрабатывают запросы пользователей● Пишут только в local strage● Могут читать из глобального хранилища
● Муравьи● Переносят данные из локального
хранилища во внешние (rabbitmq/redis/mysql)
● Local Storage● Хранилище способное придержать
данные до восстановления работоспособности внешних хранилищ
Что может пойти не так?
LocalStorage
Redis Redis
MySQLWrite Only
Storage
Отказ MySQL/RabbitMQ● Данные сохраняются в local
storage до восстановления работоспособности внешних серверов
● После восстановленя муравьи перенесут данные
Что может пойти не так?
LocalStorage
Redis
MySQLWrite Only
Storage
Отказ Redis● И запись и чтение
осуществляются в оба redis одновременно
● Если один из серверов выходит из строя второй берёт нагрузку на себя
RabbitMQ
Что может пойти не так?
LocalStorage
Redis
MySQLWrite Only
Storage
Отказ Всех Redis● Пчёлы не смогут читать
данные и часть запросов не будет работать
● Данные не потеряются, как только сервера redis восстановятся муравье перетещут туда данные
RabbitMQ
Что может пойти не так?Local
Storage
Redis
MySQLWrite Only
Storage
Отказ LocalStorage● Нода целиком выводится из
кластера● Нагрузка распределяется
по остальным нодам кластера
RabbitMQ
Redis
LocalStorage
LocalStorage
Ясень и все все все
LocalStorage
MySQLWrite Only
Storage
Redis Redis
RabbitMQ
Итого● Юниты и цепочки могут быть независимо
сконфигурированы в рантайме● Среда исполнения позволяет контролировать скорость
выполнения юнитов и цепочек● Отладка системы осуществляется через http, просто и
наглядно● Разработка юнитов не требует специальной подготовки и
даже знакомства с “Ясенем”
● Скорость запуска системы 0.1 секунды● Сократили количество серверов в два раза● Код в ясень пишут программисты из соседних проектов
Q&A
SequentialМодели исполнения
● Выполнение параллельных операций:- В разных потоках- В разных процессах- Асинхронное выполнение- На разных серверах
● Точки распараллеливания- Отложенные цепочки- Параллельные цепочки
Delayed
Parallel
Почему HTTP? Почему JSON?
● Возможность работать с системой с любого компьютера где есть браузер● Если нет браузера то достаточно curl
● Хорошая производительность библиотек работы с JSON● Возможность править конфигурацию в текстовом редакторе● Универсальный UI для редактирования JSON● Если конфигурация сложна – создаём специализированный редактор
● Веб приложение обязано работать по протоколу HTTP, зачем искать что-то ещё?
Почему так не могло продолжаться
Мы теряли деньги когда● MySQL выходил из строя или был
перегружен запросами● Под нагрузкой окзывалось что в новой
версии Rails тормоза● Нас показывали по первому каналу и люди
начинали искать билеты● Деплоились в штатном режиме● Для внесения изменений в систему
требовалась работа программистов● Passenger ы начинали массово
рестартовать под нагрузкой