First experience of laravel octane

Time:2021-7-16

First experience of laravel octane

Laravel octane has been released for several weeks. Although it is still in beta status, it can’t stop developers from loving him. In less than a month, the number of stars in GitHub has exceeded 2K; partdeveloperThey have run their project on laravel octane.

If you’re still waiting, you can 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 a simple H5 project in the production environment. In addition to some messy problems, other things excite the author. The customer also said that our platform is so fast. I’ll call you next time.

Composition of laravel octane

Laravel octane has two built-in high-performance application services:SwooleandRoadRunnerAs described in the official document:

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 always been criticized for its 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. Laravel octane accelerates our application by starting application once and staying in memory.

Laravel octane needs php8.0 support. If you work under Mac OS, you can refer to this article to update your PHP versionUpgrade to PHP 8 with Homebrew on Mac

Octane simple list

Although the official document has described it in detail, the author still demonstrates it through a simple listing 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, the reader can execute it directlyartisan octane:installTo install dependencies; 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
 >

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

Using Roadrunner server

The process of using Roadrunner is not satisfactory. The author always makes some mistakes ignored by the official documents during the installation process.

Failed to download RR executable

In executionoctane:installWhen installing Roadrunner dependency, the author can’t download RR executable file through 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 such mistakes, it is recommended to go directlyRoadrunner websiteDownload the RR executable file and. RR. Yaml configuration file of the corresponding platform and put them in the root directory of the project. Such as Mac OS platform executable file and configuration file address:

Finally, remember to modify RR’s executable permissions and Roadrunner’s worker starting command.

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, SSL configuration is enabled by default. If you don’t need to enable HTTPS access, you can comment http.ssl configuration.

Error while dialing dial tcp 127.0.0.1:7233

Roadrunner turns on the temporary feature by default, and its listen port is 7233. If you don’t want to enable this feature, you can comment on the temporary configuration.

# Drop this section for temporal feature disabling.
temporal:

Information about temporary can be found on the official websitetemporalio/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 the start 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. So far, the author’s RoadrunnerfinallyIt’s up.

First experience of laravel octane

AB Test For RoadRunner

The author uses his notebook (2018-13inch / 2.3ghz / 16GB) to do a simple AB test. The framework code has not been changed. It is the default welcome page of laravel.

After changing different concurrency parameters and requests, the results are as shown in the figure below. The QPS is 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, the welcome page of laravel will first go through the web middleware and then render the blade page; Web middleware contains a lot 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 another test, we can see that its QPS has reached the official propaganda standard of 2300 / S (is it the same with the official test to 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

During the above testing, the resource limitations of the author’s own machine are as follows.

~ ulimit -n
256

Using spool server

The use of spool server is much smoother; After installing the PHP spool extension through PECL, it can be started without any configuration.

First experience of laravel octane

AB Test For Swoole Server

The author uses the same configuration to do ab test on the spool server, and the results are as follows. The QPS of the spool server is 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 test results of no middleware routing are as follows. We 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 AB test results, the performance of the two servers is basically the same; But because it is tested in the local development environment, many factors are not considered, and the test results are only for reference.

Deployment Online

Laravel octane provides the start command to start the server, but the command can only be run in the foreground (does not support – D); When deploying to a production environment, a common approach is to useSupervisorFor process management. Readers can refer to itLaravel SailThe supervisor configuration of.

[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 subsequent continuous delivery, you can connect to the service node through Jenkins and use theoctane:reloadCommand to reload the service.

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"
    }
}

However, it should be noted that when you update the composer dependency, 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 the request processing flow of laravel octane.

First experience of laravel octane

As the server starts, the program creates a specified number of worker processes. When the request arrives, it selects one from the list of available workers and hands it to him for processing. Each worker can only process one request at a 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 when laravel octane is used.

In fact, this is consistent with the FPM model. The difference is that after processing a request, the FPM model will destroy all the memory of the request; When the subsequent request comes, you still need to perform the complete PHP initialization operation (refer toAnalysis of php-fpm startup)。 The initialization operation of laravel octane is carried out with the worker boot. In the whole life cycle of the worker, only one initial operation will be carried out (when the program is started). Subsequent requests will directly 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 real processing requests are processed by the external server. But octane’s design is worth mentioning.

It can also be seen from the source code that with the completion of worker’s 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 passes theclone $this->appGet a sandbox container. All subsequent operations are based on this 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 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);
    }
}

Again, since the same worker process can only process one request at the same time, there is no competition here, and even the modification of static variables is safe.

Notes & third party package adaptation

Since multiple requests from the same worker share the same container instance, you should be very careful when registering singleton objects with the container. For example:

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

In the example, 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; When subsequent workers process other requests, the request object obtained from the service 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 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 officialmatters needing attention。 If you think the article will help you, you can also subscribe to the author’s blogRSSOr visit the author’s blog directlyTwo Leng’s chatting fish

reference resources

Recommended Today

How to do Vue cross page communication

In Vue projects, there are many ways for components to communicate with each other, such as props/$emit, $on/$emit, vuex and ref communication. There are so many ways to communicate across components, but what should we do when the requirements require us to communicate across pages? Since each page is an independent Vue instance, there is […]