• Czas czytania ~9 min
  • 27.06.2023

W typowej konfiguracji stosu LEMP aplikacja Laravel jest uruchamiana przy każdym nowym żądaniu, rejestrowane są powiązania kontenerów, tworzone są nowe instancje, uruchamiane jest oprogramowanie pośredniczące, wywoływane są akcje trasy, a następnie generowana jest odpowiedź, która ma zostać wysłana do przeglądarki.

Podczas uruchamiania aplikacji pod Laravel Octane zmienia się jedna zasadnicza rzecz; Aplikacja jest uruchamiana tylko raz podczas uruchamiania procesów roboczych Octane i to samo wystąpienie będzie używane dla wszystkich żądań.

Aby zrozumieć, jak to działa, przyjrzyjmy się, co się dzieje, gdy uruchamia się proces roboczy Octane:

$app = require BASE_PATH . '/bootstrap/app.php'
$app->bootstrapWith([
    LoadEnvironmentVariables::class,
    LoadConfiguration::class,
    HandleExceptions::class,
    RegisterFacades::class,
    SetRequestForConsole::class,
    RegisterProviders::class,
    BootProviders::class,
]);
$app->loadDeferredProviders();
foreach ($app->make('config')->get('octane.warm') as $service) {
    $app->make($service);
}

Jak widać, instancja aplikacji Laravel jest tworzona podczas uruchamiania procesu roboczego. Ta instancja jest następnie uruchamiana przez wykonanie kilku czynności, takich jak ładowanie plików konfiguracyjnych, rejestrowanie fasad i rejestrowanie dostawców usług.

W tym momencie wszystkie i boot metody dostawców usług nieodroczonych zostały wywołane, co oznacza, że wszystkie register usługi aplikacji są teraz powiązane z kontenerem. Innymi słowy, kontener wie teraz, jak rozwiązać wszystkie powiązania usługi aplikacji. Jeśli zadzwonimy app('config') w tym momencie, kontener będzie wiedział, jak rozpoznać repozytorium konfiguracji.

Następnie wywołanie loadDeferredProviders() metody zapewni, że wszyscy odroczeni dostawcy są również zarejestrowani i uruchomieni. Ponieważ aplikacja uruchamia się tylko raz, odroczenie dostawcy nie przyniesie żadnych korzyści w zakresie wydajności. Octane załaduje je wszystkie, więc wszystkie świadczone przez nich usługi będą powiązane z kontenerem i mogą zostać rozwiązane na każde żądanie.

Wiązania w kontenerze mogą być rejestrowane jako singletony, te specjalne wiązania zostaną rozwiązane tylko raz w życiu aplikacji. Rozpoznane wystąpienia będą przechowywane w pamięci podręcznej kontenera, a te same wystąpienia będą ponownie używane przez cały okres istnienia aplikacji. Zwiększa to wydajność aplikacji, ponieważ nie trzeba konstruować tych wystąpień wielokrotnie za każdym razem, gdy trzeba je rozwiązać z kontenera.

Podczas biegania na Octane należy zwrócić szczególną uwagę na singletony. Ponieważ zostaną one rozwiązane tylko raz, każdy stan przechowywany w tych wystąpieniach będzie trwał tak długo, jak długo działa serwer Octane.

Wystąpienia są rozwiązywane przez wywołanie $app->resolve('singleton') lub $app->make('singleton'). Więc jeśli rozwiążesz wszelkie singletony w swoich dostawcach boot usług lub register metodach. Te singletony będą się utrzymywać. Singletony, które zostaną rozwiązane podczas obsługi żądań, nie będą trwałe, zostaną skonstruowane na każde żądanie podczas pracy pod Octane. Nie musisz się o nie martwić.

W przypadku, gdy chcesz rozwiązać niektóre singletony podczas uruchamiania pracownika i nie rozwiązujesz ich jawnie u usługodawcy, Octane pozwala ustawić szereg usług, aby wstępnie się ogrzały. To właśnie robią ostatnie wiersze kodu w powyższym przykładzie:

foreach ($app->make('config')->get('octane.warm') as $service) {
    $app->make($service);
}

Octane zapętla te usługi i rozwiązuje je za Ciebie. W ten sposób będą one pozostawać w pamięci kontenera tak długo, jak Octane jest uruchomiony.

Obsługa żądań

Teraz, gdy mamy instancję aplikacji z kontenerem usług, który wie, jak rozwiązać wszystkie nasze powiązania, który ma również wstępnie rozwiązane (rozgrzane) singletony, jesteśmy gotowi do obsługi żądań.

Oto jak Octane obsługuje przychodzące żądania:

$server->on('request', function($request) use ($app){
    $sandbox = clone $app;
    Container::setInstance($sandbox);
    $sandbox->make('events')->dispatch(new RequestReceived);
    $response = $sandbox->make(Kernel::class)->handle($request);
    Container::setInstance($app);
    return $response;
});

Octane klonuje oryginalną instancję aplikacji i używa klonu do obsługi przychodzącego żądania. Posiadanie instancji piaskownicy dla każdego żądania pozwala Octane'owi wyczyścić niektóre usługi, które zostały zmutowane podczas obsługi żądania, więc następne żądanie otrzyma stan czysty.

Jak wyjaśniliśmy wcześniej, singletony, które utrzymują się przez cały okres eksploatacji serwera Octane, mogą być przydatne do zwiększenia wydajności. Ale nasze aplikacje nie mogą tolerować mutacji tych przypadków wyciekających między żądaniami. Na przykład usługa może zostać zmutowana podczas żądania, config wywołując coś takiego:

app('config')->set('services.aws.key', AWS_KEY_HERE);

Teraz podczas obsługi następnego żądania services.aws.key klucz konfiguracji będzie nadal zawierał wartość ustawioną przez poprzednie żądanie. Śledzenie tych wycieków może być bardzo trudne, zwłaszcza jeśli zostały zmutowane przez pakiety stron trzecich, które nie zostały zbudowane przez programistę. Z tego powodu Laravel zapewnia aplikacji piaskownicy oddzielną usługę, która jest przycinana po każdym żądaniu, zachowując oryginalną config config usługę niezmienną w oryginalnym wystąpieniu aplikacji.

$sandbox->instance('config', clone $sandbox['config']);

Powyższy kod pokazuje, w jaki sposób Octane nadaje każdej piaskownicy sklonowane wystąpienie oryginalnej config usługi.

Wróćmy teraz do tego, jak Octane obsługuje żądania. Zanim instancja piaskownicy zostanie użyta do obsługi żądania, Octane wywołuje RequestReceived zdarzenie. Octane wykrywa to zdarzenie i wykonuje kilka kroków, aby przygotować wystąpienie piaskownicy do obsługi żądania.

Oto niektóre z ważnych rzeczy, które Octane robi po wywołaniu RequestReceived zdarzenia:

$sandbox->instance('config', clone $sandbox['config']);
$sandbox[Kernel::class]->setApplication($sandbox);
$sandbox['cache']->store('array')->flush();
$sandbox['session']->driver()->flush();
$sandbox['session']->driver()->regenerate();
$sandbox['translator']->setLocale($sandbox['config']['app.locale']);
$sandbox['translator']->setFallback($sandbox['config']['app.fallback_locale']);
$sandbox['auth']->forgetGuards();
$app->instance('request', $request);
$sandbox->instance('request', $request);

Po pierwsze, klonuje czystą kopię repozytorium konfiguracji ze świeżej instancji piaskownicy, która została właśnie sklonowana. Następnie wiąże to świeże repozytorium konfiguracji z aplikacją piaskownicy. Od teraz każda mutacja konfiguracji powinna wpływać tylko na piaskownicę.

Następnie Octane przekazał instancję piaskownicy do kilku usług kontenerowych. W ten sposób, gdy $this->app zostanie wywołany wewnątrz tych usług, zostanie zwrócone wystąpienie piaskownicy, a nie oryginalne wystąpienie aplikacji. Octane robi to dla kilku usług, ale pokazujemy usługę Kernel tylko w naszym przykładzie.

Następnie Octane opróżnia magazyn pamięci podręcznej macierzy i stan sesji, aby nie były utrwalane między żądaniami. Z tego samego powodu ustawia również ustawienia regionalne wewnątrz translatora na oryginalne ustawienia regionalne.

Następnie Octane usuwa wszystkie wystąpienia strażników uwierzytelniania, aby dla każdego żądania były tworzone nowe razy. Jest to bardzo ważne, ponieważ te instancje ochronne buforują uwierzytelnionego użytkownika w nich i musimy ustawić użytkownika dla każdego żądania, ponieważ może to być inny użytkownik.

Na koniec Octane przekazuje przychodzące wystąpienie żądania do oryginalnej instancji aplikacji, a także instancji piaskownicy. Powodem przekazania żądania do oryginalnego wystąpienia aplikacji jest to, że chcemy, aby wszystkie usługi, które zawierają odwołanie do oryginalnego wystąpienia aplikacji, nadal mogły uzyskać dostęp do żądania. Nawet jeśli przekazywanie odwołań do instancji aplikacji jest wysoce odradzane przez Octane, nadal przekazujemy żądanie obsługi przypadków, w których tak się dzieje, dopóki wszystkie aplikacje i pakiety nie będą mogły dostosować swojego kodu.

Rzeczy do rozważenia

Teraz, gdy rozumiemy, w jaki sposób Octane uruchamia naszą aplikację i używa jej do obsługi kilku przychodzących żądań, chcę podkreślić kilka problemów dla programistów aplikacji i opiekunów pakietów:

Przekazywanie instancji aplikacji do usług

Jeśli usługa musi współdziałać z instancją aplikacji i przekazujesz ją w konstruktorze, Upewnij się, że używasz instancji aplikacji przekazanej do wywołania zwrotnego, a nie tej przekazanej do usługodawcy: Powodem jest to,

// Instead of...
$this->app->bind(Service::class, function () {
    return new Service($this->app);
});
// Do...
$this->app->bind(Service::class, function ($app) {
    return new Service($app);
});

że zawiera odniesienie do oryginalnej instancji aplikacji, $this->app ponieważ była ona używana do rejestracji dostawców podczas uruchamiania. Podczas gdy obszar izolowany rozpoznaje usługę, chcesz, aby wystąpienie piaskownicy było przekazywane do konstruktora usługi, a nie do oryginalnego wystąpienia.

Inną opcją jest nieprzekazywanie instancji aplikacji do konstruktora i użycie pomocnika app() wewnątrz usługi, lub Container::getInstance(). Te pomocnicy zawsze będą odwoływać się do instancji piaskownicy.

Przekazywanie instancji aplikacji do singletonów

Nie należy przekazywać wystąpienia aplikacji do pojedynczego tonu, należy przekazać wywołanie zwrotne, które zamiast tego zwraca wystąpienie obszaru izolowanego.

// Instead of...
$this->app->singleton(Service::class, function ($app) {
    return new Service($app);
});
// Do...
$this->app->singleton(Service::class, function ($app) {
    return new Service(fn () => Container::getInstance());
});

Singletons mogą utrzymywać się między żądaniami, co oznacza, że instancja aplikacji przekazana podczas pierwszego rozpoznania singleton będzie używana, gdy usługa jest używana w każdym żądaniu.

Przekazywanie instancji żądania do singletonów

Podobnie jak w przypadku wystąpienia aplikacji, przekaż wywołanie zwrotne:

// Instead of...
$this->app->singleton(Service::class, function ($app) {
    return new Service($app['request']);
});
// Do...
$this->app->singleton(Service::class, function ($app) {
    return new Service(fn () => Container::getInstance()['request']);
});

Lub użyj pomocnika request() wewnątrz usługi i nie wstrzykuj żądania jako zależności.

Przekazywanie instancji repozytorium konfiguracji do singletonów Podobnie jak w dwóch powyższych przypadkach, zamiast tego należy przekazać wywołanie zwrotne lub użyć config() helper:

Tylko singletony

// Instead of...
$this->app->singleton(Service::class, function ($app) {
    return new Service($app['config']);
});
// Do...
$this->app->singleton(Service::class, function ($app) {
    return new Service(fn() => Container::getInstance()['config']);
});

Tylko singletony

, które zostaną rozwiązane podczas ładowania aplikacji, będą zachowywane między żądaniami. Singletony, które zostaną rozwiązane podczas obsługi żądania, zostaną zarejestrowane w kontenerze piaskownicy, ten kontener zostanie zniszczony po obsłużeniu żądania.

Aby utrwalić singletony między żądaniami, możesz rozwiązać je u swoich dostawców usług lub dodać je do tablicy wewnątrz pliku konfiguracyjnego Octane:Z drugiej strony, jeśli masz pakiet, który rejestruje i rozwiązuje singleton wewnątrz usługodawcy i chcesz opróżnić tę instancję przed każdym żądaniem, dodaj ją do tablicy warm flush wewnątrz pliku konfiguracyjnego:

'warm' => [
    ...Octane::defaultServicesToWarm(),
    Service::class
],

'flush' => [
    Service::class
],

Octane usunie te singletony z pojemnika po obsłużeniu każdego żądania.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297