Эволюция программно-аппаратного обеспечения хранения фотографий в Badoo / Дмитрий Лихачев (Badoo)
Облако в Badoo год спустя
-
Upload
yuriy-nasretdinov -
Category
Technology
-
view
766 -
download
1
description
Transcript of Облако в Badoo год спустя
«Облако» в Badoo год спустя
Юрий Насретдинов
О компании
225M пользователей
160K регистраций в сутки
2K серверов40K RPS на PHP-FPM
4M загрузок фото в день
50 языков интерфейса
О чём доклад• Общая архитектура: история создания, распределение нагрузки, отказоустойчивость.
• Логи скриптов: сбор, индексация, различные виды просмотра.
• Влияние Google App Engine — «облачный» разборщик очередей.
• Планы на будущее
• Как мы бы реализовали «облако» сейчас
Стек технологий• OS — SLES (SUSE Linux Enterprise Server)
• ЯП — PHP 5.5, C/C++, Go, Java
• Базы данных — MySQL, Tarantool, SQLite
• Кеширование — Memcached
• Веб-сервер — Nginx
Общая архитектура
• «Старая система»: mcron — утилита для раскладки crontab по машинам
• Общая архитектура новой системы («облака»)
• Распределение нагрузки по машинам, «попугаи»
• Способы обеспечения отказоустойчивости
mcron
sendSMS.phpanonChat.php #1moderation.php
config
scripts1 scripts2
facebook.phpanonChat.php #2
errorlogs.php
translate.phpanonChat.php #9
cleanup.php
scripts50
…
mcron
sendSMS.phpanonChat.php #1moderation.php
config
scripts1 scripts2
facebook.phpanonChat.php #2
errorlogs.php
translate.phpanonChat.php #9
cleanup.php
scripts50
…
mcron
sendSMS.phpfacebook.php
anonChat.php #1moderation.php
config
scripts1 scripts3
google.phpanonChat.php #2anonChat.php #3
migration.php
translate.phperrorlogs.php
anonChat.php #9cleanup.php
scripts50
…
Недостатки старой системы
• Ручное распределение нагрузки по серверам
• Ручной перенос скриптов с «упавших» машин — очень большой downtime
• Наличие «особенных» машин, на которых установлен дополнительный софт
«Облако»• Запуск заданий по расписанию / через API
• Автоматическая балансировка нагрузки
• Отсутствие «особенных» машин (на всех машинах стоит весь необходимый софт)
• Отказоустойчивость к «падению» машин — автоматический перезапуск после таймаута
«Облако» (для разработчика)
«Облако» (для разработчика)
script
job #1
job #2 job #3
job #4job #5
Архитектура
MySQL
MySQL
cloudsys1
cloudsys2
cloud1
cloudN
• • •
replication
heartbeat
mysql
Легенда:
master
phproxyd
«Облако»• Около 1 000 машин*
• 15K SQL RPS (50/50 read/write)
• 1 000 запусков скриптов в секунду
• «Запускалка» на PHP, 16 процессов
• Планировщик на go
* Цифры приведены для 1 ДЦ, у нас их 2
Планировщик
PHP
Go
Балансировка нагрузки
1000300
600250
2000230
1000200
2000180
weighted round-robin
Балансировка нагрузки
Отказоустойчивость• MySQL — ручное (!) переключение на slave в случае аварии
• Управляющая логика работает «циклами» — перед началом цикла берется лок в базе, по окончании цикла лок отпускается
• Если машина «упала» в середине цикла, то через wait_timeout на сервере соединение будет разорвано и, соответственно, отпущен лок, давая возможность работать логике другой машине
Отказоустойчивость• По умолчанию wait_timeout составляет 8 часов…
• Мы выставили wait_timeout = 60 сек
• Cпецифичная для Percona настройка innodb_kill_idle_transaction = 60 сек
• Таким образом, при любых проблемах с сетью или с машинами, максимальный простой управляющей логики составляет 1 минуту
Отказоустойчивость• «Задания» запускаются с лимитом на максимальное время их работы, который задает пользователь
• При наступлении лимита скриптами присылается SIGTERM
• Если машина не отвечает — скрипт сам «погибает» от SIGALRM, поскольку при запуске скрипта мы вызываем alarm(макс.время работы + 3 секунды)
• Часы на всех машинах идут с точностью до 1 секунды
Сбор логов• Каждое задание получает уникальный id
• Задание — запуск скрипта, с перенаправлением вывода в файлы «logs/phproxyd.<id>.(out|err).log»
• С помощью inotify слушаем изменения в директории с логами и отправляем новые строки в scribe
• С задержкой доставки scribe (несколько секунд) логи скапливаются на отдельной logs-машине
Просмотр логов• Логи для каждого скрипта складываются в отдельный файл
• Файлы «ротируются» (с использованием logrotate) раз в неделю
• Каждая строчка в логе содержит id запуска и hostname, где скрипт запускался
• Логи «индексируются» в MyISAM-таблички для быстрого просмотра истории по конкретному id
Разборщик очередей
• У нас все «важные» очереди хранятся в MySQL, для сохранности и транзакционности посылки событий
• В MySQL довольно тяжело «правильно» разбирать очереди во много потоков
Разборщик очередей• Существует много стратегий «разбора очереди» в MySQL:
• 1) SELECT … WHERE id % N = M
• 2) UPDATE … SET instance_id = N WHERE instance_id IS NULL
• 3) SELECT … WHERE shard_id = N
Разборщик очередей• Почти все стратегии плохо масштабируются при увеличении числа воркеров
• Подход с shard_id масштабируется, но нужно следить за равномерностью распределения + требуется решардинг при смене числа воркеров
• Решили написать обработчик очереди, используя API по добавлению заданий в «облако»
Разборщик очередей• Реализация: На каждую очередь создается 2 «скрипта»:
• 1) мастер, который выбирает id новых событий из очереди (однопоточный)
• 2) воркеры, которые обрабатывают пачки заданий (получают набор id заданий, которые нужно обработать)
• Мастер «помнит» все id, которые он уже выдал и выбирает из очереди с помощью SELECT id … NOT IN(…)
Разборщик очередей• Мастер группирует события в «пачки» для большей эффективности обработки
• Равномерное распределение по воркерам
• Динамическое число воркеров (on demand)
• Можно сделать такой разборщик без API, через fork(), со всеми воркерами на одной машине
Причины «падений»• Суммарный downtime системы составил 3 часа за год эксплуатации, что дает uptime 99,97%:
• 1 час — Duplicate key в MySQL :)
• 1 час — «кривой» merge (неправильно разрешены конфликты) — забыли прогнать тесты
• 30 минут — «сломанный» cron на машинах (баг в одной из версий vixie cron) — не отправлялся heartbeat
Проблемы MySQL• Основные проблемы возникают из-за глобальных mutex’ов или однопоточных подсистем:
• Медленный DROP TABLE больших таблиц — перед unlink() берется глобальный metadata lock и «висят» все транзакции
Проблемы MySQL• Медленный (однопоточный) purge thread — из-за
MVCC «удаленные» записи могут очень медленно «пуржиться» из таблиц — в InnoDB возможна ситуация, когда SELECT COUNT(*) из «пустой» таблицы идет минуты и возвращает 0
• Однопоточная репликация (до MySQL 5.6) — изменения могут не успевать применяться на реплике
Проблемы MySQL
• Высокие накладные расходы на подключение — MySQL плохо «держит» больше N подключений, где N составляет 2-3 тысячи
• В новых версиях MySQL и в MariaDB есть «connection pooling», причём для MySQL эта возможность отсутствует в Community Edition
Проблемы ядра Linux• Баг с выводом ps и «[migration/N]», который якобы
«ест 100% CPU» (на самом деле не ест)
• Очень медленный unlink() больших файлов, даже с ext4 (возникает из-за высокой фрагментации)
• «Плохая» реализация inotify — если в директории активно создают файлы и у вас много «свободной» ОЗУ, inotify_add_watch() будет занимать секунды (!) и полностью блокировать запись в эту директорию
Планы на будущее• Полностью перевести управляющую логику на Go: иметь по одной goroutine на машину «облака» — одна «затупившая» машина не будет тормозить обработку остальных заданий
• Перевести phproxyd на PHP (уже написан, нужно запустить в production) — экономия на запуске интерпретатора
• Возможно, открыть исходные тексты системы
Как бы мы реализовывали сейчас
• Управляющая логика на Go — отличный выбор, если вы почему-то не любите Erlang
• Хранение текущего состояния заданий — Tarantool + Lua процедуры
• Сразу написать новый демон для запуска заданий вместо существующего (на PHP, конечно же)