如何使用托管数据库和对象存储设置可扩展的Laravel6应用程序
介绍
在水平扩展 Web 应用程序时,您通常会面临的第一个困难是处理文件存储和数据持久性。 这主要是由于多个应用节点之间的变量数据难以保持一致性; 必须制定适当的策略,以确保在一个节点中创建的数据可以立即用于集群中的其他节点。
解决一致性问题的一种实用方法是使用托管数据库和对象存储系统。 第一个将数据持久性外包给托管数据库,后者将提供远程存储服务,您可以在其中保存静态文件和可变内容,例如用户上传的图像。 然后,每个节点都可以在应用程序级别连接到这些服务。
下图演示了如何在 PHP 应用程序的上下文中使用这种设置来实现水平可伸缩性:
在本指南中,我们将更新现有的 Laravel 6 应用程序,通过将其连接到托管的 MySQL 数据库并设置与 S3 兼容的对象存储来保存用户生成的文件,为水平可扩展性做好准备。 最后,您将在 Nginx + PHP-FPM Web 服务器上运行一个旅行列表应用程序:
注意:本指南使用 DigitalOcean Managed MySQL 和 Spaces 来演示使用托管数据库和对象存储的可扩展应用程序设置。 此处包含的说明应该以类似的方式适用于其他服务提供商。
先决条件
要开始本教程,您首先需要以下先决条件:
- 以具有 sudo 权限的非 root 用户身份访问 Ubuntu 18.04 服务器,并在您的服务器上安装活动防火墙。 要设置这些,请参阅我们的 Ubuntu 18.04 初始服务器设置指南。
- 在您的服务器上安装和配置 Nginx 和 PHP-FPM,如 如何在 Ubuntu 18.04 上安装 LEMP 的步骤 1 和 3 中所述。 您应该跳过安装 MySQL 的步骤。
- Composer 已安装在您的服务器上,如 如何在 Ubuntu 18.04 上安装和使用 Composer 的步骤 1 和 2 中所述。
- 托管 MySQL 8 数据库的管理员凭据。 对于本指南,我们将使用 DigitalOcean Managed MySQL 集群,但此处的说明应该同样适用于其他托管数据库服务。
- 一组对与 S3 兼容的对象存储服务具有读写权限的 API 密钥。 在本指南中,我们将使用 DigitalOcean Spaces,但您可以自由使用您选择的提供商。
s3cmd
工具已安装并配置为连接到您的对象存储驱动器。 有关如何为 DigitalOcean Spaces 进行设置的说明,请参阅我们的 产品文档 。
第 1 步 — 安装 MySQL 8 客户端
默认的 Ubuntu apt 存储库附带 MySQL 5 客户端,它与我们将在本指南中使用的 MySQL 8 服务器不兼容。 要安装兼容的 MySQL 客户端,我们需要使用 Oracle 提供的 MySQL APT Repository。
首先在 Web 浏览器中导航到 MySQL APT 存储库页面 。 找到右下角的下载按钮,点击进入下一页。 此页面将提示您登录或注册 Oracle Web 帐户。 您可以跳过它,而是查找显示 不,谢谢,开始我的下载 的链接。 复制链接地址并返回您的终端窗口。
此链接应指向 .deb
包,它将在您的服务器中设置 MySQL APT 存储库。 安装后,您将能够使用 apt
安装最新版本的 MySQL。 我们将使用 curl
将此文件下载到临时位置。
转到服务器的 tmp
文件夹:
cd /tmp
现在下载带有 curl
的软件包,并使用您从 MySQL APT 存储库页面复制的 URL:
curl -OL https://dev.mysql.com/get/mysql-apt-config_0.8.13-1_all.deb
下载完成后,可以使用dpkg
安装包:
sudo dpkg -i mysql-apt-config_0.8.13-1_all.deb
您将看到一个屏幕,您可以在其中选择要选择的默认 MySQL 版本,以及您感兴趣的 MySQL 组件:
您无需在此处更改任何内容,因为默认选项将安装我们需要的存储库。 选择“确定”,配置完成。
接下来,您需要使用以下命令更新您的 apt
缓存:
sudo apt update
现在我们终于可以安装 MySQL 8 客户端了:
sudo apt install mysql-client
该命令完成后,检查软件版本号以确保您拥有最新版本:
mysql --version
你会看到这样的输出:
Outputmysql Ver 8.0.18 for Linux on x86_64 (MySQL Community Server - GPL)
在下一步中,我们将使用 MySQL 客户端连接到您的托管 MySQL 服务器并为应用程序准备数据库。
第二步——创建一个新的 MySQL 用户和数据库
在撰写本文时,原生 MySQL PHP 库 mysqlnd
不支持 caching_sha2_authentication
,这是 MySQL 8 的默认身份验证方法。 我们需要使用 mysql_native_password
身份验证方法创建一个新用户,以便能够将我们的 Laravel 应用程序连接到 MySQL 8 服务器。 我们还将为我们的演示应用程序创建一个专用数据库。
要开始使用,请使用管理员帐户登录您的服务器。 用您自己的 MySQL 用户、主机和端口替换突出显示的值:
mysql -u MYSQL_USER -p -h MYSQL_HOST -P MYSQL_PORT
出现提示时,提供管理员用户的密码。 登录后,您将可以访问 MySQL 8 服务器命令行界面。
首先,我们将为应用程序创建一个新数据库。 运行以下命令创建一个名为 travellist
的新数据库:
CREATE DATABASE travellist;
接下来,我们将创建一个新用户并设置密码,使用 mysql_native_password
作为该用户的默认身份验证方法。 鼓励您将突出显示的值替换为您自己的值,并使用强密码:
CREATE USER 'travellist-user'@'%' IDENTIFIED WITH mysql_native_password BY 'MYSQL_PASSWORD';
现在我们需要授予此用户对我们的应用程序数据库的权限:
GRANT ALL ON travellist.* TO 'travellist-user'@'%';
您现在可以使用以下命令退出 MySQL 提示:
exit;
你现在有一个专用的数据库和一个兼容的用户可以从你的 Laravel 应用程序连接。 在下一步中,我们将获取应用程序代码并设置配置详细信息,以便您的应用程序可以连接到您的托管 MySQL 数据库。
在本指南中,我们将使用 Laravel Migrations 和 数据库种子 来设置我们的应用程序表。 如果您需要将现有的本地数据库迁移到 DigitalOcean Managed MySQL 数据库,请参阅我们的文档 如何将 MySQL 数据库导入 DigitalOcean Managed Databases。
第 3 步 — 设置演示应用程序
首先,我们将从其 Github 存储库 获取演示 Laravel 应用程序。 在运行下一个命令之前,请随意检查应用程序的内容。
演示应用程序是一个旅行清单应用程序,最初是在我们的指南中开发的如何在 Ubuntu 18.04 上使用 LEMP 安装和配置 Laravel。 更新后的应用程序现在包含视觉改进,包括可以由访客上传的旅行照片和世界地图。 它还介绍了数据库迁移脚本和数据库种子,以使用 artisan
命令创建应用程序表并使用示例数据填充它们。
要获取与本教程兼容的应用程序代码,我们将从 Github 上的项目存储库下载 1.1 版本。 我们将下载的 zip 文件作为 travellist.zip
保存在我们的主目录中:
cd ~ curl -L https://github.com/do-community/travellist-laravel-demo/archive/1.1.zip -o travellist.zip
现在,解压缩应用程序的内容并将其目录重命名为:
unzip travellist.zip mv travellist-laravel-demo-1.1 travellist
导航到 travellist
目录:
cd travellist
在继续之前,我们需要安装 Laravel 框架所需的几个 PHP 模块,即:php-xml
、php-mbstring
、php-xml
和 [X152X ]。 要安装这些软件包,请运行:
sudo apt install unzip php-xml php-mbstring php-xml php-bcmath
要安装应用程序依赖项,请运行:
composer install
您将看到与此类似的输出:
OutputLoading composer repositories with package information Installing dependencies (including require-dev) from lock file Package operations: 80 installs, 0 updates, 0 removals - Installing doctrine/inflector (v1.3.0): Downloading (100%) - Installing doctrine/lexer (1.1.0): Downloading (100%) - Installing dragonmantank/cron-expression (v2.3.0): Downloading (100%) - Installing erusev/parsedown (1.7.3): Downloading (100%) ... Generating optimized autoload files > Illuminate\Foundation\ComposerScripts::postAutoloadDump > @php artisan package:discover --ansi Discovered Package: beyondcode/laravel-dump-server Discovered Package: fideloper/proxy Discovered Package: laravel/tinker Discovered Package: nesbot/carbon Discovered Package: nunomaduro/collision Package manifest generated successfully.
现在已安装应用程序依赖项。 接下来,我们将配置应用程序以连接到托管 MySQL 数据库。
创建.env
配置文件并设置App Key
我们现在将创建一个 .env
文件,其中包含将用于在每个环境中配置 Laravel 应用程序的变量。 该应用程序包含一个示例文件,我们可以复制该文件,然后修改其值以反映我们的环境设置。
将 .env.example
文件复制到名为 .env
的新文件中:
cp .env.example .env
现在我们需要设置应用程序密钥。 此密钥用于加密会话数据,应设置为唯一的 32 个字符长的字符串。 我们可以使用 artisan
工具自动生成这个密钥:
php artisan key:generate
让我们编辑环境配置文件以设置数据库详细信息。 使用您选择的命令行编辑器打开 .env
文件。 在这里,我们将使用 nano
:
nano .env
查找数据库凭据部分。 以下变量需要您注意:
DB_HOST
:您的托管 MySQL 服务器主机。 DB_PORT
:您的托管 MySQL 服务器端口。 DB_DATABASE
:我们在Step 2中创建的应用数据库的名称。 DB_USERNAME
:我们在Step 2中创建的数据库用户。 DB_PASSWORD
:我们在Step 2中定义的数据库用户的密码。
使用您自己的托管 MySQL 信息和凭据更新突出显示的值:
... DB_CONNECTION=mysql DB_HOST=MANAGED_MYSQL_HOST DB_PORT=MANAGED_MYSQL_PORT DB_DATABASE=MANAGED_MYSQL_DB DB_USERNAME=MANAGED_MYSQL_USER DB_PASSWORD=MANAGED_MYSQL_PASSWORD ...
完成编辑后,键入 CTRL+X
然后 Y
和 ENTER
保存并关闭文件。
现在应用程序已配置为连接到 MySQL 数据库,我们可以使用 Laravel 的命令行工具 artisan
创建数据库表并使用示例数据填充它们。
设置存储链接
在执行 artisan
命令提供的数据库工具之前,我们需要创建一个指向公共存储文件夹的符号链接,该文件夹将托管我们在应用程序中使用的旅行照片。 这是必要的,因为我们的数据库种子脚本依赖这些示例照片在 places
表中插入数据。
以下命令将在 public
目录内创建一个符号链接,该链接通过 Web 服务器公开,指向应用程序的内部存储目录 storage/app/public
:
php artisan storage:link
OutputThe [public/storage] directory has been linked.
要检查链接是否已创建以及它指向的位置,您可以运行:
ls -la public/
你会看到这样的输出:
Outputtotal 36 drwxrwxr-x 5 sammy sammy 4096 Oct 25 14:59 . drwxrwxr-x 12 sammy sammy 4096 Oct 25 14:58 .. -rw-rw-r-- 1 sammy sammy 593 Oct 25 06:29 .htaccess drwxrwxr-x 2 sammy sammy 4096 Oct 25 06:29 css -rw-rw-r-- 1 sammy sammy 0 Oct 25 06:29 favicon.ico drwxrwxr-x 2 sammy sammy 4096 Oct 25 06:29 img -rw-rw-r-- 1 sammy sammy 1823 Oct 25 06:29 index.php drwxrwxr-x 2 sammy sammy 4096 Oct 25 06:29 js -rw-rw-r-- 1 sammy sammy 24 Oct 25 06:29 robots.txt lrwxrwxrwx 1 sammy sammy 41 Oct 25 14:59 storage -> /home/sammy/travellist/storage/app/public -rw-rw-r-- 1 sammy sammy 1194 Oct 25 06:29 web.config
迁移和填充数据库
我们现在将使用 Laravel Migrations 和 数据库种子 来设置应用程序表。 这将帮助我们确定我们的数据库配置是否按预期工作。
要执行将创建应用程序使用的表的迁移脚本,请运行:
php artisan migrate
您将看到与此类似的输出:
OutputMigration table created successfully. Migrating: 2019_09_19_123737_create_places_table Migrated: 2019_09_19_123737_create_places_table (0.26 seconds) Migrating: 2019_10_14_124700_create_photos_table Migrated: 2019_10_14_124700_create_photos_table (0.42 seconds)
要使用示例数据填充数据库,请运行:
php artisan db:seed
你会看到这样的输出:
OutputSeeding: PlacesTableSeeder Seeded: PlacesTableSeeder (0.86 seconds) Database seeding completed successfully.
应用程序表现在已创建并填充了示例数据。
运行测试服务器(可选)
您可以使用 artisan serve
命令快速验证应用程序中的所有设置是否正确,然后必须配置像 Nginx 这样的全功能 Web 服务器来长期为应用程序服务。
我们将使用端口 8000
临时为应用程序提供测试服务。 如果您在服务器上启用了 UFW 防火墙,您应该首先允许访问此端口:
sudo ufw allow 8000
现在,要运行 Laravel 通过 artisan
工具公开的内置 PHP 服务器,运行:
php artisan serve --host=0.0.0.0 --port=8000
此命令将阻止您的终端,直到被 CTRL+C
中断。 它将使用内置的 PHP Web 服务器在所有网络接口上为应用程序提供测试服务,使用端口 8000
。
现在转到您的浏览器并使用服务器的域名或端口 8000
上的 IP 地址访问应用程序:
http://server_domain_or_IP:8000
您将看到以下页面:
如果您看到此页面,则表示应用程序已成功从配置的托管数据库中提取有关位置和照片的数据。 图像文件仍存储在本地磁盘中,但我们将在本指南的后续步骤中进行更改。
完成应用程序测试后,您可以通过点击 CTRL+C
停止 serve
命令。
如果您在服务器上运行 UFW,请不要忘记再次关闭端口 8000
:
sudo ufw deny 8000
第 4 步 — 配置 Nginx 以服务于应用程序
尽管内置的 PHP Web 服务器对于开发和测试目的非常有用,但它并不打算用作服务 PHP 应用程序的长期解决方案。 推荐使用像 Nginx 这样的全功能 Web 服务器。
首先,我们将应用程序文件夹移动到 /var/www
,这是在 Nginx 上运行的 Web 应用程序的常用位置。 首先,使用 mv
命令将应用程序文件夹及其所有内容移动到 /var/www/travellist
:
sudo mv ~/travellist /var/www/travellist
现在我们需要授予 Web 服务器用户对 storage
和 bootstrap/cache
文件夹的写入权限,Laravel 存储应用程序生成的文件。 我们将使用 setfacl
设置这些权限,这是一个命令行实用程序,允许在文件和文件夹中进行更健壮和细粒度的权限设置。
要包括对所需目录的 Web 服务器用户的读取、写入和执行 (rwx) 权限,请运行:
sudo setfacl -R -m g:www-data:rwx /var/www/travellist/storage sudo setfacl -R -m g:www-data:rwx /var/www/travellist/bootstrap/cache
应用程序文件现在是有序的,但我们仍然需要配置 Nginx 来提供内容。 为此,我们将在 /etc/nginx/sites-available
创建一个新的虚拟主机配置文件:
sudo nano /etc/nginx/sites-available/travellist
以下配置文件包含 Nginx 上 Laravel 应用程序的 recommended 设置:
/etc/nginx/sites-available/travellist
server { listen 80; server_name server_domain_or_IP; root /var/www/travellist/public; add_header X-Frame-Options "SAMEORIGIN"; add_header X-XSS-Protection "1; mode=block"; add_header X-Content-Type-Options "nosniff"; index index.html index.htm index.php; charset utf-8; location / { try_files $uri $uri/ /index.php?$query_string; } location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } error_page 404 /index.php; location ~ \.php$ { fastcgi_pass unix:/var/run/php/php7.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; } location ~ /\.(?!well-known).* { deny all; } }
将此内容复制到您的 /etc/nginx/sites-available/travellist
文件并调整突出显示的值以与您自己的配置保持一致。 完成编辑后保存并关闭文件。
要激活新的虚拟主机配置文件,请在 sites-enabled
中创建指向 travellist
的符号链接:
sudo ln -s /etc/nginx/sites-available/travellist /etc/nginx/sites-enabled/
注意:如果您有另一个虚拟主机文件先前配置为用于 travellist
虚拟主机中的相同 server_name
,您可能需要通过删除来停用旧配置/etc/nginx/sites-enabled/
中对应的符号链接。
要确认配置不包含任何语法错误,您可以使用:
sudo nginx -t
你应该看到这样的输出:
Outputnginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
要应用更改,请使用以下命令重新加载 Nginx:
sudo systemctl reload nginx
如果您现在重新加载浏览器,应用程序图像将被破坏。 发生这种情况是因为我们将应用程序目录移动到服务器内的新位置,因此我们需要重新创建指向应用程序存储文件夹的符号链接。
删除旧链接:
cd /var/www/travellist rm -f public/storage
现在再次运行 artisan
命令生成存储链接:
php artisan storage:link
现在转到您的浏览器并使用服务器的域名或 IP 地址访问应用程序,如配置文件中的 server_name
指令所定义:
http://server_domain_or_IP
在下一步中,我们会将对象存储服务集成到应用程序中。 这将替换当前用于旅行照片的本地磁盘存储。
第 5 步 — 将 S3 兼容的对象存储集成到应用程序中
我们现在将应用程序设置为使用与 S3 兼容的对象存储服务来存储在索引页面上展示的旅行照片。 由于应用程序已经在本地磁盘中存储了一些示例照片,我们还将使用 s3cmd 工具将现有的本地图像文件上传到远程对象存储。
为 Laravel 设置 S3 驱动程序
Laravel 使用 league/flysystem
,一个文件系统抽象库,它使 Laravel 应用程序能够使用和组合多种存储解决方案,包括本地磁盘和云服务。 使用 s3
驱动程序需要额外的软件包。 我们可以使用 composer require
命令安装这个包。
访问应用程序目录:
cd /var/www/travellist
composer require league/flysystem-aws-s3-v3
您将看到与此类似的输出:
OutputUsing version ^1.0 for league/flysystem-aws-s3-v3 ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 8 installs, 0 updates, 0 removals - Installing mtdowling/jmespath.php (2.4.0): Loading from cache - Installing ralouphie/getallheaders (3.0.3): Loading from cache - Installing psr/http-message (1.0.1): Loading from cache - Installing guzzlehttp/psr7 (1.6.1): Loading from cache - Installing guzzlehttp/promises (v1.3.1): Loading from cache - Installing guzzlehttp/guzzle (6.4.1): Downloading (100%) - Installing aws/aws-sdk-php (3.112.28): Downloading (100%) - Installing league/flysystem-aws-s3-v3 (1.0.23): Loading from cache ...
现在已经安装了所需的包,我们可以更新应用程序以连接到对象存储。 首先,我们将再次打开 .env
文件,为您的对象存储服务设置配置详细信息,例如键、存储桶名称和区域。
打开.env
文件:
nano .env
包括以下环境变量,将突出显示的值替换为您的对象存储配置详细信息:
/var/www/travellist/.env
DO_SPACES_KEY=EXAMPLE7UQOTHDTF3GK4 DO_SPACES_SECRET=exampleb8e1ec97b97bff326955375c5 DO_SPACES_ENDPOINT=https://ams3.digitaloceanspaces.com DO_SPACES_REGION=ams3 DO_SPACES_BUCKET=sammy-travellist
完成后保存并关闭文件。 现在打开 config/filesystems.php
文件:
nano config/filesystems.php
在这个文件中,我们将在 disks
数组中创建一个新的 disk 条目。 我们将此磁盘命名为 spaces
,并使用我们在 .env
文件中设置的环境变量来配置新磁盘。 在 disks
数组中包含以下条目:
配置/文件系统.php
'spaces' => [ 'driver' => 's3', 'key' => env('DO_SPACES_KEY'), 'secret' => env('DO_SPACES_SECRET'), 'endpoint' => env('DO_SPACES_ENDPOINT'), 'region' => env('DO_SPACES_REGION'), 'bucket' => env('DO_SPACES_BUCKET'), ],
仍然在同一个文件中,找到 cloud
条目并将其更改为将新的 spaces
磁盘设置为默认云文件系统磁盘:
配置/文件系统.php
'cloud' => env('FILESYSTEM_CLOUD', 'spaces'),
完成编辑后保存并关闭文件。 从您的控制器中,您现在可以使用 Storage::cloud()
方法作为访问默认 cloud
磁盘的快捷方式。 这样,应用程序可以灵活地使用多个存储解决方案,并且您可以在每个环境的基础上在提供者之间切换。
应用程序现在配置为使用对象存储,但我们仍需要更新将新照片上传到应用程序的代码。
让我们首先检查当前的 uploadPhoto
路由,位于 PhotoController
类中。 使用文本编辑器打开文件:
nano app/Http/Controllers/PhotoController.php
应用程序/Http/Controllers/PhotoController.php
… public function uploadPhoto(Request $request) { $photo = new Photo(); $place = Place::find($request->input('place')); if (!$place) { //add new place $place = new Place(); $place->name = $request->input('place_name'); $place->lat = $request->input('place_lat'); $place->lng = $request->input('place_lng'); } $place->visited = 1; $place->save(); $photo->place()->associate($place); $photo->image = $request->image->store('/', 'public'); $photo->save(); return redirect()->route('Main'); }
此方法接受 POST
请求并在照片表中创建新照片条目。 它首先检查是否在照片上传表单中选择了现有地点,如果不是这样,它将使用提供的信息创建一个新地点。 然后将该位置设置为 visited
并保存到数据库中。 之后,将创建一个关联,以便将新照片链接到指定地点。 然后将图像文件存储在 public
磁盘的根文件夹中。 最后,照片被保存到数据库中。 然后将用户重定向到主路由,即应用程序的索引页面。
这段代码中突出显示的行是我们感兴趣的。 在该行中,图像文件使用 store
方法保存到磁盘。 store
方法用于将文件保存到 filesystem.php
配置文件中定义的任何磁盘。 在这种情况下,它使用默认磁盘来存储上传的图像。
我们将更改此行为,以便将图像保存到对象存储而不是本地磁盘。 为此,我们需要在 store
方法调用中将 public
磁盘替换为 spaces
磁盘。 我们还需要确保上传文件的可见性设置为 public 而不是 private。
以下代码包含完整的 PhotoController
类,包括更新的 uploadPhoto
方法:
应用程序/Http/Controllers/PhotoController.php
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Photo; use App\Place; use Illuminate\Support\Facades\Storage; class PhotoController extends Controller { public function uploadForm() { $places = Place::all(); return view('upload_photo', [ 'places' => $places ]); } public function uploadPhoto(Request $request) { $photo = new Photo(); $place = Place::find($request->input('place')); if (!$place) { //add new place $place = new Place(); $place->name = $request->input('place_name'); $place->lat = $request->input('place_lat'); $place->lng = $request->input('place_lng'); } $place->visited = 1; $place->save(); $photo->place()->associate($place); $photo->image = $request->image->store('/', 'spaces'); Storage::setVisibility($photo->image, 'public'); $photo->save(); return redirect()->route('Main'); } }
将更新后的代码复制到您自己的 PhotoController
中,以便它反映突出显示的更改。 完成编辑后保存并关闭文件。
我们仍然需要修改应用程序的主视图,以便它使用对象存储文件 URL 来呈现图像。 打开travel_list.blade.php
模板:
nano resources/views/travel_list.blade.php
现在找到页面的 footer
部分,目前看起来像这样:
资源/视图/travel_list.blade.php
@section('footer') <h2>Travel Photos <small>[ <a href="{{ route('Upload.form') }}">Upload Photo</a> ]</small></h2> @foreach ($photos as $photo) <div class="photo"> <img src="{{ asset('storage') . '/' . $photo->image }}" /> <p>{{ $photo->place->name }}</p> </div> @endforeach @endsection
替换当前图像 src
属性以使用 spaces
存储盘中的文件 URL:
<img src="{{ Storage::disk('spaces')->url($photo->image) }}" />
如果您现在转到浏览器并重新加载应用程序页面,它将只显示损坏的图像。 发生这种情况是因为这些旅行照片的图像文件仍然只在本地磁盘中。 我们需要将已有的图片文件上传到对象存储中,这样已经存储在数据库中的照片才能成功的展示在应用页面中。
与 s3cmd
同步本地图像
s3cmd
工具可用于将本地文件与 S3 兼容的对象存储服务同步。 我们将运行 sync
命令将 storage/app/public/photos
中的所有文件上传到对象存储服务。
访问public
应用存储目录:
cd /var/www/travellist/storage/app/public
要查看已存储在远程磁盘中的文件,可以使用 s3cmd ls
命令:
s3cmd ls s3://your_bucket_name
现在运行 sync
命令将公共存储文件夹中的现有文件上传到对象存储:
s3cmd sync ./ s3://your_bucket_name --acl-public --exclude=.gitignore
这会将当前文件夹 (storage/app/public
) 与远程对象存储的根目录同步。 你会得到类似这样的输出:
Outputupload: './bermudas.jpg' -> 's3://sammy-travellist/bermudas.jpg' [1 of 3] 2538230 of 2538230 100% in 7s 329.12 kB/s done upload: './grindavik.jpg' -> 's3://sammy-travellist/grindavik.jpg' [2 of 3] 1295260 of 1295260 100% in 5s 230.45 kB/s done upload: './japan.jpg' -> 's3://sammy-travellist/japan.jpg' [3 of 3] 8940470 of 8940470 100% in 24s 363.61 kB/s done Done. Uploaded 12773960 bytes in 37.1 seconds, 336.68 kB/s.
现在,如果您再次运行 s3cmd ls
,您将看到三个新文件已添加到对象存储桶的根文件夹中:
s3cmd ls s3://your_bucket_name
Output2019-10-25 11:49 2538230 s3://sammy-travellist/bermudas.jpg 2019-10-25 11:49 1295260 s3://sammy-travellist/grindavik.jpg 2019-10-25 11:49 8940470 s3://sammy-travellist/japan.jpg
转到您的浏览器并重新加载应用程序页面。 现在所有图像都应该可见,如果您使用浏览器调试工具检查它们,您会注意到它们都使用来自对象存储的 URL。
测试集成
演示应用程序现在功能齐全,将文件存储在远程对象存储服务中,并将数据保存到托管 MySQL 数据库中。 我们现在可以上传几张照片来测试我们的设置。
从浏览器访问 /upload
应用程序路由:
http://server_domain_or_IP/upload
您将看到以下表格:
您现在可以上传几张照片来测试对象存储集成。 从计算机中选择图像后,您可以从下拉菜单中选择现有地点,也可以通过提供其名称和 地理坐标 添加新地点,以便将其加载到应用程序地图中。
第 6 步 — 使用只读节点扩展 DigitalOcean 托管 MySQL 数据库(可选)
由于只读操作通常比数据库服务器上的写入操作更频繁,因此通过设置多个只读节点来扩展数据库集群是一种常见的做法。 这将分配由 SELECT
操作产生的负载。
为了演示此设置,我们首先将 2 个只读节点添加到我们的 DigitalOcean Managed MySQL 集群。 然后,我们将配置 Laravel 应用程序以使用这些节点。
访问 DigitalOcean Cloud Panel 并按照以下说明进行操作:
- 转到 Databases 并选择您的 MySQL 集群。
- 点击【X6X】【X10X】,在下拉菜单中选择【X27X】【X31X】。
- 配置节点选项并点击 Create 按钮。 请注意,新节点可能需要几分钟才能准备好。
- 再次重复步骤 1-4,以便您拥有 2 个只读节点。
- 记下两个节点的主机,因为我们将需要它们来进行 Laravel 配置。
准备好只读节点后,返回终端。
我们现在将配置我们的 Laravel 应用程序以使用多个数据库节点。 完成后,INSERT
和 UPDATE
等查询将被转发到您的主集群节点,而所有 SELECT
查询将被重定向到您的只读节点。
首先,转到服务器上的应用程序目录并使用您选择的文本编辑器打开您的 .env
文件:
cd /var/www/travellist nano .env
找到 MySQL 数据库配置并注释掉 DB_HOST
行:
/var/www/travellist/.env
DB_CONNECTION=mysql #DB_HOST=MANAGED_MYSQL_HOST DB_PORT=MANAGED_MYSQL_PORT DB_DATABASE=MANAGED_MYSQL_DB DB_USERNAME=MANAGED_MYSQL_USER DB_PASSWORD=MANAGED_MYSQL_PASSWORD
完成后保存并关闭文件。 现在在文本编辑器中打开 config/database.php
:
nano config/database.php
在 connections
数组中查找 mysql
条目。 您应该在此配置数组中包含 三个 新项目:read
、write
和 sticky
。 read
和 write
条目将设置集群节点,将 sticky
选项设置为 true
将重用 write
连接,以便写入数据库的数据在同一请求周期内立即可用。 如果您不想要这种行为,可以将其设置为 false
。
/var/www/travel_list/config/database.php
... 'mysql' => [ 'read' => [ 'host' => [ 'READONLY_NODE1_HOST', 'READONLY_NODE2_HOST', ], ], 'write' => [ 'host' => [ 'MANAGED_MYSQL_HOST', ], ], 'sticky' => true, ...
完成编辑后保存并关闭文件。 为了测试一切是否按预期工作,我们可以在 routes/web.php
中创建一个临时路由,以从数据库中提取一些数据并显示有关正在使用的连接的详细信息。 通过这种方式,我们将能够看到请求是如何在只读节点之间进行负载平衡的。
打开routes/web.php
文件:
nano routes/web.php
包括以下路线:
/var/www/travel_list/routes/web.php
... Route::get('/mysql-test', function () { $places = App\Place::all(); $results = DB::select( DB::raw("SHOW VARIABLES LIKE 'server_id'") ); return "Server ID: " . $results[0]->Value; });
现在转到您的浏览器并访问 /mysql-test
应用程序路由:
http://server_domain_or_IP/mysql-test
你会看到这样的页面:
重新加载页面几次,您会注意到 Server ID
值发生了变化,表明请求正在两个只读节点之间随机分布。
结论
在本指南中,我们为高可用性和可扩展环境准备了一个 Laravel 6 应用程序。 我们已将数据库系统外包给外部托管 MySQL 服务,并且我们已将与 S3 兼容的对象存储服务集成到应用程序中,以存储用户上传的文件。 最后,我们了解了如何通过在应用程序的配置文件中包含额外的只读集群节点来扩展应用程序的数据库。
包含在本指南中所做的所有修改的更新的演示应用程序代码可以在 Github 上应用程序存储库的 2.1 标签 中找到。
从这里,您可以设置 负载均衡器 以在多个节点之间分配负载和扩展您的应用程序。 您还可以利用此设置创建 容器化环境 以在 Docker 上运行您的应用程序。