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!
Tło
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.
Jak działają dostawcy uwierzytelniania w Laravelu
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),
],
Tworzenie niestandardowego dostawcy uwierzytelniania
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ą.
Zwracanie obiektu użytkownika zapisanego w pamięci podręcznej
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.
Zmień sterownik pamięci podręcznej!
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)!
Niszczenie pamięci podręcznej, gdy coś się zmieni
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).
Buforowanie jest trudne
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
DBw 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.