Одного ранку я прокинувся від сповіщень Slack. Це ніколи не є хорошим знаком.
За ніч мій екземпляр Redis повністю заповнився. Ми використовуємо Redis для двох речей:
- Session storage
- 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 із сесіями?
Динамічно створювані об’єкти, які інші вбудовують на свої веб-сайти. У нас було два таких випадки:
- Our application generated a
.js
file that others embedded on their web sites - 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 сеансів/повернення, мені подобається створити окремий файл маршрутів, який має інший стек проміжного програмного забезпечення.
Для цього нам потрібно зробити кілька речей:
- Create a new
routes/static.php
file (the name is arbitrary) - Add a middleware stack to
app/Http/Kernel.php
- 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
, призначену йому.