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

36
Каплуновский Борис [email protected] @bskaplou facebook.com/boris.kaplounovsky Дизайн поискового движка aviasales.ru http://avs.io/highload2013

description

HighLoad++ 2013

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

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

Каплуновский Борис[email protected]@bskaploufacebook.com/boris.kaplounovsky

Дизайн поискового движка aviasales.ru

http://avs.io/highload2013

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

Agenda

● Задачи метопоисковика авиабилетов● Предыдущек решение и его недостатки● Требования к новой системе● DSL “Ясеня” Юниты и цепочки● Отказоустойчивость и базы данных● Организация кластера

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

Что мы делаем?

Получаем запрос от пользвателяПосылаем 60 запросов по 2 килобайтаПолучаем 40 ответов по ~1 одному мегабайту

20 000 запросов в час13 Гигабайт в минуту~6000 проданных билетов в сутки (15 x Boeing 747)

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

КонфигурированиеРазные наборы гейтов в зависимости от:● Геоположения пользователя● Пунктов вылета и назначения● Набора пассажиров● Источника трафика● Локали пользователя● Работоспособности гейтов● Фазы луны● А также комбинаций всех вычеперечисленных параметров

Новые гейты подключаются по несколько раз в неделюПравила выбора набора гейтов меняются по несколько раз в час

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

Как это было

MySQLMaster

MySQLSlave

Ruby Passenger

Ruby Passenger

Ruby Passenger

Машинные ресурсы● Рабочий процесс занимает 300mb памяти● Занимающий ресурсы процесс 20 секунд

ничего не делает

Человеческие ресурсы● Запуск процесса RoR ~5-10 секунд● Высокая сложность системы, на введение в

проект нового программиста требовалось несколько дней

● Некоторые участки кода были “сложными для модификации”. Этот код обычно источал баги и тормоза

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

Функциональная декомпозиция вместо обьектной

Строим систему из независимых компонент которые:● Имеют один входной аргумент и один выходной аргумент● Изолированы друг от друга● Не имеют зависимостей от среды исполнения● Могут быть легко протестированы как внутри так и вне системы

params_validator

ozon_gate

eviterra_gate

airbaltic_gate

merge

{"s": [ "params_validator", {"p": [ "ozon_gate", "eviterra_gate", "airbaltic_gate" ]}, "merge"]}

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

Требования к системе

● Простота разработки и низкий порог вхождения

● Высокая отказоустойчивость

● Хорошая масштабируемость

● Простота конфигурирования

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

Примеры юнитов

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

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

Юниты — рабочие лошадки системы

● Обращаемся к любому юниту системы через 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● Расчёт цены в рублях для гейтов отдающих билеты в другой

валюте

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

Sequential

Обьединяем юниты в цепочки

Последовательные Цепочки● Входной аргумента цепочки подаётся в первый юнит цепочки● Если юнит возвращает NIL последующие юниты не вызываются и NIL обьявляется

результатом работы цепочки● Результат работы первого юнита передаётся во второй юнит● Результат работа второго юнита передаётся в третий юнит● …● Результатом работы цепочки является значение возвращаемое последним юнитом

цеопчки

eviterra_gate add_airports add_airlines

input

output

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

Что будет если?

0 +1 +5 ?

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

Что будет если?

0 +1 +5 6

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

Parallel

Обьединяем юниты в цепочки

Параллельные Цепочки● Входной аргумента цепочки подаётся во все

юниты цепочки● Результаты работы всех юнитов цепочки

собираются в массив● Массив с результатами работы всех юнитов

цепочки является результатом работы цепочки

eviterra_gate

input

output

ozon_gate

clickavia_gate

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

Что будет если?

0

+1

+5[? , ?]

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

Что будет если?

0

+1

+5[1 , 5]

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

Delayed

Обьединяем юниты в цепочки

Отложенные Цепочки● Выходным аргументом цепочки является входной аргумент цепочки● Входной аргумент цепочки подаётся в первый юнит цепочки● Результат работы первого юнита цепочки подаётся во второй юнит цепочки● And so on …

send_to_rabbitmq

input

output

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

Что будет если?

0 +1 +5

?

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

Что будет если?

0 +1 +5

0

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

Пример сложного workflow

Sequential

Delayed

send_to_queueparams_validator

Parallel

eviterra_gate

airbaltic_gate

ozon_gate

clickavia_gate

merge

input

output

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

Что нам даёт DSL

● Простота локальноый и удалённой отладки приложения● Контроль за скоростью выполнения юнитов и цепочек● Свобода менять модель выполнения цепочек

● В одном процессе● В несколькких процессах● На нескольких машинах

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

У нас есть DSL для описания workflow! Что дальше? Базы данных!

Типы данных● Справочники – часто читаются, редко обновляются

● Курсы валют● Аэропорты● Авиакомпании

● Логи – часто пишутся, редко читаются● Поиски● Клики● Информация о поведении пользователей

● Динамические данные – часто читаются, часто пишутся● Ссылки для переходов на страницу покупки● Результаты поиска

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

Справочники● Небольшие справочники храним в памяти

каждого рабочего процесса● Если справочник большой >1mb складываем

его в файловую базу данных (kyotocabinet). Файл базы данных mmapится в адресное пространство всех рабочих процессов ноды

● Информация для справочников хранится на файловой системе, при обновлении файлов через inotify данные закидываются в рабоче процессы

● Между нодами файлы со справочниками раскидываются lsyncd (inotify)

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

Логи

● Рабочие процессы не могут писать данные в глобальное хранилище непосредственно – в этом случае отказ хранилища повредит работоспособности приложения или приведёт к потере данных

● Пусть рабочие процессы складывают данные в локальное хранилище в пределах ноды, и перетаскивают данные в общее хранилище по мере возможности

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

Динамическе данные

● Скорость доступа и на чтение и на запись критичны

● Данные должны быть одинаково доступны со всех нод кластера

● In-memory key-value хранилище – ничего быстрее быть не может!

● Избыточность для обеспечения отказоустойчивости

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

( )

Детали Реализации и производительность

● Язык программирования Python 3● Цепочки и юниты внутри цепочек исполняются асинхронно с помощью Tornado● Парсер xml - lxml● Файловая база данных kyotocabinet● Локальное хранилище данных redis

● Рабочий процесс занимает 60mb ram● Виртуальная машина 8 ядер 16gb обрабатывает до 150 поисковых запросов

одновременно

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

Рабочие процессы

LocalStorage

MySQLWrite Only

Storage

Redis Redis

RabbitMQ

На ноде живут● Пчёлы

● Обрабатывают запросы пользователей● Пишут только в local strage● Могут читать из глобального хранилища

● Муравьи● Переносят данные из локального

хранилища во внешние (rabbitmq/redis/mysql)

● Local Storage● Хранилище способное придержать

данные до восстановления работоспособности внешних хранилищ

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

Что может пойти не так?

LocalStorage

Redis Redis

MySQLWrite Only

Storage

Отказ MySQL/RabbitMQ● Данные сохраняются в local

storage до восстановления работоспособности внешних серверов

● После восстановленя муравьи перенесут данные

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

Что может пойти не так?

LocalStorage

Redis

MySQLWrite Only

Storage

Отказ Redis● И запись и чтение

осуществляются в оба redis одновременно

● Если один из серверов выходит из строя второй берёт нагрузку на себя

RabbitMQ

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

Что может пойти не так?

LocalStorage

Redis

MySQLWrite Only

Storage

Отказ Всех Redis● Пчёлы не смогут читать

данные и часть запросов не будет работать

● Данные не потеряются, как только сервера redis восстановятся муравье перетещут туда данные

RabbitMQ

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

Что может пойти не так?Local

Storage

Redis

MySQLWrite Only

Storage

Отказ LocalStorage● Нода целиком выводится из

кластера● Нагрузка распределяется

по остальным нодам кластера

RabbitMQ

Redis

LocalStorage

LocalStorage

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

Ясень и все все все

LocalStorage

MySQLWrite Only

Storage

Redis Redis

RabbitMQ

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

Итого● Юниты и цепочки могут быть независимо

сконфигурированы в рантайме● Среда исполнения позволяет контролировать скорость

выполнения юнитов и цепочек● Отладка системы осуществляется через http, просто и

наглядно● Разработка юнитов не требует специальной подготовки и

даже знакомства с “Ясенем”

● Скорость запуска системы 0.1 секунды● Сократили количество серверов в два раза● Код в ясень пишут программисты из соседних проектов

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

Q&A

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

SequentialМодели исполнения

● Выполнение параллельных операций:- В разных потоках- В разных процессах- Асинхронное выполнение- На разных серверах

● Точки распараллеливания- Отложенные цепочки- Параллельные цепочки

Delayed

Parallel

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

Почему HTTP? Почему JSON?

● Возможность работать с системой с любого компьютера где есть браузер● Если нет браузера то достаточно curl

● Хорошая производительность библиотек работы с JSON● Возможность править конфигурацию в текстовом редакторе● Универсальный UI для редактирования JSON● Если конфигурация сложна – создаём специализированный редактор

● Веб приложение обязано работать по протоколу HTTP, зачем искать что-то ещё?

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

Почему так не могло продолжаться

Мы теряли деньги когда● MySQL выходил из строя или был

перегружен запросами● Под нагрузкой окзывалось что в новой

версии Rails тормоза● Нас показывали по первому каналу и люди

начинали искать билеты● Деплоились в штатном режиме● Для внесения изменений в систему

требовалась работа программистов● Passenger ы начинали массово

рестартовать под нагрузкой