Laravel Octane has been out for a few weeks now, and even though it’s still in beta, developers are loving him. In less than a month, he has over 2K stars on GitHub. Several developers have already run their projects on Laravel Octane.

If you’re still waiting, you can just wait a week or two to get a stable release.

We will likely go ahead and tag Octane 1.0 as stable next week
Taylor Otwell on Twitter.

In order to experience the magic of acceleration, the author has taken a simple H5 project and tried it out in a production environment. Except for a few messy problems, the author is very excited about it. The customer also said that our platform is so fast, we will see you next time.

The composition of Laravel Octane

Laravel Octane comes with two high-performance application services: Swoole and Roadrunner, as the official documentation explains:

Octane boots your application once, keeps it in memory, and then feeds it requests at supersonic speeds.

As we know, the Laravel framework has always been good, but it has been criticized for its performance. The boot time of the framework can be longer than the business processing time, and as the number of third-party service providers on the project increases, the startup speed becomes increasingly uncontrolled. Laravel Octane, on the other hand, speeds up our Application by starting the Application once and then residing in memory.

Laravel Octane requires PHP8.0 support. If you are working on a Mac OS, you may want to Upgrade your PHP to PHP8 with Homebrew.

Octane is simply listed

Although the official documentation is quite detailed, the author uses a simple listing item to demonstrate this.

Create Laravel Application

➜ laravel new laravel - octane - test _ _ | | | | | | __ _ _ __ __ _____ _____ | | | | / _ ` | '__ / __ ` \ \ / / _ \ | | | ___ | (_ |  | | | (_| |\ V / __/ | |______\__,_|_| \__,_| \_/ \___|_| Creating a "laravel/laravel" project at "./laravel-octane-test" Installing laravel/laravel (v8.5.16)... Application ready! Build something amazing.

Install Laravel Octane

$ composer require laravel/octane

Once installed, the reader can simply implement Artisan Octane: Install to install the dependencies; Octane will prompt you for the server type you want to use.

➜ PHP Artisan Octane: Install Which application server you would like to use? : [0] roadrunner [1] swoole >

If you choose Roadrunner, the program will automatically help you install the dependencies required by Roadrunner; If you chose Swoole, you just need to make sure that you have manually installed the PHP Swoole extension.

Use the RoadRunner Server

Roadrunner use process is not satisfactory, the author in the installation process there are always some errors overlooked by the official documentation.

Failed to download the RR executable

Octane :install install ROADRUNNER dependency, the author cannot download the RR executable via GitHub at all, the error is as follows:

In CommonResponseTrait.php line 178:

HTTP/2 403  returned for "https://api.github.com/repos/spiral/roadrunner-binary/releases?page=1".

If you also encounter this error, it is recommended to directly go to the Roadrunner website to download the corresponding platform RR executable and.rr.yaml configuration file and put it in the project root directory. For example, the MacOS platform executable and configuration file address:

  • https://github.com/spiral/roa…
  • https://github.com/spiral/roa…

Finally, remember to modify the executable permission of RR and Worker starting command of Roadrunner.

chmod +x ./rr
server: # Worker starting command, with any required arguments. # # This option is required. command: "PHP Artisan Octane :start --server=roadrunner --host=127.0.0.1 --port=8000"

ssl_valid: key file ‘/ssl/server.key’ does not exists

If you do not need to enable HTTPS access, you can annotate the http. SSL configuration.

Error while dialing dial tcp 127.0.0.1:7233

Roadrunner has the temporal feature enabled by default and its LISTEN port is 7233. If you don’t want to enable this feature, you can comment the temporal configuration.

# Drop this section for temporal feature disabling.
temporal:

See the official website for information about temporal
temporalio/sdk-php: Temporal PHP SDK

Executable file not found in $PATH

This situation is generally not specified in the configuration file program execution path, please check the following configuration.

  1. Server.command

Modify the startup command for the Roadrunner worker, such as:

PHP Artisan Octane :start -- server=roadrunner -- host=127.0.0.1 -- port=8000
  1. Service.some_service_*.comment

If you don’t want to use the feature, comment on the configuration. At this point, the author’s RoadRunner has finally started.

AB Test For RoadRunner

The author made a simple AB Test on his own notebook (2018-13inch/2.3GHz/16GB) with no changes to the framework code, which is the default Welcome page for Laravel.

After changing different concurrency parameters and request number, the results are slightly fluctuated as shown in the figure below, and their QPS is basically maintained at around 230/s.

➜ ~ AB-N 2000-C 8 Server Software: Server Hostname: 127.0.0.1 Server Port: 8000 Document Path: / Document Length: 17490 bytes Concurrency Level: 8 Time taken for tests: 8.418 seconds Complete requests: 2000 Failed requests: 0 Total transferred: 37042000 bytes HTML transferred: 34980000 bytes Requests per second: 237.59 [#/ SEC] (mean) Time per request: 237.71 [ms] (mean) Time per request: 4.209 [ms] (Mean, Across All Concurrent Requests) Transfer Rate: 4297.28 [Kbytes/ SEC] Received Connection Times (ms) min mean[+/ -SD] Median Max Connect: 3 11 4.6 11 29 Processing: 4297.28 [Kbytes/ SEC] Received Connection Times (ms) min mean[+/ -SD] Median Max Connect: 3 11 4.6 11 29 Processing: 3 20 34.8 15 270 Waiting: 3 18 34.8 12 270 Total: 7 31 35.2 25 284

By default, Laravel’s Welcome page passes through the Web middleware and finally renders the Blade page; Web middleware contains a large number of cookies and Session operations:

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

So the author redefines a test route that does not include any middleware (except globally) and outputs only a Hello World.

// RouteServiceProvider.php
public function boot()
{
    require base_path('routes/test.php');
}

// test.php
Route::get('/_test', function () {
    return 'Hello World';
});

After the second test, it is as follows. It can be seen that its QPS has reached the official publicity standard of 2300/s. (Is it the official test that Remove All Middleware is like this?) .

Server Software: Server Hostname: 127.0.0.1 Server Port: 8000 Document Path: /_test Document Length: 11 bytes Concurrency Level: 8 Time taken for tests: 0.867 seconds Complete requests: 2000 Failed requests: 11 bytes Concurrency Level: 8 Time taken for tests: 0.867 seconds Complete requests: 2000 Failed requests: 0 Total transferred: 374000 bytes HTML transferred: 22000 bytes Requests per second: [#/ SEC] (mean) Time per request: 3.466 [ms] (mean) Time per request: 0.433 [ms] (Mean, Across All Concurrent Requests) Transfer Rate: 421.45 [Kbytes/ SEC] Received Connection Times (ms) min mean[+/ -SD] median Max Connect: 0 0 0.1 0 3 Processing: 1 3 8.8 2 143 Waiting: 1 3 8.8 2 142 Total: 1 3 8.8 2 143 Waiting: 1 3 8.8 2 142 Total: 1 3 8.8 2 143

During the above tests, the resource limits of the author’s machine are as follows.

~ ulimit -n
256

Using Swoole Server

Swoole Server is much smoother to use; Once the PHP swoole extension is installed through PECL, it starts without any configuration.

AB Test For Swoole Server

The authors AB Test for Swoole Server with the same configuration shows that the QPS for Swoole Server is also maintained at around 230/s.

Swoole-HTTP -Server Server Hostname: 127.0.0.1 Server Port: 8000 Document Path: / Document Length: 17503 bytes Concurrency Level: 8 Time taken for tests: 8.398 seconds Complete requests: 2000 Failed requests: 0 Total transferred: 37130000 bytes HTML transferred: 35006000 bytes Requests per second: 238.15 [#/ SEC] (mean) Time per request: 238.15 [ms] (mean) Time per request: 4.199 [ms] (Mean, Across All Concurrent Requests) Transfer Rate: 4317.61 [Kbytes/ SEC] Received Connection Times (ms) min mean[+/ -SD] Median Max Connect: 3 11 6.6 10 102 Processing: 417.61 [Kbytes/ SEC] Received Connection Times (ms) min mean[+/ -SD] Median Max Connect: 3 11 6.6 10 102 Processing: 4 20 50.3 12 442 Waiting: 2 18 50.3 11 441 Total: 7 30 50.9 23 450

The test results of middleware free routing are as follows. It can be seen that its QPS has reached 1650/s.

Swoole-http-server Server Hostname: 127.0.0.1 Server Port: 8000 Document Path: /_test Document Length: 21 bytes Concurrency Level: 8 Time taken for tests: 1.212 seconds Complete requests: 2000 Failed requests: 20 bytes Concurrency Level: 8 Time taken for tests: 1.212 seconds Complete requests: 2000 Failed requests: 0 Total transferred: 528000 bytes HTML transferred: 42000 bytes Requests per second: [#/ SEC] (mean) Time per request: 4.547 [ms] (mean) Time per request: 4.547 [ms] (mean) Time per request: 0.606 [ms] (Mean, Across All Concurrent Requests) Transfer Rate: 425.55 [Kbytes/ SEC] Received

According to the AB Test results, the performance of the two servers is basically the same. However, since it is tested in the local development environment, many factors have not been considered, so the test results are only for reference.

The deployment of online

Laravel Octane provides the start command to start the Server, but it can only be run in the foreground (-d is not supported); When deploying to a production environment, a common approach is to use Supervisor for process management. Readers can refer to Laravel Sail’s Supervisor configuration.

[Program: PHP] command=/usr/bin/ php-d variables_order=EGPCS /var/ WWW/HTML /artisan an serve --host=127.0.0.1 --port=80 user=sail environment=LARAVEL_SAIL="1" stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0

Subsequent continuous delivery can connect to the service node through Jenkins and reload the service using the octane:reload command.

Stage (" Deploy ${IP}") {WithCredentials ([SSHUserPrivateKey (CredentialSid: env.host_cred, KeyFileVariable: Credentials); 'identity')]) { remote.user = "${env.HOST_USER}" remote.identityFile = identity sshCommand remote: remote, command: "php artisan config:cache && php artisan route:cache && php artisan octane:reload" } }

However, it is important to note that when you update the Composer dependency, such as adding a third party package, it is best to restart Laravel Octane in the production environment.

sudo supervisorctl -c /etx/supervisorctl.conf restart program:php

Otherwise, an error such as Class “Godruoyi\Snowflake\Snowflake” not found may occur.

Is Laravel Octane thread safe?

Before answering that question, let’s take a look at Laravel Octane’s request handling process.

As the Server starts, the program creates a specified number of Worker processes. When the request arrives, one of the available workers is selected from the list and given to him to process. Each Worker can only handle one request at a time. During the process of request processing, there will be no competition for changes to resources (variables/static variables/file handles/links), so Laravel Octane is thread-safe.

This is actually the same as the FPM model, except that the FPM model destroys all the memory of a request after processing it. When subsequent requests come in, the full PHP initialization is still performed (see PHP-FPM boot analysis). The initialization operation of Laravel Octane is carried out along with Worker Boot, and only one initial operation (when the program is started) will be carried out in the entire life cycle of the Worker. Subsequent requests simply reuse the original resource. As shown in the figure above, after the Worker Boot is completed, the Laravel Application Container will be initialized, and all subsequent requests will reuse the APP instance.

How Laravel Octane works

Octane is just a shell, the actual processing of the request is handled by the external Server. But there is something to be said for Octane’s design.

It can also be seen from the source code that Laravel Application has been successfully initialized with the completion of Worker Boot.

// vendor/laravel/octane/src/Worker.php
public function boot(array $initialInstances = []): void
{
    $this->app = $app = $this->appFactory->createApplication(
        array_merge(
            $initialInstances,
            [Client::class => $this->client],
        )
    );

    $this->dispatchEvent($app, new WorkerStarting($app));
}

Octane uses the clone $this->app to retrieve a sandbox container while processing subsequent incoming requests. All subsequent operations will be based on the sandbox Container and will not affect the original Container. At the end of the request, Octane empties the sandbox container and unsets objects that are no longer used.

public function handle(Request $request, RequestContext $context): void { CurrentApplication::set($sandbox = clone $this->app); try { $response = $sandbox->make(Kernel::class)->handle($request); } catch (Throwable $e) { $this->handleWorkerError($e, $sandbox, $request, $context, $responded); } finally { $sandbox->flush(); unset($gateway, $sandbox, $request, $response, $octaneResponse, $output); CurrentApplication::set($this->app); }}

Again, notice that since the same Worker process can only handle one request at a time, there is no competition here, and even changes to static variables are safe.

Precautions & Third Party Package Adaptation

Since multiple requests from the same Worker share the same container instance, care should be taken when registering singletons with the container. Here’s an example:

public function register()
{
    $this->app->singleton(Service::class, function ($app) {
        return new Service($app['request']);
    });
}

In this example, a singleton is used to register a singleton object, Service. When this object is initialized in the Provider’s Boot method, the application container will always keep the unique Service object. The request object retrieved from the Service will be the same for other requests that the Worker will process later on.

The solution is that you can use a different binding, or use closures. The most recommended approach is to pass in only the requested information you need.

use App\Service;

$this->app->bind(Service::class, function ($app) {
    return new Service($app['request']);
});

$this->app->singleton(Service::class, function ($app) {
    return new Service(fn () => $app['request']);
});

// Or...

$service->method($request->input('name'));

Readers are strongly recommended to read the official cautions. If you find the article helpful, you can also subscribe to the author’s blog RSS feed or go directly to the author’s blog chat.

reference

  • Upgrade to PHP 8 with Homebrew on Mac https://stitcher.io/blog/php-…
  • Laravel Octane https://github.com/laravel/oc…
  • Laravel Sail https://github.com/laravel/sail
  • The relationship between the FastCgi with PHP – FPM https://godruoyi.com/posts/th…
  • PHP-FPM vs Swoole https://developpaper.com/php-…
  • Swoole Programming Notes https://wiki.swoole.com/#/get…
  • The original address https://godruoyi.com/posts/la…