Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Борис Каплуновский, Aviasales.ru

2,660 views

Published on

HighLoad++ 2013

  • Be the first to comment

Борис Каплуновский, Aviasales.ru

  1. 1. Дизайн поискового движка aviasales.ru http://avs.io/highload2013 Каплуновский Борис bk@aviasales.ru @bskaplou facebook.com/boris.kaplounovsky
  2. 2. Agenda Задачи метопоисковика авиабилетов ● Предыдущек решение и его недостатки ● Требования к новой системе ● DSL “Ясеня” Юниты и цепочки ● Отказоустойчивость и базы данных ● Организация кластера ●
  3. 3. Что мы делаем? Получаем запрос от пользвателя Посылаем 60 запросов по 2 килобайта Получаем 40 ответов по ~1 одному мегабайту 20 000 запросов в час 13 Гигабайт в минуту ~6000 проданных билетов в сутки (15 x Boeing 747)
  4. 4. Конфигурирование Разные наборы гейтов в зависимости от: ● Геоположения пользователя ● Пунктов вылета и назначения ● Набора пассажиров ● Источника трафика ● Локали пользователя ● Работоспособности гейтов ● Фазы луны ● А также комбинаций всех вычеперечисленных параметров Новые гейты подключаются по несколько раз в неделю Правила выбора набора гейтов меняются по несколько раз в час
  5. 5. Как это было ● ● Ruby Passenger MySQL Slave ● Ruby Passenger Ruby Passenger ● MySQL Master ● Машинные ресурсы Рабочий процесс занимает 300mb памяти Занимающий ресурсы процесс 20 секунд ничего не делает Человеческие ресурсы Запуск процесса RoR ~5-10 секунд Высокая сложность системы, на введение в проект нового программиста требовалось несколько дней Некоторые участки кода были “сложными для модификации”. Этот код обычно источал баги и тормоза
  6. 6. Функциональная декомпозиция вместо обьектной ozon_gate params_validator eviterra_gate airbaltic_gate ● ● ● ● merge {"s": [ "params_validator", {"p": [ "ozon_gate", "eviterra_gate", "airbaltic_gate" ]}, "merge" ]} Строим систему из независимых компонент которые: Имеют один входной аргумент и один выходной аргумент Изолированы друг от друга Не имеют зависимостей от среды исполнения Могут быть легко протестированы как внутри так и вне системы
  7. 7. Требования к системе ● Простота разработки и низкий порог вхождения ● Высокая отказоустойчивость ● Хорошая масштабируемость ● Простота конфигурирования
  8. 8. Примеры юнитов 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 class CurrencyRatesExtender: def __init__(self): self.config = {} def __call__(self, request): request['currency_rates'] = self.config request['currency_rates']['rub'] = 1 return request
  9. 9. Юниты — рабочие лошадки системы ● ● ● ● ● ● ● ● Обращаемся к любому юниту системы через http {“price”: 100, “currency”: “usd”} Для кодирования данных используем json call Каждый юнит имеет 3 url для ● Вызова юнита ● Загрузки конфигурации юнита {“usd”: 31.93, set_config CurrencyConverterUnit ● Выгрузки конфигурации юнита “eur”: 43.66} Какие бывают юниты? Запрос билетов у гейтов Добавление в результат поисков информации об аэропортах Удаление дубликатов предложений от разных агентств Отправка данных в RabbitMQ Расчёт цены в рублях для гейтов отдающих билеты в другой валюте {“price”: 3193, “currency”: “rub”}
  10. 10. Обьединяем юниты в цепочки Последовательные Цепочки ● Входной аргумента цепочки подаётся в первый юнит цепочки ● Если юнит возвращает NIL последующие юниты не вызываются и NIL обьявляется результатом работы цепочки ● Результат работы первого юнита передаётся во второй юнит ● Результат работа второго юнита передаётся в третий юнит ● … ● Результатом работы цепочки является значение возвращаемое последним юнитом input цеопчки Sequential eviterra_gate add_airports add_airlines output
  11. 11. Что будет если? 0 +1 +5 ?
  12. 12. Что будет если? 0 +1 +5 6
  13. 13. Обьединяем юниты в цепочки ● ● ● Параллельные Цепочки input Входной аргумента цепочки подаётся во все юниты цепочки Результаты работы всех юнитов цепочки собираются в массив Массив с результатами работы всех юнитов цепочки является результатом работы цепочки Parallel eviterra_gate ozon_gate clickavia_gate output
  14. 14. Что будет если? 0 +1 +5 [? , ?]
  15. 15. Что будет если? 0 +1 +5 [1 , 5]
  16. 16. Обьединяем юниты в цепочки ● ● ● ● Отложенные Цепочки Выходным аргументом цепочки является входной аргумент цепочки Входной аргумент цепочки подаётся в первый юнит цепочки Результат работы первого юнита цепочки подаётся во второй юнит цепочки And so on … input Delayed send_to_rabbitmq output
  17. 17. Что будет если? 0 +1 ? +5
  18. 18. Что будет если? 0 +1 0 +5
  19. 19. Пример сложного workflow output Sequential input Parallel eviterra_gate Delayed airbaltic_gate params_validator ozon_gate clickavia_gate merge send_to_queue
  20. 20. Что нам даёт DSL Простота локальноый и удалённой отладки приложения ● Контроль за скоростью выполнения юнитов и цепочек ● Свобода менять модель выполнения цепочек ● В одном процессе ● В несколькких процессах ● На нескольких машинах ●
  21. 21. У нас есть DSL для описания workflow! Что дальше? Базы данных! ● ● ● Типы данных Справочники – часто читаются, редко обновляются ● Курсы валют ● Аэропорты ● Авиакомпании Логи – часто пишутся, редко читаются ● Поиски ● Клики ● Информация о поведении пользователей Динамические данные – часто читаются, часто пишутся ● Ссылки для переходов на страницу покупки ● Результаты поиска
  22. 22. Справочники ● ● ● ● Небольшие справочники храним в памяти каждого рабочего процесса Если справочник большой >1mb складываем его в файловую базу данных (kyotocabinet). Файл базы данных mmapится в адресное пространство всех рабочих процессов ноды Информация для справочников хранится на файловой системе, при обновлении файлов через inotify данные закидываются в рабоче процессы Между нодами файлы со справочниками раскидываются lsyncd (inotify)
  23. 23. Логи ● ● Рабочие процессы не могут писать данные в глобальное хранилище непосредственно – в этом случае отказ хранилища повредит работоспособности приложения или приведёт к потере данных Пусть рабочие процессы складывают данные в локальное хранилище в пределах ноды, и перетаскивают данные в общее хранилище по мере возможности
  24. 24. Динамическе данные ● ● ● ● Скорость доступа и на чтение и на запись критичны Данные должны быть одинаково доступны со всех нод кластера In-memory key-value хранилище – ничего быстрее быть не может! Избыточность для обеспечения отказоустойчивости
  25. 25. Детали Реализации и производительность ● ● ● ● ● ● ● Язык программирования Python 3 ( ) Цепочки и юниты внутри цепочек исполняются асинхронно с помощью Tornado Парсер xml - lxml Файловая база данных kyotocabinet Локальное хранилище данных redis Рабочий процесс занимает 60mb ram Виртуальная машина 8 ядер 16gb обрабатывает до 150 поисковых запросов одновременно
  26. 26. Рабочие процессы ● ● ● На ноде живут Пчёлы ● Обрабатывают запросы пользователей ● Пишут только в local strage ● Могут читать из глобального хранилища Муравьи ● Переносят данные из локального хранилища во внешние (rabbitmq/redis/mysql) Local Storage ● Хранилище способное придержать данные до восстановления работоспособности внешних хранилищ Local Storage Redis Redis MySQL Write Only Storage RabbitMQ
  27. 27. Что может пойти не так? ● ● Отказ MySQL/RabbitMQ Данные сохраняются в local storage до восстановления работоспособности внешних серверов После восстановленя муравьи перенесут данные Local Storage Redis Redis MySQL Write Only Storage
  28. 28. Что может пойти не так? ● ● Отказ Redis И запись и чтение осуществляются в оба redis одновременно Если один из серверов выходит из строя второй берёт нагрузку на себя Local Storage Redis MySQL Write Only Storage RabbitMQ
  29. 29. Что может пойти не так? ● ● Отказ Всех Redis Пчёлы не смогут читать данные и часть запросов не будет работать Данные не потеряются, как только сервера redis восстановятся муравье перетещут туда данные Local Storage Redis MySQL Write Only Storage RabbitMQ
  30. 30. Что может пойти не так? Local Storage ● ● Отказ LocalStorage Нода целиком выводится из кластера Нагрузка распределяется по остальным нодам кластера Redis Local Storage Redis MySQL Write Only Storage Local Storage RabbitMQ
  31. 31. Ясень и все все все Local Storage Redis Redis MySQL Write Only Storage RabbitMQ
  32. 32. Итого ● ● ● ● ● ● ● Юниты и цепочки могут быть независимо сконфигурированы в рантайме Среда исполнения позволяет контролировать скорость выполнения юнитов и цепочек Отладка системы осуществляется через http, просто и наглядно Разработка юнитов не требует специальной подготовки и даже знакомства с “Ясенем” Скорость запуска системы 0.1 секунды Сократили количество серверов в два раза Код в ясень пишут программисты из соседних проектов
  33. 33. Q&A
  34. 34. Модели исполнения ● ● Sequential Точки распараллеливания - Отложенные цепочки - Параллельные цепочки Выполнение параллельных операций: - В разных потоках - В разных процессах - Асинхронное выполнение - На разных серверах Delayed Parallel
  35. 35. Почему HTTP? Почему JSON? ● ● ● ● ● ● ● Возможность работать с системой с любого компьютера где есть браузер Если нет браузера то достаточно curl Хорошая производительность библиотек работы с JSON Возможность править конфигурацию в текстовом редакторе Универсальный UI для редактирования JSON Если конфигурация сложна – создаём специализированный редактор Веб приложение обязано работать по протоколу HTTP, зачем искать что-то ещё?
  36. 36. Почему так не могло продолжаться ● ● ● ● ● ● Мы теряли деньги когда MySQL выходил из строя или был перегружен запросами Под нагрузкой окзывалось что в новой версии Rails тормоза Нас показывали по первому каналу и люди начинали искать билеты Деплоились в штатном режиме Для внесения изменений в систему требовалась работа программистов Passenger ы начинали массово рестартовать под нагрузкой

×