基于Nginx与Minio预签名URL实现文件直链预览的架构实践

基于Nginx与Minio预签名URL实现文件直链预览的架构实践 1. 项目缘起为什么我们需要绕过后端做直链预览做文件存储和管理的朋友对Minio肯定不陌生。它是个好东西自建S3兼容的对象存储成本可控权限清晰。但每次碰到“文件预览”这个需求很多团队的方案就有点绕前端上传文件到Minio拿到一个存储路径或者对象名然后想在前端页面直接展示图片、PDF或者视频时问题就来了。最常见的做法是前端把这个路径发给自己的后端服务后端服务再去Minio把文件流拉下来然后通过一个接口比如/preview?fileKeyxxx吐给前端。这个流程听起来没毛病但细想一下多了一层完全不必要的转发。这个后端接口成了整个预览链条的瓶颈和单点。你的应用服务器要额外处理大量的文件流I/O这消耗CPU和内存尤其是在高并发预览图片或文档的场景下。更头疼的是权限如果你的预览接口要做鉴权逻辑就变得更复杂。有没有更直接的办法当然有这就是“直链预览”的核心价值让前端浏览器能够直接、安全地访问Minio桶里的文件就像访问一个普通的静态资源URL一样完全跳过应用后端。这不仅能极大减轻后端压力还能提升预览速度因为少了一次网络跳转。今天要聊的就是用最简单、最稳定的方式基于Nginx来实现这个目标让你彻底告别为预览功能单独写接口的日子。2. 核心思路拆解直链预览的本质与安全边界在动手之前我们必须把思路理清楚。所谓“直链预览”并不是简单地把Minio的桶设置成公开可读public那样安全风险太大。我们的目标是在保证文件私密性的前提下让经过认证的前端用户能够临时获得一个指向Minio存储文件的、具有时效性的直接访问链接。这里的关键在于“临时”和“可控”。Minio本身支持生成带有预签名Presigned URL的URL这个URL在一段时间内比如5分钟可以直接访问对象过期失效。这解决了“临时”的问题。那么“可控”呢我们绝不能让用户自己随意生成任何文件的预签名URL这需要通过我们的后端服务来严格管控生成逻辑进行权限校验。所以完整的流程应该是前端需要预览文件时携带文件标识如object key和必要的身份令牌请求我们后端的“授权接口”。后端校验用户权限是否有权预览此文件校验通过后调用Minio SDK生成一个针对该文件的、短时有效的预签名URL返回给前端。前端拿到这个预签名URL后直接将其作为图片的src、视频的source或者iframe的地址进行加载浏览器会直接向Minio请求文件数据。看到这里你可能要问这不还是有后端参与吗没错权限校验和URL生成这一步后端必不可少这是安全底线。但我们成功消除了最耗资源的“文件流转发”步骤。后端只做轻量的鉴权和字符串URL生成返回一个302重定向或者直接返回URL字符串即可不再承担文件数据传输的压力。这才是“无需通过后端对外暴露预览接口”的真正含义——不暴露一个实际传输文件体的/preview接口。那么Nginx在这里面扮演什么角色一个高级的“路由员”和“保安”。我们不会让前端直接去访问Minio服务可能复杂的端口和路径而是通过Nginx反向代理对外提供一个统一、友好的域名和路径如https://static.yourdomain.com/preview/...Nginx在代理过程中可以无缝地处理预签名URL所需的查询参数让整个流程对前端更加透明和规整。3. 基础环境与工具准备在开始配置之前我们需要确保以下几个核心组件已经就位。我会假设你已经在服务器上部署了Docker这是目前最简洁的部署方式。3.1 Minio的部署与基础配置首先我们通过Docker快速拉起一个Minio服务。这里我们使用单机模式演示生产环境请考虑分布式集群。# 创建存储数据的本地目录 mkdir -p /opt/minio/data # 使用Docker运行Minio容器 docker run -d \ -p 9000:9000 \ -p 9001:9001 \ --name minio \ -v /opt/minio/data:/data \ -e MINIO_ROOT_USERadmin \ -e MINIO_ROOT_PASSWORDyour_strong_password \ quay.io/minio/minio server /data --console-address :9001参数解释与注意事项-p 9000:9000: Minio的API服务端口S3兼容接口我们的后端服务和Nginx都会通过这个端口与Minio通信。-p 9001:9001: Minio控制台端口用于Web管理界面方便我们创建桶、设置策略。MINIO_ROOT_USER和MINIO_ROOT_PASSWORD: 这是最高权限的root凭证务必设置成强密码并在生产环境通过 secrets 管理。--console-address :9001: 显式指定控制台地址避免版本差异导致的问题。运行后访问http://你的服务器IP:9001用上面设置的用户名密码登录。首先创建一个用于存储预览文件的桶例如命名为preview-bucket。至关重要的一步是这个桶的访问策略Access Policy绝对不能设置为public公开读/写。保持其默认的private私有状态。我们的安全完全依赖于预签名URL而不是桶的公开性。3.2 Nginx的安装与代理概念你的服务器上很可能已经安装了Nginx。如果没有通过包管理器安装即可例如在Ubuntu上sudo apt update sudo apt install nginx。这里我们需要理解Nginx反向代理的核心功能它接收客户端的请求比如对https://static.yourdomain.com/preview/...的访问然后将这个请求转发到后端的真实服务比如Minio的http://localhost:9000并将后端服务的响应返回给客户端。对于客户端浏览器而言它只知道在和Nginx打交道完全感知不到后端的Minio。这就为我们提供了一个统一的访问入口和进行安全、流量控制的机会。3.3 后端服务的角色定位你需要有一个正在运行的后端服务可以是Spring Boot, Node.js, Go等任何语言。这个服务需要集成Minio的客户端SDK。它的核心职责有两个用户身份与权限验证验证当前请求预览的用户是否有权访问目标文件。生成预签名URL在权限验证通过后使用Minio SDK生成一个有时效性的预签名URL。这个服务会提供一个类似GET /api/generate-pre-signed-url?fileKeyxxx的接口。它不返回文件流只返回一个URL字符串。这个接口本身负载极轻。4. 核心配置实战Nginx与Minio的对接这是实现直链预览最关键的步骤。我们要配置Nginx让它能够正确地将前端发来的预览请求代理到Minio生成的预签名URL上。4.1 Minio预签名URL的生成后端示例以Java Spring Boot集成Minio SDK为例我们先看后端如何生成这个关键的URL。首先添加Minio客户端依赖以Maven为例dependency groupIdio.minio/groupId artifactIdminio/artifactId version8.5.7/version !-- 请使用最新稳定版 -- /dependency然后编写一个服务类和方法import io.minio.BucketExistsArgs; import io.minio.GetPresignedObjectUrlArgs; import io.minio.MinioClient; import io.minio.http.Method; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; Service public class MinioService { private final MinioClient minioClient; Value(${minio.bucket-name}) private String bucketName; public MinioService(Value(${minio.endpoint}) String endpoint, Value(${minio.access-key}) String accessKey, Value(${minio.secret-key}) String secretKey) { this.minioClient MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); } /** * 生成用于预览的预签名URL * param objectName 文件在Minio中的存储路径/对象名 * param expiry 有效期单位秒 * return 预签名URL */ public String generatePreviewUrl(String objectName, int expiry) throws Exception { // 在实际业务中这里应加入严格的权限校验逻辑 // 例如检查当前登录用户是否有权访问这个objectName对应的文件 // if (!permissionService.canPreview(currentUser, objectName)) { throw new ForbiddenException(); } // 检查存储桶是否存在可选但建议 boolean found minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); if (!found) { throw new RuntimeException(存储桶不存在); } // 生成预签名URL使用GET方法 String url minioClient.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() .method(Method.GET) // 必须是GET .bucket(bucketName) .object(objectName) .expiry(expiry, TimeUnit.SECONDS) .build() ); return url; } }关键点解析Method.GET: 预览操作一定是GET请求。expiry: 这个时间不宜过长。对于预览场景通常设置60秒到300秒1-5分钟完全足够。这平衡了安全性和用户体验。时间太短用户页面还没加载完链接就失效时间太长链接泄露的风险增加。权限校验//注释部分至关重要。生成URL前必须根据你的业务逻辑验证当前用户从Session或JWT中获取是否有权限访问这个objectName。这是防止越权访问的唯一关卡。你的控制器Controller会调用这个Service接收fileKey即objectName参数校验权限后返回生成的URL字符串。4.2 Nginx反向代理配置详解现在假设你的后端API接口返回的预签名URL长这样http://minio-server:9000/preview-bucket/user/2023/photo.jpg?X-Amz-Algorithm...X-Amz-Signature...。我们不希望前端直接看到Minio的端口和复杂参数希望通过一个更清晰的域名来访问。我们在Nginx的配置文件中例如/etc/nginx/conf.d/minio-preview.conf添加如下配置server { listen 80; server_name static.yourdomain.com; # 用于静态资源/预览的专用域名 # 可选全局日志格式方便调试 access_log /var/log/nginx/minio-preview.access.log main; error_log /var/log/nginx/minio-preview.error.log warn; location /preview/ { # 核心将请求代理到Minio服务 proxy_pass http://localhost:9000/; # 注意结尾的斜杠 # 以下是一系列关键代理设置确保请求头正确传递 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 特别重要处理Minio可能需要的原始请求头 proxy_set_header Authorization ; # 清空Authorization因为预签名信息在URL参数里 proxy_hide_header x-amz-id-2; # 可选隐藏Minio特定的响应头 proxy_hide_header x-amz-request-id; # 缓冲区设置提升大文件如视频代理性能 proxy_buffering on; proxy_buffer_size 4k; proxy_buffers 8 4k; proxy_busy_buffers_size 8k; # 超时设置 proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 300s; # 预览大文件可能需要较长时间 # 禁用Nginx对后端响应内容的处理直接透传 proxy_set_header Accept-Encoding ; } # 其他location块例如处理你的主应用... # location / { ... } }配置深度解析与避坑指南proxy_pass结尾的斜杠proxy_pass http://localhost:9000/;结尾的斜杠意味着当请求static.yourdomain.com/preview/bucket/object时Nginx会将/preview/部分移除然后将请求转发给http://localhost:9000/bucket/object。这是最常用、最清晰的方式。如果你的Minio桶名就是preview也可以考虑不剥离路径但前者更灵活。proxy_set_header Host $host;这行配置至关重要。它告诉Nginx在转发请求给Minio时将HTTP请求头中的Host字段设置为Nginx接收请求时的域名static.yourdomain.com。有些Minio配置或S3兼容服务会校验这个头。如果设置成$proxy_host即localhost:9000在某些情况下可能导致签名错误。proxy_set_header Authorization ;这是最大的一个坑预签名URL的所有认证信息Signature, Credential等都是以查询参数Query String的形式存在于URL中的例如?X-Amz-Algorithm...。如果原始请求中带有Authorization请求头比如你的前端请求自己API时带的JWT TokenNginx会默认将其转发给Minio。Minio看到这个头可能会优先使用它进行验证而忽略URL中的签名参数导致返回403 SignatureDoesNotMatch错误。清空这个头可以避免冲突。proxy_hide_header用于隐藏Minio返回的一些内部头信息让响应对前端更干净。超时与缓冲区预览大文件如视频时网络传输时间可能较长。适当调大proxy_read_timeout和缓冲区大小可以避免代理过程中超时中断。配置完成后执行sudo nginx -t测试配置语法无误后sudo systemctl reload nginx重载配置。5. 前端集成与完整流程演练现在我们从前端的视角把整个流程串起来。5.1 前端请求逻辑假设你的前端是Vue/React有一个需要预览的图片组件。// 1. 点击预览或组件加载时先调用后端授权接口获取预签名URL async function getPreviewUrl(fileKey) { try { // 这里需要带上你的用户认证信息例如在请求头中加入JWT Token const response await fetch(/api/generate-pre-signed-url?fileKey${encodeURIComponent(fileKey)}, { headers: { Authorization: Bearer ${yourJwtToken} } }); if (!response.ok) { throw new Error(Failed to get preview URL); } const data await response.json(); // 假设后端返回 { code: 0, data: { url: ... } } return data.data.url; // 得到类似 http://minio-server:9000/bucket/object?... 的URL } catch (error) { console.error(Error fetching preview URL:, error); return null; } } // 2. 将获取到的URL转换为通过Nginx代理的友好URL function convertToProxyUrl(minioUrl) { // 解析原始的Minio URL const urlObj new URL(minioUrl); // 假设我们的Nginx配置将 /preview/ 代理到Minio根路径 // 原始路径可能是 /preview-bucket/user/photo.jpg?... const pathWithParams urlObj.pathname urlObj.search; // 拼接成通过Nginx代理的地址 // 注意这里需要将Minio的桶名bucket作为路径的一部分 const proxyUrl https://static.yourdomain.com/preview${pathWithParams}; return proxyUrl; } // 3. 在组件中使用 async function loadImage() { const fileKey user/2023/profile-picture.jpg; // 存储在Minio中的对象键 const rawUrl await getPreviewUrl(fileKey); if (rawUrl) { const finalPreviewUrl convertToProxyUrl(rawUrl); // 将 finalPreviewUrl 设置为 img 标签的 src // img :srcfinalPreviewUrl altPreview / } }前端注意事项错误处理预签名URL可能过期。如果图片加载失败返回403或404前端需要有一个重试机制重新调用授权接口获取新的URL然后更新src属性。可以监听img标签的onerror事件来实现。URL转换convertToProxyUrl函数是关键。它把后端返回的、直接指向Minio端口的内部URL转换成了对外公开的、通过Nginx代理的友好URL。这个转换逻辑必须与Nginx配置中的proxy_pass规则严格匹配。5.2 完整请求链路追踪让我们跟踪一次完整的用户操作用户打开一个包含图片预览的页面。前端JS执行调用后端GET /api/generate-pre-signed-url?fileKeyuser/photo.jpg携带用户Token。后端服务收到请求验证JWT Token确认用户身份。根据fileKey和用户身份执行业务权限校验例如检查这个用户是否属于可以查看这张照片的团队。校验通过后使用Minio SDK生成一个5分钟有效的预签名URL例如http://192.168.1.100:9000/preview-bucket/user/photo.jpg?X-Amz-Algorithm...X-Amz-Expires300...。将这个URL返回给前端。前端JS收到URL通过convertToProxyUrl函数将其转换为https://static.yourdomain.com/preview/preview-bucket/user/photo.jpg?X-Amz-Algorithm...。浏览器尝试加载img srchttps://static.yourdomain.com/...。DNS解析static.yourdomain.com到你的Nginx服务器IP。Nginx在80/443端口收到请求匹配location /preview/规则。Nginx剥离/preview/前缀将剩余路径/preview-bucket/user/photo.jpg?...连同所有查询参数原封不动地转发给http://localhost:9000。Minio服务收到请求识别出请求的是preview-bucket桶下的user/photo.jpg对象并验证URL中携带的签名参数。签名验证通过Minio从磁盘读取图片文件。Minio将图片数据流返回给Nginx。Nginx将数据流返回给用户的浏览器。浏览器成功渲染图片。可以看到你的应用后端第3步只在最开始进行了一次轻量的鉴权和URL生成之后繁重的文件传输工作完全由Minio和Nginx代理完成实现了流量卸载。6. 高级优化与安全加固基础流程跑通后我们可以从性能、安全和可维护性角度进行优化。6.1 性能优化缓存与CDN集成对于频繁访问的公共预览文件如产品图、新闻配图每次生成预签名URL虽然安全但仍有开销。可以考虑加入缓存层。Nginx代理缓存在location /preview/块中增加缓存配置。proxy_cache_path /var/cache/nginx levels1:2 keys_zoneminio_cache:10m max_size10g inactive60m use_temp_pathoff; location /preview/ { proxy_cache minio_cache; proxy_cache_key $scheme$request_method$host$request_uri; proxy_cache_valid 200 302 10m; # 对成功响应缓存10分钟 proxy_cache_valid 404 1m; add_header X-Cache-Status $upstream_cache_status; ... # 其他proxy_*配置 }注意使用缓存要格外小心因为预签名URL本身具有时效性。如果缓存了一个即将过期的URL响应后续请求会直接拿到过期的缓存内容导致失败。因此缓存时间proxy_cache_valid必须远小于预签名URL的有效期expiry。例如URL有效期5分钟300秒Nginx缓存可以设置1-2分钟。更安全的做法是仅对公开的、无需频繁更新的资源开启缓存。CDN集成如果预览资源面向公网且流量巨大可以将static.yourdomain.com的DNS解析到CDN服务商。CDN边缘节点会从你的Nginx源站拉取资源并缓存极大减轻源站压力并加速全球访问。配置CDN时需要注意设置合适的缓存键通常包含完整的URL和查询参数并处理好缓存过期时间与预签名URL过期时间的关系。6.2 安全加固防盗链与权限细化防盗链Referer Check防止其他网站盗用你的预览链接消耗流量。可以在Nginx层面实现。location /preview/ { valid_referers none blocked server_names *.yourdomain.com; if ($invalid_referer) { return 403; } ... # 其他proxy配置 }这允许来自yourdomain.com及其子域的请求以及直接输入URLnone的请求其他来源返回403。注意Referer头可以被伪造这不是绝对安全但能阻挡大部分普通盗链。IP访问限制如果你的预览服务仅限内网或特定IP段使用可以在Nginx的location或server块中使用allow/deny指令。后端权限校验的粒度这是最重要的安全环节。后端的/api/generate-pre-signed-url接口不能只验证用户登录态必须实现业务级权限校验。例如文件归属校验用户A只能生成属于他自己或他所在部门/项目的文件的预览URL。操作频率限制对同一用户/IP生成URL的速率进行限制防止恶意刷取。文件类型白名单只允许生成图片、文档、视频等安全类型的预览URL禁止生成可执行文件等危险类型的URL。6.3 监控与日志分析清晰的日志有助于排查问题。我们在Nginx配置中已经定义了独立的访问和错误日志。可以定期分析这些日志关注高频率的403错误可能意味着有大量的无效/过期签名请求可能是前端逻辑有问题或者遭受攻击。响应时间$upstream_response_time监控Nginx到Minio的代理延迟如果延迟过高可能是Minio服务器负载大或网络问题。缓存命中率X-Cache-Status头通过日志或监控系统统计HIT,MISS,BYPASS的比例评估缓存效果。可以在Nginx日志格式中添加这些变量log_format minio_log $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $upstream_cache_status rt$upstream_response_time; access_log /var/log/nginx/minio-preview.access.log minio_log;7. 常见问题排查与实战技巧在实际部署和运行中你几乎一定会遇到下面这些问题。这里我把踩过的坑和解决方案总结出来。7.1 签名错误403 SignatureDoesNotMatch这是最常见的问题浏览器控制台会报403错误Nginx或Minio日志中会有SignatureDoesNotMatch的提示。原因1Nginx传递了错误的Host头。如前所述确保Nginx配置中有proxy_set_header Host $host;。可以尝试暂时改为proxy_set_header Host minio-server:9000;你的Minio服务地址进行对比测试。原因2Nginx传递了多余的Authorization头。确保配置了proxy_set_header Authorization ;来清空它。原因3URL被篡改或过期。检查前端转换URL的逻辑是否正确确保查询参数被完整传递。检查后端生成的URL有效期是否太短而前端又有缓存或重复使用旧URL的情况。原因4系统时间不同步。Minio服务端和生成签名的后端服务器之间的系统时间如果相差太大通常超过15分钟会导致签名立即失效。确保所有服务器使用NTP服务保持时间同步。排查技巧在Nginx配置中临时增加proxy_pass_request_headers on;并设置详细的日志记录转发给Minio的完整请求头和URL与后端生成的原始URL进行逐字节对比。7.2 跨域问题CORS如果你的前端域名如app.yourdomain.com和预览资源域名static.yourdomain.com不同浏览器会因同源策略而阻止请求。解决方案在Minio桶级别配置CORS规则。通过Minio控制台或mc命令行工具添加允许前端域名访问的规则。例如允许来自https://app.yourdomain.com的GET、HEAD请求。# 使用mc命令行工具配置 mc alias set myminio http://localhost:9000 admin your_strong_password mc admin cors add myminio/preview-bucket --cors-rule {AllowedOrigins: [https://app.yourdomain.com], AllowedMethods: [GET, HEAD], AllowedHeaders: [*], ExposeHeaders: [ETag], MaxAgeSeconds: 300}Nginx添加CORS头作为补充也可以在Nginx代理层添加CORS响应头这样即使Minio配置遗漏Nginx也能补上。location /preview/ { ... # 添加CORS头 add_header Access-Control-Allow-Origin https://app.yourdomain.com always; add_header Access-Control-Allow-Methods GET, HEAD, OPTIONS always; add_header Access-Control-Allow-Headers * always; add_header Access-Control-Expose-Headers ETag always; if ($request_method OPTIONS) { return 204; } ... }7.3 大文件预览超时或中断预览大型视频或PDF时连接可能在中途断开。调整Nginx超时设置如前面配置所示增大proxy_read_timeout例如300秒并调整缓冲区大小proxy_buffers。调整Minio配置Minio本身也有传输超时设置。对于Docker部署可以检查环境变量或启动参数。前端分片或范围请求对于超大视频更好的方式是前端播放器支持范围请求Range Request。预签名URL同样支持范围请求播放器可以只请求文件的某一部分。确保Nginx配置中正确传递了Range请求头proxy_set_header Range $http_range;。7.4 关于HTTPS生产环境务必使用HTTPS。为static.yourdomain.com申请SSL证书可以使用Let‘s Encrypt免费证书。修改Nginx配置将listen 80;改为listen 443 ssl;并配置ssl_certificate和ssl_certificate_key路径。配置HTTP到HTTPS的重定向server { listen 80; server_name static.yourdomain.com; return 301 https://$server_name$request_uri; }注意如果你的Minio服务也是通过HTTPS访问例如部署在另一台有证书的机器上那么Nginx的proxy_pass地址应改为https://minio-server:9000。同时你可能需要配置Nginx信任Minio服务器的证书或者使用proxy_ssl_verify off;仅限测试或内网可信环境来跳过证书验证。7.5 路径匹配与重写陷阱如果你的文件路径比较复杂或者Minio桶的结构是嵌套的要特别注意Nginx的proxy_pass和路径重写规则。一个错误的斜杠可能导致404。坚持使用proxy_pass http://backend/;结尾带斜杠来剥离匹配前缀的方式通常是最不容易出错的做法。对于更复杂的路径映射需求可以结合使用rewrite指令但务必先在小范围测试。8. 方案对比与选型思考最后我们来审视一下这个方案的优劣以及它适用的场景。优势性能极致文件传输压力完全从应用后端剥离由Minio和Nginx直接处理后端CPU和内存消耗极低。架构清晰职责分离。后端专注业务鉴权Minio专注存储Nginx专注代理和缓存符合微服务的设计理念。成本可控利用成熟的Nginx和Minio无需引入额外的文件代理或网关服务。安全性好基于短时有效的预签名URL避免了永久直链的安全风险。权限控制牢牢掌握在后端业务逻辑中。局限性复杂度增加相比一个简单的/preview接口需要配置Nginx、处理CORS、调试签名问题前期有一定复杂度。依赖Minio特性完全绑定Minio的预签名URL机制。如果未来要迁移到其他对象存储如阿里云OSS、AWS S3需要适配不同的SDK和签名算法但整体架构可以复用。前端需要处理URL转换前端需要增加一层URL转换逻辑并妥善处理URL过期后的刷新。适用场景中大型Web应用有大量文件预览需求如图片社区、在线文档、视频点播。希望将静态资源流量与应用API流量分离进行独立扩缩容和监控。对应用服务器的性能和资源消耗有较高要求。不适用场景极其简单的个人项目或原型追求最快实现一个简单的后端流式传输接口可能更直接。文件预览需求极少引入Nginx和复杂配置的收益不大。必须对文件内容进行实时水印、动态压缩等后端处理的场景这种场景下文件流仍需经过后端处理。我个人在多个生产项目中采用了这套方案稳定运行了相当长的时间。最大的体会是前期多花一点时间把Nginx配置和权限校验框架搭好后期在应对突发流量和排查问题时会轻松非常多。尤其是当预览请求量突然增长时你只需要关注Nginx和Minio的监控指标而不用担心你的应用服务器被拖垮。这种将核心业务与非核心流量分离的思路在很多架构设计中都值得借鉴。