Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Asynchronous Awesome

Asynchronous Awesome

Sometimes, our use of PHP grows beyond the typical request/response cycle of dynamic page generation. Unfortunately, the threaded nature of PHP - and the stateless nature of the server - betrays any efforts to expand our utilization of the server. Image processing, video rendering, APNS (Apple Push Notification Service) integration - any of these can easily take longer than is reasonable for a simple page request.

Enter tools like message and job queues that empower daemonized PHP workers to handle data processing in the background. Yet further tools enable long-running event loops and asynchronous Promise-driven operations. PHP isn’t multi-threaded, but that doesn’t mean you’re limited to a single-thread paradigm. I will demonstrate various use cases necessitating asynchronous operations, then delve into the code and the tools that make these systems work. Every attendee will leave armed with new ways to think about the management of large data jobs in PHP and an understanding of the tools they can use to make it happen.

Eric Mann

May 17, 2023
Tweet

More Decks by Eric Mann

Other Decks in Technology

Transcript

  1. Why Bother? Image Processing Render or process large graphics in

    the background Data processing Manipulate “big data” in parallel with other tasks API Juggling Rely on multiple APIs at once and collate the results Provide Feedback Parallelize long-running tasks to update UI with progress
  2. PHP Fibers Fibers natively abstract a separate thread with an

    independent call stack. Your application can control two completely separate processes at once. Fibers are not run in parallel but allow for concurrent processing of data. They provide a helpful abstraction for jumping between two different operations without losing context.
  3. PHP Fibers In Practice With the Fiber class, you can

    instantiate a separate call stack. This creates an entirely independent thread of operation that you can control from your application. $fiber = new Fiber(function (): void { $value = Fiber::suspend('fiber'); echo "Value used to resume fiber: ", $value, "\n"; }); $value = $fiber->start(); echo "Value from fiber suspending: ", $value, "\n"; $fiber->resume('test');
  4. PHP Fibers In Practice With the Fiber class, you can

    instantiate a separate call stack. This creates an entirely independent thread of operation that you can control from your application. %> php fibers.php Value from fiber suspending: fiber Value used to resume fiber: test
  5. PHP Fibers Independent Call Stacks Fibers empower the quick creation

    of new/clean/separate call stacks in which variables and state are not shared. You can easily carve off parts of your application to run concurrently! Fibers Will Not Persist Unlike an explicitly forked child process, a Fiber only exists so long as the parent application is running. If the application exits (or crashes) the Fiber ends as well. Use an Abstraction While you certainly can use Fibers in userland, avoid using them directly. Instead, leverage frameworks like AMPHP that abstract the interface away into cleaner concepts like coroutines.
  6. AMPHP Composer-installable framework that provides event-based concurrency to PHP in

    userland. The full event loop allows for effective management of Promise and Coroutine abstractions. Likewise, AMPHP abstracts the native PHP Fiber interfaces to make your code future-proof and efficient to write.
  7. AMPHP In Practice $future1 = async(function () { echo 'Hello

    '; delay(2); echo 'the future! '; }); $future2 = async(function () { echo 'World '; delay(1); echo 'from '; }); echo "Let's start: "; $future1->await(); $future2->await(); Asynchronous calls are entirely non-blocking, allowing the program to either continue operating, or explicitly “await” for the result of the otherwise asynchronous function call.
  8. AMPHP In Practice Asynchronous calls are entirely non-blocking, allowing the

    program to either continue operating, or explicitly “await” for the result of the otherwise asynchronous function call.
  9. RabbitMQ RabbitMQ is an open source message queue. It empowers

    clients in multiple languages to create and dispatch messages to a simple queue system that can then be read and processed independently by other, completely asynchronous workers. A production environment might even use clients and workers leveraging entirely different programming languages!
  10. RabbitMQ In Practice use PhpAmqpLib\Connection\AMQPStreamConnection; $conn = new AMQPStreamConnection('localhost', 5762,

    'u', 'p'); $channel = $conn->channel(); $channel->queue_declare('default', 0, 0, 0, 0); $cb = function($msg) { $data = json_decode($msg->body, true); $to = $data['to']; $message = wordwrap($data['message'], 70, "PHP_EOL"); $headers = 'From: worker.local'; mail($to, 'Message', $message, $headers); $msg->ack(); }; $channel->basic_consume('default', '', 0, 0, 0, 0, $cb); while(count($channel->callbacks)) $channel->wait(); The first step is to create a queue and attach at least one worker that can process messages on that queue. Queues are created dynamically upon their first reference.
  11. RabbitMQ In Practice use PhpAmqpLib\Connection\AMQPStreamConnection; use PhpAmqpLib\Message\AMQPMessage; $conn = new

    AMQPStreamConnection('localhost', 5672, 'u', 'p'); $channel = $conn->channel(); $channel->queue_declare('default'); $message = ['message' => 'Welcome to the team!']; $teammates = ['[email protected]', '[email protected]']; foreach($teammates as $employee) { $message['to'] = $employee; $msg = new AMQPMessage(json_encode($message)); $channel->basic_publish($msg, '', 'default'); } $channel->close(); $connection->close(); Next, publish messages into the queue for the workers to retrieve and process.
  12. Open Swoole Open Swoole is a PECL-installed low-level extension that

    provides an asynchronous framework for PHP. It natively implements promises and coroutines. As a native extension, it outperforms any userland promise/coroutine implementation. It also supports event-driven code and channels for subprocess communication.
  13. Roadrunner Roadrunner is an alternative runtime for PHP, implemented in

    Golang. Uses the same interface you’re used to, but ships its own application server and asynchronous process manager. With Roadrunner, your entire application stays in memory. You can invoke atomic processes in parallel to the main application whenever necessary.
  14. Laravel Octane Octane leverages either (Open) Swoole or RoadRunner to

    serve your Laravel application: • First request stores the entire application in memory • Subsequent requests are fast and efficient • Handles 2-3x requests per second over Apache/Nginx alone