• Час читання ~4 хв
  • 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, а з'єднання кеш використовує 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');

Ви бачите проблему? Клієнти розміщували їх на власних веб-сайтах.Щоразу, коли хтось відвідував їхній веб-сайт, до нашої програми для вбудовування або SVG надходило HTTP-запит, і створювався сеанс.

Це означає, що веб-трафік наших клієнтів також створював сеанси в нашій веб-програмі!

Як скоротити створення сеансу

Виправлення полягає в тому, що переконайтеся, що ми не створюємо сеанси для певних маршрутів. Досить просто сказати, але як нам цього досягти?

Виявилося, що створення файлів 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, щоб створити новий стек проміжного програмного забезпечення.Ми можемо скопіювати стек проміжного програмного забезпечення веб і видалити проміжне програмне забезпечення, яке обробляє файли 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