戰(zhàn))
簡(jiǎn)介fastdfs-nginx-module-1.22.zip 是面向 Nginx 運(yùn)維與后端開發(fā)人員的中間件源碼包用于打通 FastDFS 分布式文件系統(tǒng)與 Nginx讓 Nginx 直接處理文件上傳下載請(qǐng)求緩解應(yīng)用服務(wù)器壓力適合搭建高并發(fā)文件服務(wù)的中高級(jí)技術(shù)人員。壓縮包共 8 個(gè)文件約 22KB以 C 源碼為主包含模塊核心實(shí)現(xiàn)與公共頭文件另附 conf 配置示例、config 編譯腳本、INSTALL 安裝說(shuō)明、readme 說(shuō)明文檔及 HISTORY 版本記錄覆蓋從編譯到配置的完整鏈路。該版本穩(wěn)定且與 FastDFS 對(duì)應(yīng)版本兼容性良好可支撐防盜鏈、限速、緩存等高級(jí)功能。目前已有 641 人學(xué)習(xí)下載讀者可據(jù)此掌握模塊編譯接入、Nginx location 指令配置與常見(jiàn)排錯(cuò)思路快速構(gòu)建高性能、高可用的文件服務(wù)。1. fastdfs-nginx-module-1.22.zip 到底解決什么問(wèn)題從一次 404 排查說(shuō)起去年幫一個(gè)做在線教育的團(tuán)隊(duì)排查文件訪問(wèn)故障現(xiàn)象很典型上傳接口返回成功文件 ID 也拿到了但瀏覽器訪問(wèn)http://file.xxx.com/group1/M00/00/00/xxx.jpg直接 404Nginx 日志里連請(qǐng)求記錄都沒(méi)有。他們用的是 FastDFS 做分布式存儲(chǔ)Tracker 和 Storage 都正常問(wèn)題出在 Storage 節(jié)點(diǎn)上的 Nginx 沒(méi)有正確加載 fastdfs-nginx-module。這個(gè)模塊的核心作用是讓 Nginx 直接讀取 FastDFS Storage 本地磁盤上的文件繞過(guò) Tracker 的調(diào)度把文件訪問(wèn)路徑縮短一跳。沒(méi)有它Nginx 只能做反向代理轉(zhuǎn)發(fā)到 Storage 自帶的 HTTP 服務(wù)性能和穩(wěn)定性都差一截。fastdfs-nginx-module-1.22.zip就是這套方案的源碼包配合 FastDFS 5.x/6.x 和 Nginx 1.18~1.24 使用。適合正在搭 FastDFS 集群、被文件訪問(wèn) 404 或超時(shí)折磨的運(yùn)維和后端也適合想把文件服務(wù)從「能跑」調(diào)到「能扛」的熟手。2. 模塊編譯與 Nginx 集成從源碼包到可加載的 so 文件2.1 為什么必須重新編譯 Nginx 而不是動(dòng)態(tài)加載fastdfs-nginx-module 不是標(biāo)準(zhǔn)的 Nginx 動(dòng)態(tài)模塊它依賴 FastDFS 的客戶端庫(kù)libfastcommon和fastdfs的頭文件在 Nginx 編譯階段就要把模塊源碼一起編進(jìn)去。常見(jiàn)做法是下載 Nginx 源碼./configure時(shí)用--add-module指向解壓后的模塊目錄。很多人圖省事想用load_module動(dòng)態(tài)加載結(jié)果編譯出來(lái)的 so 文件符號(hào)缺失Nginx 啟動(dòng)直接報(bào)undefined symbol: g_fdfs_connect_timeout。這個(gè)模塊的變量和 FastDFS 全局配置強(qiáng)綁定動(dòng)態(tài)加載在 1.22 版本上基本走不通血淚經(jīng)驗(yàn)是老老實(shí)實(shí)重新編譯。先確認(rèn)依賴版本匹配。FastDFS 6.x 對(duì)應(yīng)的 libfastcommon 要用 1.0.60 以上否則編譯模塊時(shí)會(huì)報(bào)fdfs_global.h找不到。我一般會(huì)先跑一遍pkg-config --modversion libfastcommon確認(rèn)版本沒(méi)有 pkg-config 文件就直接看/usr/local/lib下有沒(méi)有l(wèi)ibfastcommon.so。# 安裝編譯依賴CentOS 和 Ubuntu 包名不同按需選 yum install -y gcc gcc-c make libtool pcre-devel zlib-devel openssl-devel # 或者 Ubuntu apt install -y build-essential libpcre3-dev zlib1g-dev libssl-dev # 確認(rèn) libfastcommon 和 fastdfs 已安裝 ls /usr/local/lib/libfastcommon.so ls /usr/local/lib/libfdfsclient.so參數(shù)說(shuō)明pcre-devel是 Nginx rewrite 模塊依賴zlib-devel給 gzip 用openssl-devel在需要 HTTPS 時(shí)必須有。如果只做內(nèi)網(wǎng)文件訪問(wèn)openssl 可以省但生產(chǎn)環(huán)境建議加上后面配 HTTPS 反向代理會(huì)用到。2.2 解壓模塊并編譯進(jìn) Nginx 的完整命令拿到fastdfs-nginx-module-1.22.zip后解壓到一個(gè)固定路徑我習(xí)慣放/usr/local/src。注意模塊目錄里有一個(gè)src子目錄--add-module要指向這一層指向外層會(huì)報(bào)config文件找不到。cd /usr/local/src unzip fastdfs-nginx-module-1.22.zip # 解壓后目錄名通常是 fastdfs-nginx-module-1.22 ls fastdfs-nginx-module-1.22/src/config # 下載對(duì)應(yīng)版本 Nginx 源碼這里以 1.24.0 為例 wget https://nginx.org/download/nginx-1.24.0.tar.gz tar -zxvf nginx-1.24.0.tar.gz cd nginx-1.24.0 # 配置編譯參數(shù)關(guān)鍵在 --add-module ./configure \ --prefix/usr/local/nginx \ --with-http_ssl_module \ --with-http_gzip_static_module \ --add-module/usr/local/src/fastdfs-nginx-module-1.22/src make make install邏輯說(shuō)明--prefix決定 Nginx 安裝路徑后面配 systemd 或 init 腳本都依賴這個(gè)路徑。--add-module把模塊源碼編進(jìn) Nginx 二進(jìn)制編譯完成后可以用nginx -V看到--add-module參數(shù)確認(rèn)生效。make階段如果報(bào)fdfs_define.h: No such file or directory說(shuō)明 fastdfs 頭文件路徑?jīng)]被找到需要在 configure 前加--with-cc-opt-I/usr/local/include和--with-ld-opt-L/usr/local/lib。編譯完成后不要急著啟動(dòng)先把模塊自帶的配置文件復(fù)制到 FastDFS 配置目錄否則 Nginx 啟動(dòng)會(huì)報(bào)mod_fastdfs.conf not found。cp /usr/local/src/fastdfs-nginx-module-1.22/src/mod_fastdfs.conf /etc/fdfs/這個(gè)文件是模塊和 FastDFS 之間的橋梁下一章會(huì)逐項(xiàng)拆解里面的參數(shù)。2.3 驗(yàn)證模塊是否真正加載編譯安裝完用nginx -V看輸出里有沒(méi)有--add-module/usr/local/src/fastdfs-nginx-module-1.22/src。有的話說(shuō)明模塊已經(jīng)編進(jìn)二進(jìn)制。再跑nginx -t測(cè)試配置如果報(bào)unknown directive ngx_fastdfs_module說(shuō)明模塊沒(méi)編進(jìn)去或者 Nginx 啟動(dòng)時(shí)用的不是新編譯的二進(jìn)制。常見(jiàn)坑是系統(tǒng)里原來(lái)有 yum 裝的 Nginxwhich nginx指向/usr/sbin/nginx而新編譯的在/usr/local/nginx/sbin/nginx啟動(dòng)時(shí)路徑寫錯(cuò)就白干了。/usr/local/nginx/sbin/nginx -V 21 | grep fastdfs # 預(yù)期輸出包含 --add-module/usr/local/src/fastdfs-nginx-module-1.22/src /usr/local/nginx/sbin/nginx -t # 預(yù)期輸出 syntax is ok 和 test is successful如果nginx -t報(bào)ngx_fastdfs_module未知指令先確認(rèn)nginx.conf里有沒(méi)有寫ngx_fastdfs_module;這一行。這個(gè)指令必須放在server塊里放在http塊或者location塊里都不生效。我見(jiàn)過(guò)有人把它寫在http塊頂層Nginx 直接啟動(dòng)失敗日志里報(bào)directive is not allowed here。3. mod_fastdfs.conf 參數(shù)逐項(xiàng)拆解與 Nginx location 配置3.1 模塊配置文件里最容易配錯(cuò)的五個(gè)參數(shù)/etc/fdfs/mod_fastdfs.conf是模塊運(yùn)行時(shí)的核心配置參數(shù)不多但每個(gè)都影響文件能否被正確讀取。下面這張表是我在實(shí)際部署中反復(fù)核對(duì)過(guò)的關(guān)鍵項(xiàng)按重要性排序。參數(shù)作用常見(jiàn)錯(cuò)誤值正確做法tracker_serverTracker 地址只寫 IP 不寫端口必須寫成ip:port多個(gè)用換行分隔group_name存儲(chǔ)組名留空或?qū)戝e(cuò)與 Storage 的 group 一致默認(rèn) group1url_have_group_nameURL 是否含組名與 Nginx location 不匹配訪問(wèn)路徑帶 group1 就設(shè) truestore_path_count存儲(chǔ)路徑數(shù)量與實(shí)際磁盤數(shù)不符與 Storage 的 store_path_count 一致store_path0存儲(chǔ)路徑指向錯(cuò)誤目錄與 Storage 的 store_path0 完全一致tracker_server如果配錯(cuò)Nginx 啟動(dòng)時(shí)不會(huì)報(bào)錯(cuò)但訪問(wèn)文件會(huì)卡住直到超時(shí)日志里出現(xiàn)connect to tracker server failed。url_have_group_name這個(gè)參數(shù)最玄學(xué)設(shè)成 false 時(shí)訪問(wèn)/group1/M00/...會(huì) 404設(shè)成 true 時(shí)訪問(wèn)/M00/...也會(huì) 404必須和 Nginx 里 location 的匹配規(guī)則對(duì)齊。# /etc/fdfs/mod_fastdfs.conf 關(guān)鍵片段 tracker_server192.168.1.10:22122 tracker_server192.168.1.11:22122 group_namegroup1 url_have_group_nametrue store_path_count1 store_path0/data/fastdfs/storage參數(shù)說(shuō)明tracker_server可以寫多個(gè)模塊會(huì)輪詢連接但不會(huì)自動(dòng)故障轉(zhuǎn)移Tracker 掛一個(gè)會(huì)影響部分請(qǐng)求。store_path0必須和 Storage 節(jié)點(diǎn)上storage.conf里的store_path0完全一致差一個(gè)字符都會(huì)導(dǎo)致文件找不到。group_name在多組環(huán)境下尤其重要寫錯(cuò)組名會(huì)去錯(cuò)誤的組里找文件。3.2 Nginx location 匹配規(guī)則與 root 路徑設(shè)置Nginx 里要配一個(gè) location 來(lái)攔截文件訪問(wèn)請(qǐng)求把路徑映射到 FastDFS 的存儲(chǔ)目錄。核心指令是ngx_fastdfs_module它告訴 Nginx 這個(gè) location 交給 FastDFS 模塊處理。root或alias要指向 Storage 的store_path0下的data目錄注意是data不是store_path0本身。server { listen 8888; server_name file.xxx.com; location ~/group[0-9]/M00 { ngx_fastdfs_module; } location /group1/M00 { alias /data/fastdfs/storage/data; ngx_fastdfs_module; } }邏輯說(shuō)明第一個(gè) location 用正則匹配group加數(shù)字加M00適合多組環(huán)境模塊會(huì)根據(jù) URL 里的組名去對(duì)應(yīng)組找文件。第二個(gè) location 是精確前綴匹配只處理 group1alias把 URL 路徑映射到本地磁盤。兩種寫法選一種即可混用會(huì)導(dǎo)致匹配優(yōu)先級(jí)混亂。ngx_fastdfs_module指令必須寫在 location 塊內(nèi)且一個(gè) location 只能寫一次。如果訪問(wèn)文件返回 404 但磁盤上文件確實(shí)存在先檢查alias路徑末尾有沒(méi)有斜杠。alias /data/fastdfs/storage/data;和alias /data/fastdfs/storage/data/;在 Nginx 里行為不同前者會(huì)把 location 匹配部分拼到路徑后面后者會(huì)替換掉。我一般用alias不帶末尾斜杠配合正則 location 使用。3.3 啟動(dòng)順序與日志排查入口FastDFS 的啟動(dòng)順序有講究先起 Tracker再起 Storage最后起 Nginx。順序反了 Nginx 啟動(dòng)時(shí)連不上 Tracker雖然不會(huì)崩但模塊初始化會(huì)失敗后續(xù)所有文件請(qǐng)求都返回 500。啟動(dòng)完用ps aux | grep fdfs確認(rèn) Tracker 和 Storage 進(jìn)程都在。# 啟動(dòng)順序 /usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf start /usr/bin/fdfs_storaged /etc/fdfs/storage.conf start /usr/local/nginx/sbin/nginx # 排查日志位置 tail -f /data/fastdfs/storage/logs/storaged.log tail -f /usr/local/nginx/logs/error.log邏輯說(shuō)明Storage 日志里會(huì)記錄文件上傳和同步的詳細(xì)信息Nginx error.log 里會(huì)記錄模塊加載和文件讀取的錯(cuò)誤。如果 Nginx 報(bào)file not found但 Storage 日志顯示文件已上傳大概率是store_path0配錯(cuò)或者alias路徑不對(duì)。如果 Nginx 報(bào)connect to tracker server failed檢查 Tracker 是否啟動(dòng)、防火墻是否放行 22122 端口。4. 避坑與常見(jiàn)問(wèn)題排查那些讓我熬夜的報(bào)錯(cuò)4.1 現(xiàn)象Nginx 啟動(dòng)報(bào) unknown directive ngx_fastdfs_module原因模塊沒(méi)有編譯進(jìn) Nginx或者啟動(dòng)的 Nginx 二進(jìn)制不是新編譯的那個(gè)。系統(tǒng)里同時(shí)存在 yum 安裝的 Nginx 和源碼編譯的 Nginx 時(shí)nginx命令默認(rèn)走/usr/sbin/nginx而模塊只編進(jìn)了/usr/local/nginx/sbin/nginx。解決用絕對(duì)路徑啟動(dòng)/usr/local/nginx/sbin/nginx并用nginx -V確認(rèn)輸出里有--add-module。如果確認(rèn)編進(jìn)去了還報(bào)這個(gè)錯(cuò)檢查nginx.conf里ngx_fastdfs_module是不是寫在了http塊或server塊頂層它只能出現(xiàn)在location塊內(nèi)。4.2 現(xiàn)象訪問(wèn)文件返回 404Nginx 日志無(wú)記錄原因請(qǐng)求沒(méi)有匹配到任何 location被 Nginx 默認(rèn)的 404 處理了。常見(jiàn)于 location 正則寫錯(cuò)比如location ~/group[0-9]/M00中間多了空格或者 URL 里的組名不是 group1 而是 group2正則沒(méi)覆蓋到。解決先用curl -v看請(qǐng)求實(shí)際路徑再對(duì)照nginx.conf里的 location 規(guī)則。臨時(shí)加一個(gè)location / { return 200 hit; }測(cè)試請(qǐng)求是否到達(dá) Nginx。如果請(qǐng)求路徑帶 URL 編碼比如%2F正則里的/匹配不到需要在 location 前加rewrite解碼。4.3 現(xiàn)象文件能訪問(wèn)但下載到一半中斷原因Nginx 的sendfile和 FastDFS 模塊的讀取方式?jīng)_突或者output_buffers設(shè)置過(guò)小。模塊從磁盤讀文件時(shí)用的是pread如果 Nginx 開了sendfile on大文件傳輸時(shí)可能觸發(fā)內(nèi)核 bug 導(dǎo)致連接重置。解決在nginx.conf的http塊里加sendfile off;或者針對(duì)文件 location 單獨(dú)關(guān)掉。同時(shí)把output_buffers 4 256k;調(diào)大client_max_body_size按實(shí)際文件大小設(shè)置。如果還是斷檢查 Storage 磁盤是否有壞道dmesg里會(huì)有 I/O error。4.4 現(xiàn)象多組環(huán)境下文件串組group1 的文件在 group2 里找原因mod_fastdfs.conf里group_name寫死了 group1但 Nginx location 正則匹配了所有組。模塊收到 group2 的請(qǐng)求后仍然去 group1 的存儲(chǔ)路徑找文件自然找不到。解決多組環(huán)境要把group_name留空或者注釋掉讓模塊根據(jù) URL 里的組名動(dòng)態(tài)選擇。同時(shí)確認(rèn)每個(gè) Storage 組的store_path0路徑不同否則多個(gè)組寫同一塊磁盤會(huì)互相覆蓋。url_have_group_name必須設(shè)為 true否則模塊無(wú)法從 URL 提取組名。4.5 現(xiàn)象Nginx 錯(cuò)誤日志刷 tracker server timeout原因Tracker 服務(wù)器負(fù)載過(guò)高或者網(wǎng)絡(luò)抖動(dòng)模塊連接 Tracker 的超時(shí)時(shí)間默認(rèn) 30 秒高并發(fā)下大量請(qǐng)求堆積在連接 Tracker 這一步。FastDFS 的 Tracker 本身不存文件只做調(diào)度但每個(gè)文件請(qǐng)求都要先問(wèn) Tracker 要 Storage 地址。解決在mod_fastdfs.conf里調(diào)大connect_timeout和network_timeout同時(shí)增加 Tracker 節(jié)點(diǎn)數(shù)量。更徹底的做法是開啟storage_server直連模式讓 Nginx 直接讀本地文件不走 Tracker但這要求 Nginx 和 Storage 部署在同一臺(tái)機(jī)器。如果文件訪問(wèn)量極大建議在前面加一層 CDN 或者本地緩存。5. 進(jìn)階技巧用 Nginx 緩存和日志分析把文件服務(wù)壓到極限模塊跑通只是第一步生產(chǎn)環(huán)境還要解決兩個(gè)問(wèn)題一是 Tracker 在高并發(fā)下成為瓶頸二是文件訪問(wèn)日志分散在多臺(tái) Storage 上沒(méi)法統(tǒng)一分析。我一般會(huì)在 Nginx 層加兩級(jí)緩存再配一個(gè)日志聚合腳本。第一級(jí)緩存用 Nginx 的proxy_cache思路但 fastdfs-nginx-module 不走 proxy所以要用open_file_cache。這個(gè)指令緩存文件描述符和元數(shù)據(jù)對(duì)頻繁訪問(wèn)的小文件效果明顯。配置放在http塊里open_file_cache max10000 inactive60s; open_file_cache_valid 80s; open_file_cache_min_uses 2; open_file_cache_errors on;參數(shù)說(shuō)明max10000表示最多緩存一萬(wàn)個(gè)文件描述符按每個(gè)描述符約 1KB 內(nèi)存算大概占 10MB。inactive60s是 60 秒內(nèi)沒(méi)被訪問(wèn)就淘汰。min_uses 2表示文件被訪問(wèn)兩次以上才緩存避免冷文件占坑。open_file_cache_errors on會(huì)緩存文件不存在的錯(cuò)誤防止惡意請(qǐng)求反復(fù)穿透到磁盤。第二級(jí)緩存用fastcgi_cache或者proxy_cache把熱點(diǎn)文件緩存在 Nginx 本地磁盤。但 fastdfs-nginx-module 的輸出不走 upstream沒(méi)法直接用 proxy_cache。我的做法是在模塊前面加一個(gè)location判斷如果本地緩存目錄有文件就直接返回沒(méi)有才走模塊。這需要配合try_files和root指向緩存目錄實(shí)現(xiàn)稍復(fù)雜但能把 Tracker 的 QPS 降一個(gè)數(shù)量級(jí)。日志分析方面Nginx 的 access.log 默認(rèn)格式不包含文件大小和響應(yīng)時(shí)間需要在nginx.conf里自定義 log_formatlog_format fastdfs $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $request_time $upstream_response_time; access_log /var/log/nginx/fastdfs_access.log fastdfs;邏輯說(shuō)明$request_time是 Nginx 處理請(qǐng)求的總時(shí)間$upstream_response_time在 fastdfs-nginx-module 場(chǎng)景下通常為空因?yàn)槟K不走 upstream。$body_bytes_sent是實(shí)際發(fā)送給客戶端的字節(jié)數(shù)用來(lái)判斷文件是否完整傳輸。日志按天切割后用 awk 統(tǒng)計(jì) top 100 大文件和最慢請(qǐng)求# 統(tǒng)計(jì)響應(yīng)時(shí)間超過(guò) 1 秒的請(qǐng)求 awk $NF 1.0 {print $7, $NF} /var/log/nginx/fastdfs_access.log | sort -k2 -rn | head -20 # 統(tǒng)計(jì)訪問(wèn)量最大的文件 awk {print $7} /var/log/nginx/fastdfs_access.log | sort | uniq -c | sort -rn | head -20這兩個(gè)命令我每次上線新 Storage 節(jié)點(diǎn)都會(huì)跑一遍確認(rèn)沒(méi)有異常慢請(qǐng)求和熱點(diǎn)文件集中。如果發(fā)現(xiàn)某個(gè)文件被反復(fù)訪問(wèn)就在 Nginx 層加expires頭讓客戶端緩存減少回源。還有一個(gè)容易忽略的點(diǎn)是文件 ID 的 URL 編碼。FastDFS 生成的文件 ID 里可能包含和/在 URL 里需要編碼成%2B和%2F。Nginx 默認(rèn)會(huì)解碼一次如果 location 正則里寫的是/group1/M00而實(shí)際請(qǐng)求是/group1%2FM00匹配就會(huì)失敗。我一般會(huì)在server塊里加merge_slashes off;并配合rewrite處理編碼路徑。這個(gè)坑在對(duì)接 Java 客戶端時(shí)特別常見(jiàn)因?yàn)?Java 的 URLEncoder 默認(rèn)把/編成%2F。從那以后我每次部署 fastdfs-nginx-module都會(huì)先跑一遍nginx -V確認(rèn)模塊加載再用curl測(cè)三種路徑帶組名、不帶組名、帶 URL 編碼全部通過(guò)才切流量。這套流程幫我省了至少三次半夜回滾。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取