當前服務中有大量的文件,包括圖書、短文等,同時也包括音頻、視頻、圖片等資源。其存儲及下載方案是需要仔細思考。一方面滿足大量文件存儲、后續(xù)擴容等基本需求;另一方面還要能夠支持高性能下載,解放服務器壓力;同時還要考慮文檔地址的安全性,防盜鏈,以及檢查用戶的下載權限等。
本系統(tǒng)的設計主要為了解決以下幾個問題:
- 大量靜態(tài)文件的存儲以及訪問,包括圖片、pdf、epub、音頻、視頻甚至是網(wǎng)頁文檔或JS文件
- 將文件的存儲與業(yè)務數(shù)據(jù)分離以及與業(yè)務分離,只提供純粹的存儲和訪問
- 解決文件存儲單點故障
- 實現(xiàn)文件高性能讀取,文檔下載過程一方面支持實現(xiàn)鑒權,另一方面高下載性能且不影響業(yè)務服務
系統(tǒng)架構

系統(tǒng)整體架構參見上圖,描述了本系統(tǒng)的所有部署模塊、結構,以及對文檔的下載過程做了詳細描述。從架構圖上看該方案主要分為以下幾個模塊:
- 內容管理系統(tǒng),主要針對業(yè)務實現(xiàn)的客戶端上傳或服務端上傳服務
- 文件映射服務(閱讀資源服務),主要實現(xiàn)文檔上傳FastDFS后的存儲位置記錄(MySql),以及提供資源下載的處理接口(Handler),當下載是提供內部映射(FileMapping)機制,在本服務中實現(xiàn)鑒權處理,配合Nginx模塊實現(xiàn)文件保護措施
- FastDFS模塊,提供資源高性能存儲,解決單點存儲問題
- Nginx模塊,提供文件分發(fā)功能,同時集成ngx-fastdfs模塊,支持FastDFS文件Nginx訪問機制,配合Nginx內部映射機制,實現(xiàn)文檔保護措施。在該模中,利用Nginx的文件分發(fā)功能(sendfile),使得文件映射服務,只提供鑒權、文件映射,不再提供從FastDFS中下載文檔到容器,讀取文件到內存,并通過容器將內容寫回給客戶端,從而釋放了內存、IO、并發(fā)等方面的資源,從而提高文檔的高速訪問,達到高性能讀取的目的
邏輯處理流程

上圖流程描述了文檔存儲的處理流程,對照系統(tǒng)架構中,主要操作為:文檔映射服務、內容管理系統(tǒng)、FastDFS等三個模塊。
- 當用戶將文檔提交到內容管理系統(tǒng)中,該服務將接收到的文件直接提交到FastDFS模塊中
- 當文檔存儲成功后,F(xiàn)astDFS將返回文檔存儲信息
- 內容管理系統(tǒng)接收到文檔存儲信息后,將該信息存儲到文檔映射服務中,并告知客戶端上傳結果
上述流程,就是一個普通的上傳流程,唯一區(qū)別是增加了文檔映射服務模塊。在該模塊中,主要存儲了兩種信息:
- 文件在FastDFS中的位置信息
- 用戶訪問該文件的URL,比如
https://www.xx.com/storage/748/startjava.epub,該URL可以是生成的短Url(/storage/beNvds34sd) ;URL也可能是資源的標識b20190819001;總之通過該訪問URL能夠映射到FastDFS的位置信息
上述流程中,對于文件存儲主要依賴于FastDFS,而對于上傳的并發(fā)、內存等需求,未發(fā)生改變,可通過部署分節(jié)點提高上傳性能。對于讀多寫少的系統(tǒng)而言(通常更多的大文件仍然是通過后臺分步存儲),高性能的讀取對于性能的提高效果更為顯著。
對于文件的讀取,主要用到的模塊為:
- Nginx(文件分發(fā))
- 文件映射服務,主要是提供鑒權邏輯以及根據(jù)請求的URL映射到對應的文件位置,并提供Nginx內部映射頭信息,進行服務器內部跳轉;
-
FastDFS,提供文檔讀取
- Nginx作為反向代理和負載均衡,接收用戶訪問文檔請求,并將該請求轉發(fā)到文檔映射服務中
- 文檔映射服務根據(jù)請求Url,查詢(緩存)該Url對應的文件位置
- 查詢到文件位置后,通過設置響應頭X-Accel-Redirect,該頭信息的值為文件的實際存儲位置
- 設置X-Accel-Redirect頭信息后,服務將自動內部轉發(fā)到Nginx上,Nginx讀取設置的文件位置信息,并將該文件信息轉發(fā)到ngx-fastdfs-module模塊中
- ngx-fastdfs-module模塊讀取文件數(shù)據(jù),并將該數(shù)據(jù)返回給Nginx,從而實現(xiàn)Nginx分發(fā)文件,實現(xiàn)高性能下載文件的目的
以上步驟為下載文件的整體流程,為了實現(xiàn)鑒權、Nginx高性能分發(fā)文件,需要對Nginx進行下述配置:
- Nginx開啟sendfile功能
在http模塊中添加以下信息:
sendfile on;
- 通過nginx轉發(fā)時自動添加X-Sendfile信息,該節(jié)點和sendfile節(jié)點同步開啟和關閉
在http模塊中添加以下信息,參考上圖:
proxy_set_header X-Sendfile on;
對于文件映射服務來說,可以通過讀取該節(jié)點信息,以確定當前服務是否開啟了Nginx文件分發(fā)功能,如果讀取不到或該值不為on,則可以通過容器進行文件讀取和下載(性能低) -
對于FastDFS的Nginx,需要開啟internal屬性,該屬性開啟后,則表明當前的代理反問,只能通過服務器內部轉發(fā),瀏覽器等客戶端無法感知,且外部無法直接訪問該代理URL,從而可以配合文件映射服務實現(xiàn)鑒權和限流的措施
- Nginx的X-Accel-Redirect靜態(tài)轉發(fā)。對于Nginx將請求下載服務轉發(fā)給文件映射服務后,文件映射服務查詢到文件位置信息,通過在響應頭中設置該信息,即X-Accel-Redirect=文件位置信息,該設置可以實現(xiàn)服務器內部轉發(fā),客戶端無法感知,從而保護了真實的文件位置信息。雖然該方案效率會比302方案性能稍低,但可以實現(xiàn)鑒權及文件保護,因此在該方案中作為文件轉發(fā)的處理方案
- 文件映射服務與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; # 對轉發(fā)請求,添加X-Sendfile節(jié)點信息
sendfile on; #開啟nginx文件分發(fā)
location /M00 部分,為FastDFS的配置,其中添加了internal,保證該代理只能通過內部轉發(fā)請求。root節(jié)點配置的是FastDFS的文件存儲位置。
后一個包含/d/的節(jié)點配置,模擬了文件映射服務的配置。
文件映射處理邏輯
@GetMapping("/t")
public void download(HttpServletResponse response, @RequestHeader(value = "X-Sendfile", required = false) String useXSend) {
if ("on".equalsIgnoreCase(useXSend)) {
// nginx分發(fā)下載
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分發(fā)下載的過程,對于容器下載,就是普通的讀取文件,并寫數(shù)據(jù)流到客戶端。通過X-Sendfile控制是走容器下載,還是走Nginx下載。另外Nginx和容器都可以支持斷點續(xù)傳,后續(xù)會描述Nginx的斷點續(xù)傳功能。


