• Czas czytania ~5 min
  • 28.01.2025

Aby uzyskać jeszcze szybsze zyski z aplikacji Laravel o dużym natężeniu ruchu, możesz buforować uwierzytelnionych użytkowników, aby uniknąć podróży do bazy danych.

W tym artykule pokażę Ci, jak to zrobić — ale to nie jest szybkie rozwiązanie; Będziemy musieli zastanowić się, co się stanie, gdy użytkownik zostanie zaktualizowany lub usunięty.

Zajrzyjmy do pamięci podręcznej.

Wolisz oglądać? Ten artykuł jest dostępny jako darmowy film na Codecourse!

Dla każdej uwierzytelnionej strony lub żądania API w Twojej aplikacji, Laravel pobierze nowego użytkownika z bazy danych za pomocą zapytania podobnego do tego (oczywiście w zależności od identyfikatora):

select * from `users` where `id` = 1 limit 1

Obecnie nie ma możliwości automatycznego buforowania tego obiektu użytkownika. Tak długo, jak użytkownik jest uwierzytelniony, to zapytanie będzie zawsze uruchamiane dla każdego żądania.

Ponieważ jest mało prawdopodobne, aby użytkownik zmieniał się tak często między żądaniami, warto buforować to, dopóki coś się nie zmieni, szczególnie w przypadku aplikacji o dużym natężeniu ruchu.

Pierwszym krokiem jest zrozumienie mechanizmu dostawcy Laravel Auth .

Domyślnie Laravel używa metody EloquentUserProvider do zarządzania uwierzytelnionymi użytkownikami. Ta klasa zawiera wiele pomocnych metod, takich jak retrieveById, rehashPasswordIfRequired i validateCredentials. Zasadniczo wszystko, co jest wymagane do pobrania i zaktualizowania użytkownika w odniesieniu do uwierzytelniania.

Możesz dodać nowego dostawcę, korzystając z Auth::provider metody podobnej do tej:

Auth::provider('someCustomProvider', function (Application $app, array $config) {
    // return a custom provider here
});

Po dodaniu tego dostawcy w czasie wykonywania, możesz go zamienić w config/auth.php pliku konfiguracyjnym:

'providers' => [
    'users' => [
        'driver' => 'someCustomProvider',
        'model' => env('AUTH_MODEL', App\Models\User::class),
    ],

Teraz rozumiemy dostawców uwierzytelniania trochę lepiej, stwórzmy własnych!

Zacznij od utworzenia klasy provider, CachedEloquentUserProvider. Możesz to nazwać jak chcesz lub umieścić w dowolnym miejscu w swojej aplikacji:

namespace App\Auth\Providers;
use Illuminate\Auth\EloquentUserProvider;
class CachedEloquentUserProvider extends EloquentUserProvider
{
    public function retrieveById($identifier)
    {
        //
    }
}

To rozszerza podstawę EloquentUserProvider , aby zapewnić wszystkie dodatkowe funkcje, które chcemy zachować. Zajmujemy się tylko zastąpieniem , retrieveById aby wybrać, w jaki sposób pobieramy użytkownika.

Nie robimy teraz nic, aby pobrać użytkownika (omówimy to dalej), ale podłączmy go do naszego niestandardowego dostawcy w metodzie AppServerProvider's boot ':

public function boot(): void
{
    Auth::provider('cachedEloquent', function (Application $app, array $config) {
        return new CachedEloquentUserProvider(
            $app['hash'],
            $config['model']
        );
    });
}

W konstruktorze dla oryginalnego EloquentUserProvider, musimy przekazać bieżący hasher (odpowiedzialny za haszowanie haseł itp.), a także przestrzeń nazw modelu z konfiguracji, która reprezentuje użytkownika (zazwyczaj App\Models\User).

Dlatego właśnie przeszliśmy przez te dwie rzeczy powyżej.

Teraz przełącz sterownik config/auth.php na cachedEloquent (lub jakkolwiek to nazwałeś).

'providers' => [
    'users' => [
        'driver' => 'cachedEloquent',
        'model' => env('AUTH_MODEL', App\Models\User::class),
    ],

W tym momencie aplikacja nie będzie mogła pobrać uwierzytelnionych użytkowników, ponieważ pozostawiliśmy nową retrieveById metodę pustą.

Nadszedł czas, aby wypełnić retrieveById tę metodę wersją użytkownika zapisaną w pamięci podręcznej.

Opcje tutaj są nieograniczone, ale oto dobry początek:

public function retrieveById($identifier)
{
    return cache()->remember('user_' . $identifier, now()->addHours(2), function () use ($identifier) {
        return parent::retrieveById($identifier);
    });
}

Tutaj $identifier jest tylko identyfikator użytkownika, więc przekazujemy to do metody nadrzędnej retrieveById , aby zrobiła swoje. Ale oczywiście używamy cache()->remember do buforowania i zwracania wyniku.

Oto nieco krótszy sposób na uzyskanie tego samego wyniku za pomocą funkcji strzałkowej PHP:

public function retrieveById($identifier)
{
    return cache()->remember(
        'user_' . $identifier,
        now()->addHours(2),
        fn () => parent::retrieveById($identifier)
    );
}

Każda metoda działa dobrze; zależy to od tego, czy robisz wiele więcej w ramach domknięcia, w którym to przypadku wybrałbyś standardową funkcję wywołania zwrotnego.

Domyślnie Laravel używa bazy danych jako sterownika pamięci podręcznej. Będziesz chciał to zmienić, w przeciwnym razie wrócimy do pobierania wersji użytkownika z pamięci podręcznej z bazy danych... znów.

CACHE_STORE=redis

Gdy to się zmieni, powinno być możliwe zalogowanie się do aplikacji i wyświetlenie początkowego żądania bazy danych dla użytkownika. Jednak po odświeżeniu pobieramy dane użytkownika z naszej pamięci podręcznej (w tym przypadku Redis)!

Buforowanie jest świetne, ale musimy zadbać o zniszczenie (unieważnienie) pamięci podręcznej, gdy coś się zmieni.

Jeśli użytkownik zaktualizuje teraz swoje szczegóły w aplikacji, nie zobaczy tych zmian natychmiast odzwierciedlonych i będzie musiał poczekać na wygaśnięcie pamięci podręcznej. Nie jest to idealne rozwiązanie.

Aby obejść ten problem, możemy po prostu obserwować User zmiany i ręcznie unieważnić pamięć podręczną.

Zacznij od utworzenia UserObserver:

php artisan make:observer UserObserver

Open it up i zarejestruj zdarzenia dla updated i deleted.

class UserObserver
{
    public function updated(User $user)
    {
        cache()->forget('user_' . $user->id);
    }
    public function deleted(User $user)
    {
        cache()->forget('user_' . $user->id);
    }
}

Nasz klucz pamięci podręcznej został ustawiony na user_[id], więc po prostu unieważniamy ten klucz.

Zarejestruj obserwatora w User modelu i gotowe:Gdy użytkownicy zmienią się lub zostaną usunięci, pamięć podręczna zostanie unieważniona, a my skończymy na buforowaniu nowych danych (lub po prostu usunięciu ich całkowicie,

use App\Observers\UserObserver;
#[ObservedBy(UserObserver::class)]
class User extends Authenticatable
{
    //...
}

jeśli użytkownik zostanie usunięty).

Ważne jest, aby pamiętać, że jest to bardzo uproszczony pogląd na unieważnianie danych użytkownika w pamięci podręcznej za pomocą dwóch zdarzeń Eloquent. W rzeczywistości, gdy aplikacja staje się coraz bardziej złożona, mogą wystąpić przypadki brzegowe, w których albo nie chcesz unieważnić pamięci podręcznej, albo pamięć podręczna nie zostanie unieważniona, gdy chcesz.

Na przykład:

  • Jeśli używasz fasady DB w dowolnym miejscu aplikacji do aktualizowania użytkownika, pamięć podręczna nie zostanie unieważniona, ponieważ zdarzenie Eloquent nie zostanie wyzwolone
  • Jeśli dane w bazie danych zostaną zaktualizowane ręcznie, pamięć podręczna nie zostanie unieważniona
  • Jeśli kolumna niepubliczna (np. email_verified_at) dla Twojego użytkownika zostanie zaktualizowana, spowoduje to unieważnienie pamięci podręcznej, ale możesz tego nie chcieć.
  • Jeśli masz zadanie w kolejce (na przykład), które regularnie aktualizuje użytkownika (np. kiedy ostatnio wysłał żądanie), będzie to stale unieważniać pamięć podręczną.

Jak powiedziałem na początku artykułu, nie jest to szybkie i łatwe rozwiązanie, które nie wymaga przemyślenia.

Tak, przyspieszy to Twoją aplikację. Mimo to wymaga to trochę więcej wysiłku z Twojej strony, aby upewnić się, że pamięć podręczna nie zwraca starych danych lub nie unieważniasz pamięci podręcznej zbyt często i nie sprawiasz, że uwierzytelnione buforowanie jest bezużyteczne.

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

Codziennie zestrzeliwujemy „Shahedy”. Za każdym razem to uratowane życie. Potrzebujemy mobilności: busa lub przyczepy. Każda wpłata = kolejna noc pod ochroną.

🚐 Zbiórka na busa dla zespołu, pułk 1020 🎯 Cel: 500 000 ₴
🔗 Link do zbiórkiі 💳 4441 1111 2546 4663