• Время чтения ~2 мин
  • 10.07.2022

Однажды утром я проснулся от уведомлений Slack. Это плохой знак.

За ночь мой экземпляр Redis был полностью заполнен. Мы используем Redis для двух целей:

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

Я использовал TablePlus, чтобы посмотреть, что можно сделать в Redis. Немного сложно сказать, что происходит, поскольку Laravel использует случайные хэши как часть ключей кэша, а полезная нагрузка кодируется/шифруется.

Однако я увидел две базы данных Redis (db0 и db1). Проверив файл config/databases.php, я обнаружил, что для 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'),
        ],
 
    ],
];

Соединение по умолчанию использует db0, а соединение cache использует db1.Оказывается, сеансы хранятся в db0, а то, что мы кэшируем в коде, использует db1. В базе данных по умолчанию, db0, было НАМНОГО больше ключей, чем в базе данных кэша.

Приложение создавало слишком много сеансов.

Что создает сеанс?

Каждый веб-запрос (для маршрутов, определенных в routes/web.php) создает сеанс (или использует существующий). Веб-приложения возвращают файл cookie при создании сеанса. Веб-браузеры сохраняют эти файлы cookie и отправляют их обратно при выполнении дополнительных веб-запросов. Это позволяет нашим веб-приложениям знать, какой сеанс действителен для данного пользователя.

Если бы браузер не возвращал файл cookie при каждом запросе, пользователь не смог бы оставаться в системе.

Сеансы на основе API работают иначе. Каждый сеанс создается, а затем уничтожается при каждом веб-запросе — файлы cookie не используются. Вместо этого клиенту необходимо отправлять информацию об аутентификации при каждом веб-запросе (обычно это какой-то токен).

Что взорвало Redis?

Итак, что же тогда привело к тому, что наш экземпляр Redis взорвался сеансами?

Динамически генерируемые ресурсы, которые другие пользователи встраивают на свои веб-сайты. У нас было два таких случая:

  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

Эти маршруты были определены в нашем файле routes/web.php:

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

Вы видите проблему? Клиенты помещали их на свои собственные веб-сайты.Каждый раз, когда кто-то посещал их веб-сайт, к нашему приложению отправлялся HTTP-запрос для встраивания или SVG, и это создавало сеанс.

Это означает, что веб-трафик наших клиентов также создавал сеансы в нашем веб-приложении!

Как уменьшить количество сеансов

Исправление заключается в том, что мы не должны создавать сеансы для определенных маршрутов. Достаточно просто сказать, но как этого добиться?

Оказывается, создание файлов cookie и сеансов выполняется в промежуточном программном обеспечении Laravel. Это хорошо, поскольку мы контролируем, какое промежуточное ПО применяется к каждому маршруту.

Чтобы гарантировать, что некоторые маршруты не создают сеансовые/возвратные файлы cookie, я предпочитаю создавать отдельный файл маршрутов с другим стеком промежуточного ПО.

Для этого нам нужно сделать несколько вещей:

  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

Новый файл маршрутов прост — мы создаем новый файл и перемещаем в него наши определения маршрутов:

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

Затем мы можем обновить файл Kernel.php, чтобы создать новый стек промежуточного ПО.Мы можем скопировать стек промежуточного программного обеспечения web и удалить промежуточное программное обеспечение, которое обрабатывает файлы cookie и сеансы:

# 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

Мы создали новую группу ПО промежуточного слоя с именем static. Это похоже на промежуточное ПО API, но у нас нет регулирования.

Наконец, нам нужно зарегистрировать новый файл маршрутов и применить нашу новую группу промежуточного программного обеспечения static.Мы сделаем это, обновив 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 регистрирует каждый файл маршрута и определяет их промежуточное ПО. Вот как каждый объект в routes/web.php получает назначенную ему группу промежуточного программного обеспечения web.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

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...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297