Система asset-ов Yii 2 очень удобна. Можно расположить ресурсы в пакетах или ближе к исходникам и фреймворк при первом запросе на генерируемую PHP страницу скопирует их (или сделает симлинк) в нужное место.
Пока экземпляр приложения один, всё работает замечательно, а вот когда появляется балансировщик, это уже не работает потому как запрос каждый раз уходит на разный сервер. Проблем тут несколько. Yii, чтобы избавить от залипания браузерного кеша, при выборе директории, в которую копируется ресурс, использует время изменения директории:
$path = (is_file($path) ? dirname($path) : $path) . filemtime($path);
return sprintf('%x', crc32($path . Yii::getVersion() . '|' . $this->linkAssets));
На каждом экземпляре приложения время будет разным, поэтому ресурсы окажутся в разных директория и если HTML будет запрошен с одного сервера, а ресурсы с других, получим 404.
Решается просто. В конфиге задаём hashCallback
для компонента assetManager
:
'hashCallback' => static function ($path) {
return hash('md4', $path);
}
Но до конца это проблему не решает. Теперь пути совпадают, но при первых запросах получается что в HTML прописан путь к ресурсу с другого сервера, а там ещё запросы не обрабатывались и такого ресурса нет. Опять 404.
Бороться с этим можно несколькими способами. Первый сводится к тому, что ресурсы собираются на машине разработчика или билд-сервере и раскатываются вместе с приложением. Вариаций тут много:
- Отказаться от asset manager, использовать файлы в вебруте.
- Использовать webpack или другую систему сборки на node.
- Собирать ресурсы командой
asset
.
Второй способ — сделать ресурсы общими. Здесь опять несколько вариаций:
- Использовать NFS чтобы сделать файловую систему ресурсов общей. Но это не очень быстро и надёжно.
- Использовать CDN. Например, ресурсы закинуть на S3 и раздавать оттуда. Заодно получим снижение нагрузки на серверах приложений.
Последний вариант делается при помощи пакета mikk150/yii2-asset-manager-flysystem
. Ставим, настраиваем:
'assetManager' => [
'class' => mikk150\assetmanager\AssetManager::class,
'basePath' => './',
'baseUrl' => 'Базовый URL статического контента',
'flySystem' => [
'class' => creocoder\flysystem\AwsS3Filesystem::class,
'host' => 'Хост статического контента',
'key' => 'Ключик S3',
'secret' => 'Secret S3',
'region' => 'Регион S3',
'version' => 'Версия файлов',
'bucket' => 'Bucket',
'prefix' => 'Путь к ресурсам' . '/assets',
],
],