• Час читання ~2 хв
  • 10.07.2022

Одного ранку я прокинувся від сповіщень Slack. Це ніколи не є хорошим знаком.

За ніч мій екземпляр Redis повністю заповнився. Ми використовуємо Redis для двох речей:

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

Я використовував TablePlus, щоб побачити, що я можу в Redis. Трохи важко зрозуміти, що відбувається, оскільки Laravel використовує випадкові хеші як частину ключів кешу, а корисне навантаження закодовано/зашифровано.

Однак я бачив, що було 2 бази даних Redis (db0 і db1). Перевіривши файл config/databases.php, я виявив, що для Redis справді визначено 2 відповідні бази даних.

# 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