Оказавшись внутри цикла, никогда не за его пределами

Одна вещь, которую мне пришлось реализовать против моей воли, - это сервер Websocket… на PHP. Вы не ослышались.

Я всегда очень много говорил о том, что PHP используется для вещей, которые не подходят, например, для удержания соединений сокетов, когда природа языка и сама среда выполнения однопоточные, или, если вы говорите, «блокирующие».

Но даже если бы я не захотел и посмотрел на Go и Rust для реализации, я понял, что WebSocket на PHP не был ЭТОМ плохим для работы, но с некоторыми оговорками.

Веб-сокеты и асинхронная природа PHP

Некоторое время назад некоторые люди обнаружили, что PHP может обрабатывать своего рода параллелизм с помощью генераторов.

Вкратце, генераторы - это функции, которые могут перебирать неизвестные и возвращать значения по мере их получения, просто приостанавливая выполнение функции и ожидая следующего значения. Это как двоюродный брат, менее дружелюбный к волокнам.

Генератор - это ключ к параллелизму, методике, используемой в некоторых языках программирования, таких как Javascript. Вы можете сгенерировать цикл по нескольким «вызываемым объектам», зарегистрированным внутри значения, которое всегда возвращает результат вызываемого объекта в стеке:

Это только верхушка айсберга. Этот простой пример вернет bar foo если вы его запустите. Есть интересная небольшая книга о генераторах и о том, как они могут работать, чтобы сделать код асинхронным.

Для тех, кому нужна производительность, эти библиотеки могут «обойти» снижение производительности генератора и напрямую использовать библиотеки, которые сами управляют событиями, например libuv, libevent и libev. ReactPHP попросит пользователя установить «драйвер», в то время как Amphp будет использовать их автоматически в этом порядке.

Розетка как источник генератора

Дело в том, что в PHP есть все необходимое для работы с WebSockets. Функция stream_socket_server в PHP позволяет создать сокет, который возвращает любое входящее соединение, поскольку он работает как генератор, с параметром stream_set_blocking, установленным как неблокирующий.

Соедините это с асинхронной библиотекой, такой как ReactPHP или Amp, одной из наиболее часто используемых в наши дни, и вы можете получить обработчик соединения, который не блокирует все приложение в ожидании следующего соединения. Это вкратце, поскольку подробности заслуживают отдельной статьи.

Переход на WebSockets

Теперь, когда у нас есть очень хакерский, но стабильный способ выполнять асинхронные вызовы на PHP - по крайней мере, до тех пор, пока волокна не появятся в PHP 8.1 в конце года - следующий шаг - подключиться к серверу сокетов, а затем обновить его. входящие соединения HTTP-запроса к соединениям WebSockets, сохраняя их открытыми в памяти.

К счастью для нас, уже есть библиотеки, которые подумали об этом и предлагают свой собственный WebSocket Server. Из наиболее часто используемых - Ratchet, который негласно использует ReactPHP, в то время как Amp имеет свой собственный зрелый WebSocket Server.

После выбора яда вы будете в основном озабочены тем, чтобы что-то делать с подключенным клиентом и сообщениями, которые отправляются туда и обратно на сервер с использованием одного постоянного экземпляра PHP.

Веб-сокеты Swoole и Roadrunner

Дело в том, что Swoole и Roadrunner представляют собой своего рода разные звери, у каждого из которых есть свой способ сделать PHP быстрее.

Roadrunner проще понять, поскольку его можно считать полной заменой NGINX или Apache. Это двоичный файл веб-сервера, который подключается к PHP и уравновешивает его нагрузку, не останавливая среду выполнения PHP при каждом обслуживании запроса, а вместо этого поддерживает ее в рабочем состоянии. Сам PHP не затронут, нет необходимости добавлять внешние зависимости и расширения. Это очень чистая реализация, которая скоро должна иметь дружественную поддержку для получения соединений WebSockets и передачи их в PHP. Он даже поддерживает gRPC, метрики и простую настройку.

С другой стороны, Swoole сам по себе является расширением, которое вы должны загрузить в PHP. Это расширение позволяет запускать веб-сервер внутри PHP. Вы можете подумать, что в вашем коде есть NGINX, но с более детальным контролем.

Проблема в том, что вам нужно скомпилировать и установить расширение Swoole через PECL, что не всем нравится делать, особенно в Windows, учитывая, что вам нужно будет использовать WSL2, чтобы фактически его использовать. Преимущество Swoole заключается в повторном использовании собственного стека параллелизма путем замены драйверов базы данных, HTTP-клиентов и Redis на асинхронную работу, что иногда дает лучшую производительность, если вы много работаете над этими частями.

Websocket возможны ... сейчас

Вы можете использовать и развернуть сервер WebSocket на сервере PHP уже сегодня. Учтите, что если вы используете чистую реализацию PHP, основанную на ReactPHP или Amp, это означает переход в их цикл событий и соблюдение того, что они работают в асинхронном контексте.

Производительность сервера Websocket, созданного на чистом PHP, неплохая. Ребята из BeyondCode используют Ratchet и поделились этой метрикой производительности со своим сервером поверх приложения Laravel.

Вот еще один тест, который был запущен на 2-гигабайтной капле Digital Ocean с 2 процессорами. Максимальное количество одновременных подключений на этом сервере составляет почти 60 000.

Конечно, использование PHP приведет к меньшей производительности, чем любой скомпилированный язык, такой как C ++, Rust или даже Go, если мы рассмотрим знаменитый пример 1 миллиона соединений с использованием всего лишь 600 МБ памяти. Мы можем взять еще Node.js, обрабатывающий еще и миллион соединений с помощью библиотеки для WebSockets, написанной на C.

Преимущество здесь - повторное использование кода. Если вы уже написали часть своего приложения на PHP, запуск WebSocket с той же кодовой базой означает более быструю разработку, поскольку вам не нужно ничего переводить.

Что ж, если ваш сервер начинает обрабатывать сотни тысяч подключенных пользователей, вы можете начать искать более крупные решения, такие как Pusher, PubNub, Stream, PieSocket, AWS API Gateway, Firebase Realtime Database. И другие системы для надежной потоковой передачи.

Надеюсь, как только Fibers перейдут на PHP и асинхронные библиотеки обновят свою кодовую базу, WebSockets на PHP могут стать быстрее и, будем надеяться, получат более широкое распространение для экспериментов, таких как Laravel Livewire, чтобы стать лучше.