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. Asynchronous
    Awesome
    php[tek] 2023 • Eric Mann

    View Slide

  2. Traditional PHP Lifecycle
    Singular
    Execution
    No Events
    Blocking I/O

    View Slide

  3. Asynchronous Operations
    Parallel Concurrent
    Doing two things
    simultaneously
    Doing two things
    independently

    View Slide

  4. 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

    View Slide

  5. Asynchronous
    PHP Landscape

    View Slide

  6. Fibers
    Native concurrency support
    within PHP 8.1+
    01

    View Slide

  7. 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.

    View Slide

  8. 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');

    View Slide

  9. 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

    View Slide

  10. 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.

    View Slide

  11. AMPHP
    Asynchronous, multi-tasking PHP
    library support
    02

    View Slide

  12. 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.

    View Slide

  13. 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.

    View Slide

  14. 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.

    View Slide

  15. RabbitMQ
    Multi-language support for
    sending messages between apps
    03

    View Slide

  16. 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!

    View Slide

  17. 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.

    View Slide

  18. 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.

    View Slide

  19. RabbitMQ
    App Queue Worker
    Worker
    Worker

    View Slide

  20. RabbitMQ
    App
    Queue
    Worker
    Worker
    Worker
    Queue

    View Slide

  21. … and Friends
    Native extensions to PHP:
    Open Swoole, RoadRunner, etc…
    04

    View Slide

  22. 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.

    View Slide

  23. 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.

    View Slide

  24. 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

    View Slide

  25. Thank you
    Any questions?
    [email protected]

    View Slide

  26. Just released
    this week!
    Get your own copy in print
    or DRM-free digital!

    View Slide