当前服务中有大量的文件,包括图书、短文等,同时也包括音频、视频、图片等资源。其存储及下载方案是需要仔细思考。一方面满足大量文件存储、后续扩容等基本需求;另一方面还要能够支持高性能下载,解放服务器压力;同时还要考虑文档地址的安全性,防盗链,以及检查用户的下载权限等。
本系统的设计主要为了解决以下几个问题:
- 大量静态文件的存储以及访问,包括图片、pdf、epub、音频、视频甚至是网页文档或JS文件
- 将文件的存储与业务数据分离以及与业务分离,只提供纯粹的存储和访问
- 解决文件存储单点故障
- 实现文件高性能读取,文档下载过程一方面支持实现鉴权,另一方面高下载性能且不影响业务服务
系统架构
架构图系统整体架构参见上图,描述了本系统的所有部署模块、结构,以及对文档的下载过程做了详细描述。从架构图上看该方案主要分为以下几个模块:
- 内容管理系统,主要针对业务实现的客户端上传或服务端上传服务
- 文件映射服务(阅读资源服务),主要实现文档上传FastDFS后的存储位置记录(MySql),以及提供资源下载的处理接口(Handler),当下载是提供内部映射(FileMapping)机制,在本服务中实现鉴权处理,配合Nginx模块实现文件保护措施
- FastDFS模块,提供资源高性能存储,解决单点存储问题
- Nginx模块,提供文件分发功能,同时集成ngx-fastdfs模块,支持FastDFS文件Nginx访问机制,配合Nginx内部映射机制,实现文档保护措施。在该模中,利用Nginx的文件分发功能(sendfile),使得文件映射服务,只提供鉴权、文件映射,不再提供从FastDFS中下载文档到容器,读取文件到内存,并通过容器将内容写回给客户端,从而释放了内存、IO、并发等方面的资源,从而提高文档的高速访问,达到高性能读取的目的
逻辑处理流程
上图流程描述了文档存储的处理流程,对照系统架构中,主要操作为:文档映射服务、内容管理系统、FastDFS等三个模块。
- 当用户将文档提交到内容管理系统中,该服务将接收到的文件直接提交到FastDFS模块中
- 当文档存储成功后,FastDFS将返回文档存储信息
- 内容管理系统接收到文档存储信息后,将该信息存储到文档映射服务中,并告知客户端上传结果
上述流程,就是一个普通的上传流程,唯一区别是增加了文档映射服务模块。在该模块中,主要存储了两种信息:
- 文件在FastDFS中的位置信息
- 用户访问该文件的URL,比如
https://www.xx.com/storage/748/startjava.epub
,该URL可以是生成的短Url(/storage/beNvds34sd
) ;URL也可能是资源的标识b20190819001
;总之通过该访问URL能够映射到FastDFS的位置信息
上述流程中,对于文件存储主要依赖于FastDFS,而对于上传的并发、内存等需求,未发生改变,可通过部署分节点提高上传性能。对于读多写少的系统而言(通常更多的大文件仍然是通过后台分步存储),高性能的读取对于性能的提高效果更为显著。
对于文件的读取,主要用到的模块为:
- Nginx(文件分发)
- 文件映射服务,主要是提供鉴权逻辑以及根据请求的URL映射到对应的文件位置,并提供Nginx内部映射头信息,进行服务器内部跳转;
-
FastDFS,提供文档读取
- Nginx作为反向代理和负载均衡,接收用户访问文档请求,并将该请求转发到文档映射服务中
- 文档映射服务根据请求Url,查询(缓存)该Url对应的文件位置
- 查询到文件位置后,通过设置响应头X-Accel-Redirect,该头信息的值为文件的实际存储位置
- 设置X-Accel-Redirect头信息后,服务将自动内部转发到Nginx上,Nginx读取设置的文件位置信息,并将该文件信息转发到ngx-fastdfs-module模块中
- ngx-fastdfs-module模块读取文件数据,并将该数据返回给Nginx,从而实现Nginx分发文件,实现高性能下载文件的目的
以上步骤为下载文件的整体流程,为了实现鉴权、Nginx高性能分发文件,需要对Nginx进行下述配置:
- Nginx开启sendfile功能
在http模块中添加以下信息:
sendfile on;
- 通过nginx转发时自动添加X-Sendfile信息,该节点和sendfile节点同步开启和关闭
在http模块中添加以下信息,参考上图:
proxy_set_header X-Sendfile on;
对于文件映射服务来说,可以通过读取该节点信息,以确定当前服务是否开启了Nginx文件分发功能,如果读取不到或该值不为on,则可以通过容器进行文件读取和下载(性能低) -
对于FastDFS的Nginx,需要开启internal属性,该属性开启后,则表明当前的代理反问,只能通过服务器内部转发,浏览器等客户端无法感知,且外部无法直接访问该代理URL,从而可以配合文件映射服务实现鉴权和限流的措施
- Nginx的X-Accel-Redirect静态转发。对于Nginx将请求下载服务转发给文件映射服务后,文件映射服务查询到文件位置信息,通过在响应头中设置该信息,即X-Accel-Redirect=文件位置信息,该设置可以实现服务器内部转发,客户端无法感知,从而保护了真实的文件位置信息。虽然该方案效率会比302方案性能稍低,但可以实现鉴权及文件保护,因此在该方案中作为文件转发的处理方案
- 文件映射服务与ngx-fastdfs-module共存于一个Nginx服务
具体部署方案
Nginx配置
http {
include mime.types;
default_type application/octet-stream;
proxy_set_header Host $host:$server_port;
# 远程IP
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;
proxy_set_header X-Sendfile on;
sendfile on;
server {
listen 9001;
server_name localhost;
location /M00 {
internal;
root /home/storage/fdfs/data/;
ngx_fastdfs_module;
}
location / {
root html;
if ($request_uri ~ (.*)/d/) {
rewrite ^.*/d/(.*) /$1 break;
proxy_pass http://http_t;
}
}
}
}
以上配置为基础配置。部分说明:
proxy_set_header X-Sendfile on; # 对转发请求,添加X-Sendfile节点信息
sendfile on; #开启nginx文件分发
location /M00
部分,为FastDFS的配置,其中添加了internal
,保证该代理只能通过内部转发请求。root节点配置的是FastDFS的文件存储位置。
后一个包含/d/
的节点配置,模拟了文件映射服务的配置。
文件映射处理逻辑
@GetMapping("/t")
public void download(HttpServletResponse response, @RequestHeader(value = "X-Sendfile", required = false) String useXSend) {
if ("on".equalsIgnoreCase(useXSend)) {
// nginx分发下载
response.setHeader("Content-Disposition", "attachment;filename=test.epub");
response.setHeader("X-Accel-Redirect", "/M00/00/00/wKhxkF1wLL6AGsQVABFbpT1QNps669.epub");
response.setHeader("X-Accel-Buffering", "yes");
} else {
// 容器下载
}
}
上述示例程序中,只描述了Nginx分发下载的过程,对于容器下载,就是普通的读取文件,并写数据流到客户端。通过X-Sendfile控制是走容器下载,还是走Nginx下载。另外Nginx和容器都可以支持断点续传,后续会描述Nginx的断点续传功能。
网友评论