Demonizing PHP application to gain higher performance. Splitting an application into infrastructure (go) and business logic (PHP) layers to gain higher flexibility.
2. 2
Антон Титов (a.k.a. “Wolfy-J”)
Технический директор и соучредитель Spiral Scout
https://github.com/wolfy-j https://habr.com/ru/users/lachezis/ https://twitter.com/lachezis
Введение
Коммерческая разработка последние 12 лет
Основной стек: PHP7, Golang
3. О чем я буду рассказывать
1. Краткий экскурс в жизненный цикл умирающего PHP-приложения
2. Ускоряем PHP-приложение за счёт демонизации
3. RoadRunner - разработка собственного сервера PHP-приложений
4. Варианты использования RoadRunner
Введение
3
4. Ускорение приложения:
- Снижение расходов
- Избавление от 502 ошибок
Цель презентации
Введение
Увеличение гибкости разработки:
- Упрощаем РНР-код
- Интегрируем Golang в РНР-проект
- Контроль приложения на
инфраструктурном уровне
4
7. Жизненный цикл РНР-приложения
Ускоряем приложение с
помощью Lazy-loading
- Уменьшаем среднее время ответа
- Запрашиваем только необходимый
код
- Используем фреймворк
- Полагаемся на OPCache
7
8. Жизненный цикл РНР-приложения
Кэшируем частые вычисления
- Уменьшаем среднее время ответа
- Усложняем приложение
- Прогреваем наш код
- Примеры: ORM, роутинг, шаблоны
8
9. Обработказапроса
Жизненный цикл приложения
+ Что такое memory leak, race
condition, deadlock?
+ Полная очистка памяти между
запросами
+ Можно меньше думать*
- Много лишней работы (CPU, IO)
- Невозможно кэшировать в памяти
Жизненный цикл РНР-приложения
- Keep-Alive не эффективен
9
10. Жизненный цикл РНР-приложения
Как это работает на сервере
+ Проверено - работает
+ Есть альтернатива - Nginx-Unit
+ Почти невозможно убить
приложение
- Один запрос - один процесс - одна
смерть*
- PHP-FPM всё равно если у вас
блог или нагруженный API
10
12. Что можно сделать
Загрузка и обработка кода
Ускоряем наше приложение
- OPCache
- RFC: Preloading
- JIT
12
13. Что можно сделать
Загрузка и обработка кода
Ускоряем наше приложение
- OPCache
- RFC: Preloading
- JIT
Радикальная смена стека
- HHVM, kPHP, PeachPie
- Переписать всё на language
13
14. Что можно сделать переделать
Загрузка и обработка кода
Ускоряем наше приложение
- OPCache
- RFC: Preloading
- JIT
Радикальная смена стека
- HHVM, kPHP, PeachPie
- Переписать всё на language
Избавиться от загрузки кода
Избавиться от уничтожения
процесса
Использовать память процесса
на полную катушку
14
15. Ускоряем наше приложение
Переносим точку входа -
демонизация
Задача
- Внедряем в приложение основной
цикл
- Загружаем код только один раз
- Минимально меняем код приложения
Цели
- Уменьшение среднего времени ответа
- Снижение нагрузки на сервер
Обработказапроса
- Держим соединения открытыми
15
16. Ускоряем наше приложение
Обработказапроса
Адаптируем приложение
“Бесплатная” инициализация:
- Фокусируемся только на runtime
- Предварительные вычисления:
- Роутинг
- Шаблоны
- Настройки, конфиги
- Схемы ORM
- Singleton - это не ругательство
16
19. Проблемы долгоживущей модели
Ускоряем наше приложение
Утечки памяти
- Используем проверенные библиотеки, пишем аккуратно
- Мониторим воркеры
Утечки данных
- Не храним активного пользователя в глобальном контексте
- Аккуратно обращаемся с данными сессий
19
20. Проблемы долгоживущей модели
Утечки памяти
Ускоряем наше приложение
- Используем проверенные библиотеки, пишем аккуратно
- Мониторим воркеры
Утечки данных
- Не храним активного пользователя в глобальном контексте
- Аккуратно обращаемся с данными сессий
Управление ресурсами
- Контролируем соединения к базам данных
- Избегаем долгоживущих file lock
20
22. Исследуем долгоживущую модель
Обработказапроса
Неблокирующий подход
+ Максимальная производительность
+ Новые возможности: промисы, веб-
сокеты
- Неэффективное использование
блокирующих библиотек
- Повышенная сложность разработки
- Приложение и сервер - один процесс
+ Приложение и сервер - один процесс
22
23. Исследуем долгоживущую модель
Существующие решения
+ Максимальная производительность
+ Новые возможности: промисы, веб-
сокеты
- Неэффективное использование
блокирующих библиотек
- Повышенная сложность разработки
- Приложение и сервер - один процесс
+ Приложение и сервер - один процесс
23
24. Исследуем долгоживущую модель
Блокирующий подход
Обработказапроса
+ Пишем однопоточный код
+ Игнорируем время инициализации
- Потенциальные утечки памяти
- Общение между процессами не
бесплатное
+ Выполняем тяжелую работу вне РНР
24
25. Исследуем долгоживущую модель
Существующие решения
+ Интеграция с несколькими
фреймворками
- Медленный
- Утечки памяти на уровне сервера
- Не работает на Windows*
+ Написан на РНР
PHP-PM
25
28. Пишем свой сервер
Ожидания
Пишем свой сервер приложения
- Совместимость с существующими фреймворками
- Разные процессы для сервера и приложения, горячая перезагрузка
28
29. Пишем свой сервер
Ожидания
Пишем свой сервер приложения
- Совместимость с существующими фреймворками
- Разные процессы для сервера и приложения, горячая перезагрузка
- Высокая скорость и стабильность работы
29
30. Пишем свой сервер
Ожидания
Пишем свой сервер приложения
- Совместимость с существующими фреймворками
- Разные процессы для сервера и приложения, горячая перезагрузка
- Высокая скорость и стабильность работы
- Легкая расширяемость, больше чем HTTP-Server
30
31. Пишем свой сервер
Ожидания
Пишем свой сервер приложения
- Совместимость с существующими фреймворками
- Разные процессы для сервера и приложения, горячая перезагрузка
- Высокая скорость и стабильность работы
- Легкая расширяемость, больше чем HTTP-Server
- Работа из коробки везде, где только возможно
31
32. Пишем свой сервер
Ожидания
Пишем свой сервер приложения
- Совместимость с существующими фреймворками
- Разные процессы для сервера и приложения, горячая перезагрузка
- Высокая скорость и стабильность работы
- Легкая расширяемость, больше чем HTTP-Server
- Работа из коробки везде, где только возможно
- Возможность писать очень быстрые расширения
32
34. Что необходимо для создания PHP-сервера
- Коммуникация между Golang и РНР-процессами
RoadRunner
- Менеджер процессов - создание, уничтожение, мониторинг
- Балансировщик задач - эффективно раздаем задачи воркерам
- HTTP-стек - передаем данные НТТР-запроса воркеру
34
35. Варианты взаимодействия между процессами
Embedding:
RoadRunner
- Требует кастомной сборки РНР
- Сложен в настройке
- Общий процесс для сервера и РНР
- Пример: https://github.com/deuill/go-php
35
36. Варианты взаимодействия между процессами
Embedding:
RoadRunner
- Требует кастомной сборки РНР
- Сложен в настройке
- Общий процесс для сервера и РНР
- Пример: https://github.com/deuill/go-php
Shared Memory:
- Зависит от OS
- Требуется ручная синхронизация
- Сложен в реализации
36
37. Варианты взаимодействия между процессами
Embedding:
RoadRunner
- Требует кастомной сборки РНР
- Сложен в настройке
- Общий процесс для сервера и РНР
- Пример: https://github.com/deuill/go-php
Shared Memory:
- Зависит от OS
- Требуется ручная синхронизация
- Сложен в реализации
KISS
37
38. Пишем свой транспортный протокол - Goridge
Требования:
RoadRunner
- Работа используя PIPES, UNIX/TCP SOCKETS
- Проще - лучше (KISS)
- Минимальный оверхед
- Заголовки пакетов
- Полудуплекс
- Обнаружение ошибок
- Возможность реализации на РНР без внешних пакетов
38
39. Goridge v2.0: Структура пакета данных
RoadRunner
1 байт: тип [CONTROL, ERROR, STREAM, FORMAT]
8 байт: размер (LE) 8 байт: размер (BE) Данные
- 17 байт оверхед (заголовок)
- 2^64 максимальный размер
- 8 контрольных флагов
- Обнаружение ошибок, используя инвертированный size ([1234]:[4321])
39
40. Goridge v3.0*: Работа над ошибками
RoadRunner
1 байт: тип [CONTROL, ERROR, STREAM, FORMAT]
8 байт: размер (LE) контрольная сумма Данные
- уменьшаем оверхед
- больше пространства для мета-данных
- поддержка Async PHP
40
41. Реализация на уровне языка
RoadRunner
- encoding/binary
- io для STDIN, STDOUT
- net для UNIX, TCP
- pack/unpack
- streams для STDIN/STDOUT
- sockets для UNIX, TCP
41
44. Менеджер PHP-процессов - Воркер
RoadRunner
- Используем os/exec, runtime, sync,
atomic
- Мониторим состояние
- Собираем статистику
- Общаемся через Goridge
- Собираем данные STDERR в
реальном времени
44
45. Менеджер PHP-процессов - Фабрика
- Graceful-уничтожение воркера
(через запрос STOP)
- Запрашиваем создание
воркера в любой момент
- PID Handshake при
использовании сокетов
RoadRunner
45
46. - Храним активные воркеры в стеке
- Запрашиваем первый свободный воркер
- Используем Golang buffered channel в виде LIFO Stack
RoadRunner
Балансировщик задач: запрос воркера
46
47. - Блокируем воркер на время выполнения запроса
- Timeout в случае, если нет свободных воркеров
- Используем sync, atomic
RoadRunner
Балансировщик задач: обработка запроса
47
48. - В случае успеха возвращаем воркер в конец стека
- Воркер готов выполнить следующий запрос
- Отдаем ответ пользователю
RoadRunnerRoadRunner
Балансировщик задач: возврат воркера
48
49. - В случае ошибки просим
новый воркер
- Отдаем ошибку
пользователю
RoadRunnerRoadRunnerRoadRunner
Балансировщик задач: обработка ошибок
49
50. - Блокирующий подход
- 200ns на аллокацию
воркера
- Работа даже в случае
критических ошибок
RoadRunnerRoadRunnerRoadRunner
Балансировщик задач: обработка ошибок
50
52. - Конвертируем net/http-запрос в PSR-7-формат
- Посылаем запрос первому свободному воркеру
- Распаковываем запрос в PSR-7-объект
- Генерируем ответ
RoadRunnerRoadRunnerRoadRunner
Реализация HTTP стека
52
53. Реализация на уровне языка
RoadRunner
- Стандартная библиотека
- HTTPS, HTTP/2
- Множество расширений
- Не зависит от фреймворка
- Множество расширений
- Иммутабельный*
NET/HTTP PSR-7
53
54. Как это выглядит в коде
- Единая точка входа
- Запускается в любом
окружении
- Конфигурация количества
воркеров, таймаутов и т.д.
RoadRunner
54
55. Как это выглядит в коде
RoadRunner
55
- Единая точка входа
- Запускается в любом
окружении
- Конфигурация количества
воркеров, таймаутов и т.д.
58. Модульность
- Возможность использования в виде
standalone библиотеки
- Общение с воркерами бинарными
пакетами
- Общение с сервером через Goridge
RPC
RoadRunner
58
59. Пишем Middleware на Golang: Авторизация
- Валидируем запрос до вызова PHP
- Передаем контекст, используя PSR-7-
атрибуты
- Реализуем Rate-Limiter, Circuit-Breaker
RoadRunner
59
60. Пишем Middleware на Golang: Мониторинг
- Собираем метрики приложения в
реальном времени
- Собираем application-specific метрики
- Смотрим за потреблением памяти
RoadRunner
60
61. Распределенный трейсинг и логирование
- Тегируем запросы
- Передаем тег запроса нижестоящему
сервису
- Собираем логи со всех воркеров
- Строим распределенный трейс вызовов
RoadRunner
61
62. Записываем историю запросов
- Сохраняем историю запросов
- Производим нагрузочное тестирование
- Производим автоматическое
тестирование
- Убеждаемся в стабильности API
RoadRunner
62
63. Обрабатываем часть запросов на Golang
- Обрабатываем избранные запросы на
Golang
- Переносим код на другой стек, по
необходимости
RoadRunner
63
65. Управляем окружением PHP
RoadRunner
65
- Модифицируем ENV, используя
внешний провайдер
- Избавляемся от .env
- Упрощаем деплой
- Изолируем окружение внутри
RoadRunner
66. Интегрируем Golang-библиотеки в PHP
RoadRunner
66
- Библиотека для полнотекстового
поиска BleveSearch
- Тонкий сервисный слой на PHP
- Поиск по документации без внешних
баз данных
67. AWS Lambda
RoadRunner
67
- На основе Golang runtime
- Минимальный оверхед
- Доступ к настройкам окружения
- < 0.5ms на echo-запрос после
прогрева
https://roadrunner.dev/docs/library-aws-lambda
68. gRPC для PHP
- Protobuf-прокси с генерацией
сервисного слоя
- Golang и PHP-сервисы в одном
приложении
- Middleware (Interceptors)
- Может работать в связке с HTTP
RoadRunner
68
https://github.com/spiral/php-grpc
69. Сервер очередей
- Смена драйвера без изменения кода
- Работа без брокера (очередь в памяти)
- Реконнекты, pre-fetch контроль
- Graceful shutdown, pause, resume
- Publish/Consume из PHP и Golang
- Ловит фатальные ошибки воркеров
RoadRunner
69
https://github.com/spiral/jobs
70. Разделяем доменные области
PHP
- Бизнес-логика приложения, API
- HTML-рендеринг
- ORM и работа с базами данных
Golang
- Инфраструктурная логика
- Авторизация
- Мониторинг
- Очереди, источники данных
- Многопоточные алгоритмы
- WebSockets, etc
RoadRunner
70
72. Использование на Production
- В четыре раза более низкий latency по
сравнению c PHP-FPM
- Отсутствие 502 ошибок под нагрузками
- Uptime воркеров до 2-х месяцев
- Используем Keep-Alive
- Работа в Alpine Docker в Kubernetes
RoadRunner
72
79. RoadRunner
RoadRunner
79
- Если требуется ускорить приложение
- Если нужен больший контроль над PHP
- Если возможностей PHP не хватает
- Когда вашу проблему решает Golang-библиотека
Контрибьюторы приветствуются.
Спасибо за внимание!