Async Expressive with Swoole
Have you used Node.js?
For those of my readers unfamiliar with Node.js, it's a server-side JavaScript framework that provides the ability to create, among other things, network services. To do so, it provides an event loop, which allows for such things as asynchronous processing.
In the PHP ecosystem, a group of Chinese developers have been creating an extension that provides many of the same capabilities as Node.js. This extension, called Swoole, allows you to create web servers with asynchronous capabilities. In many cases, the asynchronous capabilities are handled via coroutines, allowing you to write normal, synchronous code that still benefits from the asynchronous nature of the system event loop, allowing your server to continue responding to new requests as they come in!
We've been gradually adding and refining our Swoole support in Expressive, and recently issued a stable release that will work with any PSR-15 request handler. In this post, I'll enumerate what I feel are the reasons for considering Swoole when deploying your PHP middleware application.
I feel there are three key advantages to Swoole, and, by extension, any async PHP runtime:
- Application-specific servers
- Performance
- Async processing
Application-specific servers
There are a few general architectures for applications:
- A single web server sitting in front of many web applications.
- A single web server sitting in front of a single web application.
- A load balancer sitting in front of many servers. Some servers might serve the same application, to provide redundancy. (Often, today, these may even be identical docker containers.)
The first scenario is common in internal networks and development, and in many shared hosting scenarios. It's generally considered less secure, however, as a vulnerability in one application can potentially escalate to affect all applications hosted on the server. Additionally, it means that any updates to PHP versions must be tested on all applications, which often means updates are few and far between — which is also problematic from a security standpoint.
When you want to isolate the environment, you'll move to a single web server, single PHP application model:
And when you start scaling, this becomes a load balancer sitting in front of many of these web server/PHP application pairs:
In each of these last two scenarios, there's one thing I want to point out: your application consists of at least two distinct services: the PHP processes, and a web server.
You may have other services as well, such as an RDBMS or document database, cache, search, etc. But generally these are on separate servers and scaled separately. As such, they're outside of this discussion.
In these scenarios, this means each "server" is actually a composite. And when you are adding redundancy to your architecture, this adds significant complexity. It's one more process on each and every node that can fail, and additional configuration you need when deploying.
When we start thinking about microservices, this becomes more problematic. Microservices should be quick and easy to deploy; one service per container is both typical and desired.
What Swoole lets us do is remove one layer of that complexity.
We can have a service per container, and that container can be built with only PHP. We start the Swoole HTTP server, and it's ready to go. We then tell the reverse proxy or load balancer how to route to it, and we're done.
This is useful in each of the scenarios, including the one web server/mulitiple applications scenario, as we can have different PHP runtimes per application. Our "web server" becomes a reverse proxy instead.
Application-specific servers allow us to simplify our deployment, and ship microservices quickly.
Performance
Remember when PHP 7 came out, and it was like doubling the performance of your application?
What if you could do that again?
In our initial benchmarks of Expressive applications, we found that they performed four times better under Swoole th
Truncated by Planet PHP, read more at the original (another 6968 bytes)