2. О компании
225M пользователей
4M загрузок фото в день
40K RPS на PHP-FPM 2K серверов
160K регистраций в сутки
50 языков интерфейса
3. О чём доклад
• Общая архитектура: история создания, распределение
нагрузки, отказоустойчивость.
• Логи скриптов: сбор, индексация, различные виды
просмотра.
• Влияние Google App Engine — «облачный» разборщик
очередей.
• Планы на будущее
• Как мы бы реализовали «облако» сейчас
4. Стек технологий
• OS — SLES (SUSE Linux Enterprise Server)
• ЯП — PHP 5.5, C/C++, Go, Java
• Базы данных — MySQL, Tarantool, SQLite
• Кеширование — Memcached
• Веб-сервер — Nginx
5. Общая архитектура
• «Старая система»: mcron — утилита для
раскладки crontab по машинам
• Общая архитектура новой системы («облака»)
• Распределение нагрузки по машинам, «попугаи»
• Способы обеспечения отказоустойчивости
9. Недостатки старой системы
• Ручное распределение нагрузки по серверам
• Ручной перенос скриптов с «упавших» машин —
очень большой downtime
• Наличие «особенных» машин, на которых
установлен дополнительный софт
10. «Облако»
• Запуск заданий по расписанию / через API
• Автоматическая балансировка нагрузки
• Отсутствие «особенных» машин (на всех
машинах стоит весь необходимый софт)
• Отказоустойчивость к «падению» машин —
автоматический перезапуск после таймаута
14. Архитектура
MySQL
MySQL
cloudsys1
cloudsys2
cloud1
•
•
•
cloudN
Легенда:
master
replication
mysql
phproxyd
heartbeat
15. «Облако»
• Около 1 000 машин*
• 15K SQL RPS (50/50 read/write)
• 1 000 запусков скриптов в секунду
• «Запускалка» на PHP, 16 процессов
• Планировщик на go
* Цифры приведены для 1 ДЦ, у нас их 2
19. Отказоустойчивость
• MySQL — ручное (!) переключение на slave в случае
аварии
• Управляющая логика работает «циклами» — перед
началом цикла берется лок в базе, по окончании цикла
лок отпускается
• Если машина «упала» в середине цикла, то через
wait_timeout на сервере соединение будет разорвано
и, соответственно, отпущен лок, давая возможность
работать логике другой машине
20. Отказоустойчивость
• По умолчанию wait_timeout составляет 8 часов…
• Мы выставили wait_timeout = 60 сек
• Cпецифичная для Percona настройка
innodb_kill_idle_transaction = 60 сек
• Таким образом, при любых проблемах с сетью
или с машинами, максимальный простой
управляющей логики составляет 1 минуту
21. Отказоустойчивость
• «Задания» запускаются с лимитом на максимальное
время их работы, который задает пользователь
• При наступлении лимита скриптами присылается
SIGTERM
• Если машина не отвечает — скрипт сам «погибает» от
SIGALRM, поскольку при запуске скрипта мы вызываем
alarm(макс.время работы + 3 секунды)
• Часы на всех машинах идут с точностью до 1 секунды
22. Сбор логов
• Каждое задание получает уникальный id
• Задание — запуск скрипта, с перенаправлением
вывода в файлы «logs/phproxyd.<id>.(out|err).log»
• С помощью inotify слушаем изменения в директории
с логами и отправляем новые строки в scribe
• С задержкой доставки scribe (несколько секунд) логи
скапливаются на отдельной logs-машине
23. Просмотр логов
• Логи для каждого скрипта складываются в отдельный
файл
• Файлы «ротируются» (с использованием logrotate) раз в
неделю
• Каждая строчка в логе содержит id запуска и hostname,
где скрипт запускался
• Логи «индексируются» в MyISAM-таблички для
быстрого просмотра истории по конкретному id
24. Разборщик очередей
• У нас все «важные» очереди хранятся в MySQL,
для сохранности и транзакционности посылки
событий
• В MySQL довольно тяжело «правильно»
разбирать очереди во много потоков
25. Разборщик очередей
• Существует много стратегий «разбора очереди»
в MySQL:
• 1) SELECT … WHERE id % N = M
• 2) UPDATE … SET instance_id = N WHERE
instance_id IS NULL
• 3) SELECT … WHERE shard_id = N
26. Разборщик очередей
• Почти все стратегии плохо масштабируются при
увеличении числа воркеров
• Подход с shard_id масштабируется, но нужно
следить за равномерностью распределения +
требуется решардинг при смене числа воркеров
• Решили написать обработчик очереди, используя
API по добавлению заданий в «облако»
27. Разборщик очередей
• Реализация: На каждую очередь создается 2 «скрипта»:
• 1) мастер, который выбирает id новых событий из
очереди (однопоточный)
• 2) воркеры, которые обрабатывают пачки заданий
(получают набор id заданий, которые нужно
обработать)
• Мастер «помнит» все id, которые он уже выдал и
выбирает из очереди с помощью SELECT id … NOT IN(…)
28. Разборщик очередей
• Мастер группирует события в «пачки» для
большей эффективности обработки
• Равномерное распределение по воркерам
• Динамическое число воркеров (on demand)
• Можно сделать такой разборщик без API, через
fork(), со всеми воркерами на одной машине
29. Причины «падений»
• Суммарный downtime системы составил 3 часа за год
эксплуатации, что дает uptime 99,97%:
• 1 час — Duplicate key в MySQL :)
• 1 час — «кривой» merge (неправильно разрешены
конфликты) — забыли прогнать тесты
• 30 минут — «сломанный» cron на машинах (баг в
одной из версий vixie cron) — не отправлялся heartbeat
30. Проблемы MySQL
• Основные проблемы возникают из-за
глобальных mutex’ов или однопоточных
подсистем:
• Медленный DROP TABLE больших таблиц —
перед unlink() берется глобальный metadata lock
и «висят» все транзакции
31. Проблемы MySQL
• Медленный (однопоточный) purge thread — из-за
MVCC «удаленные» записи могут очень
медленно «пуржиться» из таблиц — в InnoDB
возможна ситуация, когда SELECT COUNT(*) из
«пустой» таблицы идет минуты и возвращает 0
• Однопоточная репликация (до MySQL 5.6) —
изменения могут не успевать применяться на
реплике
32. Проблемы MySQL
• Высокие накладные расходы на подключение —
MySQL плохо «держит» больше N подключений,
где N составляет 2-3 тысячи
• В новых версиях MySQL и в MariaDB есть
«connection pooling», причём для MySQL эта
возможность отсутствует в Community Edition
33. Проблемы ядра Linux
• Баг с выводом ps и «[migration/N]», который якобы
«ест 100% CPU» (на самом деле не ест)
• Очень медленный unlink() больших файлов, даже с
ext4 (возникает из-за высокой фрагментации)
• «Плохая» реализация inotify — если в директории
активно создают файлы и у вас много «свободной»
ОЗУ, inotify_add_watch() будет занимать секунды (!)
и полностью блокировать запись в эту директорию
34. Планы на будущее
• Полностью перевести управляющую логику на Go:
иметь по одной goroutine на машину «облака» —
одна «затупившая» машина не будет тормозить
обработку остальных заданий
• Перевести phproxyd на PHP (уже написан, нужно
запустить в production) — экономия на запуске
интерпретатора
• Возможно, открыть исходные тексты системы
35. Как бы мы реализовывали сейчас
• Управляющая логика на Go — отличный выбор,
если вы почему-то не любите Erlang
• Хранение текущего состояния заданий —
Tarantool + Lua процедуры
• Сразу написать новый демон для запуска
заданий вместо существующего (на PHP, конечно
же)