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