Laravel Octane has been around for a few weeks now, and while it’s still in beta, developers love it. In less than a month, it has more than 2K stars on GitHub. Some developers have run their projects on Laravel Octane.

If you’re still on the fence, wait a week or two for the stable version.

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 tried out a simple H5 project in the production environment, except for some messy problems, the author was very excited, the customer also said that our platform is so fast, I will call you next time.

Composition of Laravel Octane

Laravel Octane is built with two high-performance application services: Swoole and RoadRunner, as described in the official documentation:

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

We know that Laravel frameworks have always been great, but they’ve always been a bit of a performance problem. The boot time of the framework may be longer than the service processing time, and the startup speed is increasingly uncontrolled as the number of third-party service providers in the project increases. Laravel Octane speeds up our Application by launching the Application once and staying in memory.

Laravel Octane requires PHP8.0 support. If you are working on macOS, you can refer to this article to Upgrade your PHP version to PHP8 with Homebrew on Mac.

Octane is simply shown as column

Although the official documentation is quite detailed, the author demonstrates this with a simple list item.

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.Copy the code

Install Laravel Octane

$ composer require laravel/octane
Copy the code

After the installation is successful, the reader can directly run artisan Octane: Install to install the dependency; Octane will prompt you for the type of server you want to use.

➜ PHP artisan Octane: Install Which application Server you would like to use? : [0] roadrunner [1] swoole >Copy the code

If you select RoadRunner, the program will automatically install RoadRunner dependencies for you; If you choose Swoole, you just need to make sure you have installed the PHP Swoole extension manually.

Use the RoadRunner Server

The use of RoadRunner was not satisfactory. The author always made some mistakes in the installation process that were ignored by the official documents.

Failed to download the RR executable. Procedure

Octane :install install RoadRunner dependency: octane:install RoadRunner dependency: octane:install RoadRunner dependency

In CommonResponseTrait.php line 178:

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

If you encounter this error, go to the RoadRunner website to download the rr executable and.rr.yaml configuration file and put it in the root directory of your project. For example, macOS platform executables and configuration file addresses:

  • Github.com/spiral/road…
  • Github.com/spiral/road…

Finally remember to modify the rr executable permission and the RoadRunner Worker starting Command.

chmod +x ./rr
Copy the code
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"
Copy the code

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

The RoadRunner configuration file has SSL enabled by default. If you do not want to enable HTTPS access, you can comment http. SSL configuration.

Error while Dialing TCP 127.0.0.1:7233

RoadRunner enables temporal feature by default, and its LISTEN port is 7233. If you do not want to enable this feature, you can comment temporal configuration.

# Drop this section for temporal feature disabling.
temporal:
Copy the code

Information about temporal PHP can be found on temporalio/ SDK-php: temporal PHP SDK

Executable file not found in $PATH

In this case, the program execution path is not specified in the configuration file. Please check the following configuration.

  1. Server.command

Modify to RoadRunner worker’s startup command, for example:

PHP artisan octane:start -- server= Roadrunner -- host=127.0.0.1 -- port=8000Copy the code
  1. Service.some_service_*.comment

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

AB Test For RoadRunner

The author did a simple AB Test on his own notebook (2018-13inch/ 2.3ghz /16GB) with the framework code unchanged for Laravel’s default Welcome page.

After changing different concurrent parameters and number of requests, the results are slightly fluctuated as shown in the figure below, and their QPS basically remain around 230/s.

➜ ~ ab-n 2000-C 8 http://127.0.0.1:8000/ 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: 33.671 [ms] (mean Time per request: 33.671) [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: 3 20 34.8 15 270 Waiting: 3 18 34.8 12 270 Total: 7 31 35.2 25 284Copy the code

By default, Laravel’s Welcome page passes through the Web middleware and ends up on the rendered Blade page; Web middleware contains a lot 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,
    ],
];
Copy the code

So the author redefines a test route that does not contain any middleware (except global) 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';
});
Copy the code

The next time you test it again, you can see that its QPS has reached the advertised level of 2300/s. .

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: 0 Total transferred: 374000 bytes HTML transferred: 22000 bytes Requests per second: 2307.81 [#/ SEC] (mean Time per request: 3.466 [ms] (mean Time per request: 3.466) 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 143Copy the code

During the above tests, the author’s native resource limitations are as follows.

~ ulimit -n
256
Copy the code

Using Swoole Server

Swoole Server is much smoother to use; Once the PHP swoole extension is installed via PECl, it launches without any configuration.

AB Test For Swoole Server

The author uses the same configuration to conduct AB Test on Swoole Server, and the results are as follows: its QPS is basically maintained at about 230/s.

Server Software: 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: 33.592 [ms] (mean Time per request: 33.592) [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: 4 20 50.3 12 442 Waiting: 2 18 50.3 11 441 Total: 7 30 50.9 23 450Copy the code

The test result of no middleware routing is as follows, and it can be seen that its QPS has reached 1650/s.

Server Software: 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: 0 Total transferred: 528000 bytes HTML transferred: 42000 bytes Requests per second: 1650.63 [#/ SEC] (mean Time per request: 4.847 [ms] (mean Time per request: 4.847) 0.606 [MS] (mean, across all concurrent requests) Transfer rate: 425.55 [Kbytes/ SEC] ReceivedCopy the code

AB Test results show that the performance of the two servers is basically the same. However, as it is tested in the local development environment, many factors are not considered, so the test results are for reference only.

The deployment of online

Laravel Octane provides the start command to start the Server, but this command can only be run in the foreground (-d is not supported); When deployed to production, it is common to use The Supervisor for process management. Refer to the Supervisor configuration for Laravel Sail.

[program: PHP] command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan 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=0Copy the code

For subsequent continuous delivery, Jenkins can connect to the service node and reload the service using the octane:reload command.

Stage (" Deploy ${IP}") {withCredentials([sshUserPrivateKey(credentialsId: env.HOST_CRED, keyFileVariable: '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" } }Copy the code

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

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

Otherwise an error like Class “Godruoyi\Snowflake\Snowflake” not found may occur.

Is Laravel Octane thread-safe?

Before we answer that question, let’s take a look at Laravel Octane’s request processing flow.

As the Server starts, the program creates a specified number of Worker processes. When a request comes in, one of the available workers is selected and handed over to him. Each Worker can only handle one request at a time. During the process of request processing, there is no competition between the modification of resources (variables/static variables/file handles/links), so the thread (process) is safe when Laravel Octane is used.

This is the same as the FPM model. The difference is that the FPM model destroys all memory requested by a request after processing it. When subsequent requests arrive, complete PHP initialization is still performed (see phP-FPM startup analysis). However, the initialization operation of Laravel Octane is carried out with Worker Boot, and only one initial operation (when the program is started) will be carried out in the whole life cycle of Worker. Subsequent requests will simply reuse the original resource. As shown in the figure above, after Worker Boot is complete, Laravel Application Container will be initialized, and all subsequent requests will reuse the App instance.

Laravel Octane works

Octane is just a shell, and the actual processing of requests is handled by the external Server. But Octane’s design is worth mentioning.

It can also be seen from the source code that Laravel Application has been successfully initialized with the completion of Worker’s 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));
}
Copy the code

Octane gets a sandbox container by clone $this->app when processing subsequent incoming requests. All subsequent operations are based on the sandbox Container and do not affect the original Container. At the end of the request, Octane empties the sandbox and unsets objects that are no longer in use.

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); }}Copy the code

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

Precautions & Third-party package adaptation

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

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

In this example, the singleton is used to register a singleton object Service. When the object is initialized in a Provider’s Boot method, the application container will always keep a unique Service object. Subsequent requests processed by the Worker will obtain the same request object from the Service.

The solution is that you can bind differently, or use closures. The most recommended approach is to only pass in 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'));
Copy the code

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 directly visit the author’s blog.

reference

  • Upgrade to PHP 8 with Homebrew on Mac Stitcher. IO /blog/ phP-8 -…
  • Laravel Octane github.com/laravel/oct…
  • Laravel Sail github.com/laravel/sai…
  • The relationship between FastCgi and phP-fpm godruoyi.com/posts/the-r…
  • PHP – FPM vs Swoole developpaper.com/php-fpm-vs-…
  • Swoole Programming instructions wiki.swoole.com/#/getting_s…
  • Original address godruoyi.com/posts/larav…