• Czas czytania ~5 min
  • 10.07.2022

Pewnego ranka obudziły mnie powiadomienia ze Slacka. To nigdy nie jest dobry znak.

Z dnia na dzień moja instancja Redis całkowicie się zapełniła. Używamy Redisa do dwóch rzeczy:

  1. Session storage
  2. Caching a few bits of data, nothing substantial

Użyłem TablePlus, aby zobaczyć, co mogę w Redis. Trudno powiedzieć, co się dzieje, ponieważ Laravel używa losowych skrótów jako części kluczy pamięci podręcznej, a ładunki są kodowane/szyfrowane.

Jednak zauważyłem, że istnieją 2 bazy danych Redis (db0 i db1). Sprawdzając plik config/databases.php, odkryłem, że 2 odpowiednie bazy danych zostały rzeczywiście zdefiniowane dla Redis.

# File config/databases.php
return [
    // Things ommitted here...
 
 
    /*
    |--------------------------------------------------------------------------
    | Redis Databases
    |--------------------------------------------------------------------------
    |
    | Redis is an open source, fast, and advanced key-value store that also
    | provides a richer body of commands than a typical key-value system
    | such as APC or Memcached. Laravel makes it easy to dig right in.
    |
    */
 
    'redis' => [
 
        'client' => env('REDIS_CLIENT', 'phpredis'),
 
        'options' => [
            'cluster' => env('REDIS_CLUSTER', 'redis'),
            'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
        ],
 
        'default' => [
            'url' => env('REDIS_URL'),
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', '6379'),
            'database' => env('REDIS_DB', '0'),
        ],
 
        'cache' => [
            'url' => env('REDIS_URL'),
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', '6379'),
            'database' => env('REDIS_CACHE_DB', '1'),
        ],
 
    ],
];

Połączenie domyślne używa db0, podczas gdy połączenie cache używa db1.Okazuje się, że sesje są przechowywane w db0, podczas gdy to, co buforujemy w kodzie, używa db1. Domyślna baza danych, db0, miała DUŻO więcej kluczy niż baza danych pamięci podręcznej.

Aplikacja tworzyła zbyt wiele sesji.

Co tworzy sesję?

Każde żądanie internetowe (dla tras zdefiniowanych w routes/web).php) tworzy sesję (lub używa już istniejącej). Aplikacje internetowe zwracają plik cookie podczas tworzenia sesji. Przeglądarki internetowe przechowują te pliki cookie i wysyłają je z powrotem podczas wysyłania dodatkowych żądań internetowych. Dzięki temu nasze aplikacje internetowe mogą wiedzieć, która sesja jest ważna dla danego użytkownika.

Jeśli przeglądarka nie zwraca pliku cookie przy każdym żądaniu, użytkownik nie będzie mógł pozostać zalogowany.

Sesje oparte na interfejsie API nie działają w ten sposób. Każda sesja jest tworzona, a następnie niszczona w ramach każdego żądania internetowego - nie ma żadnych plików cookie. Zamiast tego klient musi wysyłać informacje uwierzytelniające przy każdym żądaniu sieciowym (zwykle pewnego rodzaju token).

Co wysadziło Redisa?

Co w takim razie spowodowało wysadzenie naszej instancji Redis z sesjami?

Dynamicznie generowane zasoby, które inni osadzają w swoich witrynach. Mieliśmy dwa takie przypadki:

  1. Our application generated a .js file that others embedded on their web sites
  2. Our application also generated .svg images for the same purpose

Te trasy zostały zdefiniowane w naszym pliku routes/web.php:

Route::get('/embed.js');
Route::get('/{project}/share.js');

Czy widzisz problem? Klienci umieszczali je na swoich stronach internetowych.Za każdym razem, gdy ktoś odwiedzał ich witrynę, wysyłano do naszej aplikacji żądanie HTTP dotyczące osadzania lub SVG, co tworzyło sesję.

Oznacza to, że ruch internetowy naszych klientów również tworzył sesje w naszej aplikacji internetowej!

Jak ograniczyć tworzenie sesji

Poprawka polega na upewnieniu się, że nie tworzymy sesji dla niektórych tras. Łatwo powiedzieć, ale jak to zrobić?

Okazuje się, że tworzenie plików cookie i sesji odbywa się w oprogramowaniu pośredniczącym Laravela. To dobrze, ponieważ kontrolujemy, które oprogramowanie pośredniczące jest stosowane do każdej trasy.

Aby upewnić się, że niektóre trasy nie tworzą sesji/zwracanych plików cookie, lubię utworzyć oddzielny plik tras, który ma inny stos oprogramowania pośredniego.

Aby to zrobić, musimy zrobić kilka rzeczy:

  1. Create a new routes/static.php file (the name is arbitrary)
  2. Add a middleware stack to app/Http/Kernel.php
  3. Update app/Providers/RouteServiceProvider.php to load our new route file, and apply our new middleware stack

Nowy plik tras jest prosty — tworzymy nowy plik i przenosimy do niego nasze definicje tras:

# File routes/static.php`
 
# Move these from routes/web.php
Route::get('/embed.js');
Route::get('/{project}/share.js');

Następnie możemy zaktualizować plik Kernel.php, aby utworzyć nowy stos oprogramowania pośredniego.Możemy skopiować stos oprogramowania pośredniego web i usunąć oprogramowanie pośredniczące obsługujące pliki cookie i sesje:

# File app/Http/Kernel.php
  
 # Items omitted here
  
     /**
      * The application's route middleware groups.
      *
      * @var array
      */
     protected $middlewareGroups = [
         'web' => [
             \App\Http\Middleware\EncryptCookies::class,
             \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
             \Illuminate\Session\Middleware\StartSession::class,
             // \Illuminate\Session\Middleware\AuthenticateSession::class,
             \Illuminate\View\Middleware\ShareErrorsFromSession::class,
             \App\Http\Middleware\VerifyCsrfToken::class,
             \Illuminate\Routing\Middleware\SubstituteBindings::class,
         ],
  
         'api' => [
             // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
             'throttle:api',
             \Illuminate\Routing\Middleware\SubstituteBindings::class,
         ],26+ 27+        'static' => [28+            \Illuminate\Routing\Middleware\SubstituteBindings::class,29+        ], 
     ];
  
 # Items omitted here

Utworzyliśmy nową grupę oprogramowania pośredniego o nazwie static. Jest podobny do oprogramowania pośredniczącego API, ale nie mamy dławienia.

Na koniec musimy zarejestrować nowy plik tras i zastosować naszą nową statyczną grupę oprogramowania pośredniego.Zrobimy to, aktualizując RouteServiceProvider.php:

# File app/Providers/RouteServiceProvider.php
  
 # Items omitted here
     /**
      * Define your route model bindings, pattern filters, etc.
      *
      * @return void
      */
     public function boot()
     {
         $this->configureRateLimiting();
  
         $this->routes(function () {
             Route::prefix('api')
                 ->middleware('api')
                 ->namespace($this->namespace)
                 ->group(base_path('routes/api.php'));
  
             Route::middleware('web')
                 ->namespace($this->namespace)
                 ->group(base_path('routes/web.php'));22+ 23+            Route::middleware('static')24+                ->namespace($this->namespace)25+                ->group(base_path('routes/static.php'));26+        }); 
     }
  
 # Items omitted here

RouteServiveProvider rejestruje każdy plik trasy i określa jego oprogramowanie pośredniczące. W ten sposób everyting w routes/web.php otrzymuje przypisaną do niego grupę oprogramowania pośredniego web.

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