3

Laravel Octane

Laravel Octane has been released for several weeks. Although it is still in beta status, it cannot stop the developers’ love for him. In less than one month, the number of stars on GitHub has exceeded 2K; some developers The project runs on Laravel Octane.

If you are still on the sidelines, you can also wait for the stable version in a week or two (the original version was released on 2021-04-29, and currently (05-27) has been released).

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 tested the water with a simple H5 project in a production environment. Except for some messy problems, the author is very excited. The customer also said that our platform is so fast. I'm looking for you again.

Composition of Laravel Octane

Laravel Octane has two built-in high-performance application services: Swoole and RoadRunner , as the official document introduces:

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

We know that the Laravel framework has always been excellent, but it has been criticized in terms of performance. The boot time of the framework may be longer than the business processing time, and with the increase of the third-party service providers of the project, its startup speed becomes more and more uncontrolled. And Laravel Octane accelerates our application by starting Application once and resident in memory.

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

Octane simple example

Although the official documents have been described in detail, the author still demonstrates it through a simple demonstration project.

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

After the installation is successful, readers can directly execute artisan octane:install to install 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 install the dependencies required by RoadRunner; and if you choose Swoole, you only need to make sure that you have manually installed the PHP swoole extension.

Use RoadRunner Server

The use of RoadRunner is unsatisfactory, and the author will always make some mistakes that the official documents ignore during the installation process.

Failed to download rr executable file

When executing octane:install install the RoadRunner dependency, the author cannot download the rr executable file through GitHub at all on his own machine, and the error prompted 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 such an error, it is recommended to go directly to RoadRunner official website download the rr executable file and .rr.yaml configuration file of the corresponding platform and put it in the project root directory. Such as the executable file and configuration file address of the macOS platform:

Finally, remember to modify the executable permissions of rr and the 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

In RoadRunner's configuration file, the ssl configuration is enabled by default. If you don't need to enable https access, you can comment the http.ssl configuration.

Error while dialing dial tcp 127.0.0.1:7233

RoadRunner enables the temporal feature by default, and its listen port is 7233. If you don't want to enable this feature, you can annotate the temporal configuration.

# Drop this section for temporal feature disabling.
temporal:
For information about temporal, please check the official website temporalio/sdk-php: Temporal PHP SDK

Executable file not found in $PATH

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

  1. Server.command

Modify the startup command of 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 this feature, comment the configuration. At this point, the author's RoadRunner finally started

Laravel Octane RoadRunner

AB Test For RoadRunner

The author used his laptop (2018-13inch/2.3GHz/16GB) to make a simple AB Test, without any changes to the framework code, it is Laravel's default welcome page.

After changing different concurrent parameters and the number of requests, the results obtained fluctuate slightly as shown in the figure below, and the QPS is basically maintained at about 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:       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:     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 will go through the web middleware first, and finally render the blade page; while the web middleware contains a large number of Cookie 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, which does not contain any middleware (except global), and only outputs a Hello World.

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

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

After testing again as follows, you can see that its QPS has reached the official publicity standard of 2300/s (Is the official test also the same as Remove All Middleware?).

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

In the above test process, the author's local resource limits are as follows.

~ ulimit -n
256

Use Swoole Server

The use of Swoole server will be much smoother; after installing the PHP swoole extension via pecl, it can be started without any configuration.

Laravel Swoole

AB Test For Swoole Server

The author uses the same configuration to perform AB Test on the swoole server, and the results are as follows, and 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:       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:     4   20  50.3     12     442
Waiting:        2   18  50.3     11     441
Total:          7   30  50.9     23     450

The results of the middleware-free routing test are as follows, and you can see 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:       0.606 [ms] (mean, across all concurrent requests)
Transfer rate:          425.55 [Kbytes/sec] received

From the results of AB Test, the performance of the two servers is basically the same; however, because it is tested in a local development environment, there are many factors that have not been taken into consideration, and the test results are for reference only.

Deploy online

Although Laravel Octane provides the start command to start the Server, this command can only be run in the foreground (-d is not supported); when deploying to a production environment, the common method is to use Supervisor for process management. Readers can refer to the Supervisor configuration of 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=0

In the subsequent continuous delivery, you can connect to the service node through Jenkins and use the octane:reload command to reload the service.

stage("部署 ${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"
    }
}

However, it should be noted here that when you update Composer dependencies, such as adding a third-party package, you'd better restart Laravel Octane in the production environment.

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

Otherwise, errors such as Class "Godruoyi\Snowflake\Snowflake" not found may occur.

Is Laravel Octane thread safe?

Before answering this question, let's take a look at Laravel Octane's request processing flow.

Laravel Octane

As the Server starts, the program will create a specified number of Worker processes. When the request comes, one will be selected from the list of available Workers and handed over to him for processing. Each Worker can only process one request at the same time. In the process of request processing, there is no competition for the modification of resources (variables/static variables/file handles/links), so the thread (process) is safe in Laravel Octane.

This is actually consistent with the FPM model. The difference is that after processing a request, the FPM model destroys all the memory requested by the request; when subsequent requests come, the complete PHP initialization operation is still performed (refer to PHP-FPM Start analysis ). The initialization operation of Laravel Octane is carried out with Worker Boot. During the entire Worker's life cycle, only one initial operation (when the program is started) will be carried out. Subsequent requests will directly reuse the original resources. As shown in the figure above, after 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, and the actual processing request is handled by an external server. But Octane's design is worth talking about.

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

// 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));
}

When processing subsequent requests, Octane obtains a sandbox container clone $this->app All subsequent operations are based on this sandbox container and will not affect the original Container. After the request is over, Octane will empty the sandbox container and unset 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);
    }
}
Note again that since the same Worker process can only process one request at the same time, there is no competition here. Even the modification of static variables is safe.

Note & third-party package adaptation

Since multiple requests of the same Worker will share the same container instance, you should be especially careful when registering singleton objects in the container. Such as the following example:

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

In the example, a singleton is used to register a singleton object Service. When the object is initialized in the Boot method of a Provider, the application container will always maintain a unique Service object; subsequent Workers will obtain it from the Service when processing other requests. The request object will be the same.

The solution is that you can change the binding method, or use closures. The most recommended way is to pass in only the request 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'));

It is strongly recommended that readers read the official precautions . If you think the article is helpful to you, you can also subscribe to the author’s blog RSS or directly visit the author’s blog to talk about miscellaneous fish .

reference


godruoyi
2.9k 声望319 粉丝

二楞徐的闲谈杂鱼