十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux下FastDFS搭建与Nginx缩略图生成实战指南

Linux下FastDFS搭建与Nginx缩略图生成实战指南 很多做后端的朋友第一次搭 FastDFS都有个共同的困惑文件是存上去了图片缩略图怎么搞我自己在 Linux 下完整搭过一套 FastDFS 文件服务从 Tracker、Storage 到 Nginx 访问模块再到缩略图生成踩了不少坑也把整个流程彻底理顺了。这篇文章就把这套可落地的方案完整写出来包括版本搭配、配置文件关键项、缩略图两种实现方式和常见问题排查给你一个可以直接照着抄的实操参考。1. 安装前的设计思路FastDFS 解决什么问题缩略图放在哪一层1.1 FastDFS 的组成结构FastDFS 是一个用 C 语言写的开源分布式文件系统它的定位非常明确轻量、简洁、不做复杂功能只负责文件存储和访问调度。整体上分成两个角色Tracker跟踪服务器负责调度。客户端上传或下载文件时先找 TrackerTracker 告诉客户端应该找哪个 Storage。Storage存储服务器真正存放文件的节点。Storage 可以分成多个组group组内多台机器互为备份自动同步文件组与组之间则是水平扩展。用一个生活化的类比Tracker 就像前台接待Storage 就像后面的库房。客户不会直接去库房搬东西而是先问前台“我这单货该去哪里取”。前台告诉你地址后客户端再和对应的 Storage 交互。这样做的好处是客户端不需要知道集群里所有节点的拓扑只要认准 Tracker 就行。FastDFS 本身不感知文件内容不管你是 JPG、PNG、PDF 还是视频在它眼里都是一串字节它会根据文件内容计算出一个唯一的 file_id并按规则把文件写到 Storage 磁盘上。这个特性决定了缩略图需要我们自己处理因为 FastDFS 不会自动帮你生成图片尺寸。1.2 缩略图方案选型为什么我推荐上传时静态生成既然 FastDFS 不生成缩略图那缩略图就必须由外部机制来做。常见的做法有三种方案实现方式优点缺点上传时生成客户端/后端收到原图后先调用 ImageMagick 等工具生成缩略图再把原图和缩略图都传给 FastDFS访问稳定、无额外计算、存储可控上传链路多一步处理对上传性能有轻微影响Nginx 动态生成通过 Nginx 的 image_filter 模块在访问时实时缩放省存储不需要预先生成所有尺寸消耗 CPU高并发下压力大与 FastDFS 模块配合不直观异步任务生成上传原图后丢消息队列worker 异步生成缩略图并回传 FastDFS上传响应快适合大批量场景架构复杂需要引入队列和任务管理如果你只是中小团队用户上传量又不是特别大我最推荐的是第一种上传时用 ImageMagick 生成好缩略图和原图一起存到 FastDFS。原因很简单——静态文件访问永远比动态计算可靠而且磁盘存储成本在当前硬件条件下完全可以接受。后面我两种方案都会讲但整体流程以第一种为落地主线。2. Linux 环境准备与依赖安装2.1 版本搭配和系统环境我这次使用的是一台 CentOS 7.9 的虚拟机2 核 4G 内存生产环境也够用。根据我踩过多次坑的经验版本搭配非常重要不同版本之间很容易出现编译或运行时报错。我验证过的一套稳定组合系统CentOS 7.9 64 位libfastcommon1.0.36FastDFS5.12fastdfs-nginx-module1.22Nginx1.18.0ImageMagick系统自带源版本即可如果你希望使用 FastDFS 6.x那么 libfastcommon 一定要匹配新版本不要混着用。版本不匹配最常见的表现就是编译时报“未定义引用”或者运行时提示找不到符号。2.2 安装基础编译依赖FastDFS 是 C 项目Nginx 也是 C 项目所以编译工具链是必须的。在 CentOS 上执行yum install -y gcc gcc-c make automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl openssl-devel libevent libevent-devel如果你是 Ubuntu/Debian对应包名类似apt-get install -y build-essential libpcre3 libpcre3-dev zlib1g-dev libssl-dev libevent-dev这里有几个需要解释的依赖gcc/gcc-c编译 FastDFS 源码用。pcre-devel、zlib-devel、openssl-devel编译 Nginx 时需要Nginx 的正则支持、gzip 压缩和 HTTPS 都会用到这些开发库。libevent-develFastDFS 的老版本在某些场景下会依赖 libevent特别是网络事件处理。这些依赖装好后最好先用getenforce看一下 SELinux 状态。如果返回 Enforcing建议先改成 permissive或者后续给相关目录配置 SELinux 规则。不是不能开而是刚入门时开着的 SELinux 会带来大量“权限不足”的坑排错会很痛苦。setenforce 0 sed -i s/^SELINUXenforcing/SELINUXpermissive/ /etc/selinux/config防火墙测试环境也可以先停掉systemctl stop firewalld systemctl disable firewalld2.3 编译安装 libfastcommonlibfastcommon 是 FastDFS 依赖的基础库必须先装。把源码包放到/usr/local/src下cd /usr/local/src tar zxvf libfastcommon-1.0.36.tar.gz cd libfastcommon-1.0.36 ./make.sh ./make.sh install编译安装完成后默认的安装路径是/usr/lib64/。但 FastDFS 编译时可能会去/usr/local/lib找这个库所以很多老朋友都会在这里踩坑。保险起见把库文件做两个软链ln -s /usr/lib64/libfastcommon.so /usr/local/lib/libfastcommon.so ln -s /usr/lib64/libfastcommon.so /usr/lib/libfastcommon.so然后再执行ldconfig这一步做完后续编译 FastDFS 会顺利很多。3. 编译安装 FastDFS 并创建数据目录3.1 编译安装过程同样把 FastDFS 源码放到/usr/local/srccd /usr/local/src tar zxvf FastDFS-5.12.tar.gz cd FastDFS-5.12 ./make.sh ./make.sh install安装完成后可执行文件会放到/usr/bin/包括以下常用命令fdfs_trackerdTracker 服务进程fdfs_storagedStorage 服务进程fdfs_monitor查看集群状态fdfs_upload_file测试上传文件fdfs_download_file测试下载文件fdfs_file_info查看文件信息配置文件模板在/etc/fdfs/目录下安装后自动生成但默认只有.sample后缀ls /etc/fdfs/ client.conf.sample storage.conf.sample tracker.conf.sample后续需要自己复制并修改。3.2 规划数据目录FastDFS 的目录结构其实有一定历史包袱刚接触的人容易搞混。建议统一把数据放在/data/fastdfs下mkdir -p /data/fastdfs/tracker mkdir -p /data/fastdfs/storage后面配置时要注意Tracker 的 base_path 指向/data/fastdfs/trackerStorage 的 base_path 指向/data/fastdfs/storage但 FastDFS 会在 Storage 的 base_path 下自动创建data子目录真正存文件的是/data/fastdfs/storage/data。这个细节在配置mod_fastdfs.conf时尤其关键很多人的 404 就是在这里埋下的。4. 配置 Tracker 和 Storage 并完成上传测试4.1 Tracker 配置复制示例配置并修改cp /etc/fdfs/tracker.conf.sample /etc/fdfs/tracker.conf vim /etc/fdfs/tracker.conf关键项port22122 base_path/data/fastdfs/trackerport是 Tracker 的服务端口客户端和 Storage 都要通过它来通信。base_path存放 Tracker 的日志和状态数据必须提前建好目录。http.server_port在 tracker.conf 里也存在但 FastDFS 自带 HTTP 服务已经基本废弃实际访问走 Nginx这里不用太关心。启动 Tracker/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf确认进程和端口ps -ef | grep fdfs netstat -lnpt | grep 22122看到 22122 端口监听就说明 Tracker 起来了。4.2 Storage 配置复制 Storage 配置cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage.conf vim /etc/fdfs/storage.conf我改动后的核心配置group_namegroup1 port23000 base_path/data/fastdfs/storage store_path_count1 store_path0/data/fastdfs/storage tracker_server127.0.0.1:22122 http.server_port8888解释几个关键点group_namegroup1单机部署就一个组。如果后面扩容同一组内的 Storage 是互相备份的关系不同组才是扩展容量。port23000Storage 的服务端口默认就是这个Storage 之间同步文件和客户端读写都用它。store_path0/data/fastdfs/storage这里写的是 Storage 的 base_pathFastDFS 会自动在下面创建 data 目录不要手动指定到 data 子目录。tracker_server127.0.0.1:22122告诉 Storage 去哪个 Tracker 注册。如果有多个 Tracker可以写多行。http.server_port8888这个端口是给 fastdfs-nginx-module 用的Nginx 监听 8888后续配置要保持一致。启动 Storage/usr/bin/fdfs_storaged /etc/fdfs/storage.conf然后查看 Storage 是否在 Tracker 上注册成功/usr/bin/fdfs_monitor /etc/fdfs/storage.conf如果输出里能看到类似storage 1: ip: 127.0.0.1 ... ACTIVE的状态就说明注册成功。如果看到OFFLINE或NOT ALIVE优先检查防火墙和tracker_server配置。4.3 配置 client 并测试上传FastDFS 自带的命令行工具fdfs_upload_file可以帮我们快速验证上传流程。首先生成客户端配置cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf vim /etc/fdfs/client.conf关键项base_path/data/fastdfs/client tracker_server127.0.0.1:22122base_path 目录记得创建mkdir -p /data/fastdfs/client然后用一张测试图片上传/usr/bin/fdfs_upload_file /etc/fdfs/client.conf /root/test.jpg正常会返回一个文件路径例如group1/M00/00/00/wKgfKmAtBLaAZxZfAAAH4_G0D4I875.jpg这个路径很有用。group1是组名M00是 store_path 的索引符号后面的/00/00/是二级目录最后的字符串是 file_id。FastDFS 就是通过这个路径定位到存储节点上的实际文件。5. 接入 Nginx 和 fastdfs-nginx-module 提供下载服务5.1 为什么要在 Storage 前面加 NginxFastDFS 的 Storage 自带一个简单的 HTTP 服务但性能和功能都比较弱生产环境一般不用。更常见的方式是在 Storage 前部署 Nginx通过 fastdfs-nginx-module 让 Nginx 直接读取 Storage 磁盘上的文件。这样做的好处有两个静态文件由 Nginx 直接返回不经过后端应用性能更高。后续可以在 Nginx 层加缓存、限流、日志等能力灵活很多。fastdfs-nginx-module 的原理并不复杂Storage 在本地保存文件后会生成对应的 file_id。当客户端来访问时Nginx 通过模块解析 file_id 中的组名、路径信息直接在配置的 store_path0 中查找文件并返回。5.2 编译 Nginx 并添加模块先编译安装 fastdfs-nginx-module。源码解压后需要修改一个 config 文件cd /usr/local/src tar zxvf fastdfs-nginx-module-1.22.tar.gz vim fastdfs-nginx-module-1.22/src/config我这边改成了这样ngx_module_incs/usr/include CORE_INCS$CORE_INCS $ngx_module_incs CORE_LIBS$CORE_LIBS -L/usr/local/lib -lfastcommon -lfdfsclient不同版本可能已经内置了正确路径但改一遍更稳妥。这个文件的作用是告诉 Nginx 编译时去哪里找 fastdfs 的头文件和库文件。准备好 Nginx 源码cd /usr/local/src tar zxvf nginx-1.18.0.tar.gz cd nginx-1.18.0还会用到 image_filter 模块做动态缩略图所以先把 gd 库装上yum install -y gd gd-devel然后配置 Nginx./configure \ --prefix/usr/local/nginx \ --with-http_image_filter_module \ --add-module/usr/local/src/fastdfs-nginx-module-1.22/src编译安装make make install注意--with-http_image_filter_module依赖 gd-devel如果没装会直接报错。如果不想用动态缩略图这个模块可以不编译但既然装了后面多一种方案。5.3 配置 mod_fastdfs.conffastdfs-nginx-module 需要一份自己的配置文件源码包里有一个mod_fastdfs.conf复制到/etc/fdfs/cp /usr/local/src/fastdfs-nginx-module-1.22/mod_fastdfs.conf /etc/fdfs/ vim /etc/fdfs/mod_fastdfs.conf核心配置base_path/data/fastdfs/storage tracker_server127.0.0.1:22122 storage_server_port23000 group_namegroup1 url_have_group_nametrue store_path_count1 store_path0/data/fastdfs/storage/data这里重点提醒store_path0必须写成/data/fastdfs/storage/data不是/data/fastdfs/storage。我一开始在这里掉了坑结果 Nginx 一直 404。url_have_group_nametrue表示访问 URL 里包含组名对应上传返回的group1/M00/...。storage_server_port23000是 Storage 的服务端口模块在某些场景下会回源到 Storage 获取文件信息。5.4 配置 Nginx server在/usr/local/nginx/conf/nginx.conf里加一个 serverserver { listen 8888; server_name _; location /group1/M00/ { ngx_fastdfs_module; } }注意 location 写的是/group1/M00/因为上传返回的文件路径就是从group1/M00/开始的。如果url_have_group_name设置成了 false那 location 要改成/M00/。启动 Nginx/usr/local/nginx/sbin/nginx然后通过 HTTP 访问刚才上传的测试文件curl -I http://127.0.0.1:8888/group1/M00/00/00/wKgfKmAtBLaAZxZfAAAH4_G0D4I875.jpg如果返回 200说明 Nginx 已经能直接从 FastDFS 读取文件。如果返回 404去/data/fastdfs/storage/logs/下的 storage 日志和 Nginx 的 error.log 里找原因大概率是store_path0路径或url_have_group_name配错了。6. 生成缩略图的两种落地方式6.1 方式一上传时用 ImageMagick 生成静态缩略图这是我推荐的生产方案。思路很直接上传原图时程序先调用 ImageMagick 生成指定尺寸的缩略图然后把原图和缩略图都上传到 FastDFS数据库里记录两个 file_id。先安装 ImageMagickyum install -y ImageMagick ImageMagick-devel安装完成后可以用convert -version确认版本。ImageMagick 最常用的缩略图命令是convert input.jpg -thumbnail 200x200^ -gravity center -extent 200x200 thumb_200x200.jpg这里解释一下参数-thumbnail 200x200^将图片缩放至至少 200x200然后按比例缩放^表示短边先满足目标尺寸可能会裁掉部分图片。-gravity center裁剪时从图片中心开始。-extent 200x200最终输出严格限制在 200x200配合上面的缩小算法实现“居中裁剪”。这其实是很多图床常见的 cover 模式不会拉伸图片又能保证每个缩略图尺寸统一。如果不想要裁剪只想保持比例可以去掉^和extentconvert input.jpg -thumbnail 200x200 thumb_200x200.jpg但这种方式得到的图片不一定是 200x200而是长边或宽边为 200 的等比例图界面布局如果不抠尺寸问题不大如果 UI 需要固定宽高建议用 cover 模式。接下来写一个简单的上传脚本把原图和缩略图都传上去。以 shell 脚本为例#!/bin/bash CLIENT_CONF/etc/fdfs/client.conf ORIGINAL_FILE$1 SIZE200x200 THUMB_FILE/tmp/thumb_${SIZE}.jpg if [ ! -f $ORIGINAL_FILE ]; then echo 原图不存在 exit 1 fi convert $ORIGINAL_FILE -thumbnail ${SIZE}^ -gravity center -extent $SIZE $THUMB_FILE ORIGINAAL_FILE_ID$(/usr/bin/fdfs_upload_file $CLIENT_CONF $ORIGINAL_FILE) THUMB_FILE_ID$(/usr/bin/fdfs_upload_file $CLIENT_CONF $THUMB_FILE) echo 原图 file_id: $ORIGINAAL_FILE_ID echo 缩略图 file_id: $THUMB_FILE_ID实际项目里你可能用的是 Java、Python 或 Go 的 FastDFS Client但核心逻辑一样先生成缩略图后上传。缩略图文件最好加上尺寸后缀比如_200x200.jpg方便人眼识别。这里有几个实操经验缩略图先生成再上传两个文件都只走一次 FastDFS 上传链路效率最高。如果先传原图再下载回来生成缩略图会增加磁盘和网络 IO。需要多种尺寸时可以用循环生成多个缩略图一次性上传。FastDFS 的 file_id 是根据文件内容生成的不同尺寸的图片 file_id 必然不同不用担心互相覆盖。建议在数据库里单独记录缩略图的 file_id不要试图通过原图 file_id 拼接缩略图 URL。FastDFS 的文件路径是内容寻址的不是你手动拼一个_200x200就能访问到的。6.2 方式二Nginx image_filter 动态生成缩略图再讲一种备选方案Nginx 的 image_filter 模块在访问时动态生成缩略图。这种方式对普通本地文件系统很友好配置也简单location /thumb/ { alias /data/images/; image_filter resize 200 200; image_filter_jpeg_quality 85; }访问/thumb/a.jpg时Nginx 会读取/data/images/a.jpg实时缩放到 200x200再返回给客户端。它省去了预先存储所有尺寸缩略图的空间很适合图片数量少、尺寸要求灵活的场景。但如果你用了 fastdfs-nginx-module就要特别注意了ngx_fastdfs_module不走 Nginx 的alias或root而是由模块自己根据 file_id 去 Storage 磁盘上定位文件。image_filter 在处理图片路径时拿不到普通文件路径所以两者直接放在同一个 location 里通常不会按你预期工作。如果一定要用动态缩略图我的建议是在一台只放 FastDFS 原图的 Nginx 后面再挂一层专门做缩略图处理的 Nginx 或反代。或者放弃 fastdfs-nginx-module在 Storage 上挂载一个普通目录用alias image_filter 的方式处理缩略图请求。但从可维护性来看我依然更倾向于方案一。动态缩略图看起来省事实际上每次请求都要消耗 CPU 做图片处理高并发时很容易把机器打满而且 Nginx 的 image_filter 对 GIF 动画只保留第一帧对 WebP、PNG 的支持也有细节坑。静态缩略图虽然上传时慢几十毫秒但访问时纯静态输出性能和稳定性都要好很多。6.3 缩略图路径的统一规划如果你选择了静态缩略图方案建议在一开始就定好命名规则。比较常见的做法是原图group1/M00/00/00/wKgfKmAtBLaAZxZfAAAH4_G0D4I875.jpg缩略图group1/M00/00/00/wKgfKmAtBLaAZxZfAAAH4_G0D4I875_200x200.jpg注意这个_200x200后缀只是我们约定俗成用于识别的名称实际存到 FastDFS 后返回的 file_id 并不是在原图 file_id 后面直接加后缀。原因是 FastDFS 的 file_id 是基于文件内容生成的缩略图内容不同生成的 ID 也不同。所以不要在前端写死“原图路径 尺寸后缀”来拼缩略图地址而是让后端返回数据库里已保存的缩略图地址。如果你们团队非要追求“一个 URL 能自动取到任意尺寸”那就得走动态缩略图方案或者自建一层 URL 映射服务。我个人的经验是绝大多数业务场景都有固定的几种缩略图尺寸没必要为“任意尺寸”过度设计。7. 常见问题与排查技巧7.1 典型问题速查表现象可能原因解决方法fdfs_trackerd 启动失败base_path 目录不存在、端口被占用创建目录检查 22122 端口是否被占用Storage 启动后 fdfs_monitor 显示 OFFLINEtracker_server 配错、防火墙拦截检查 Tracker 地址、关闭防火墙或放行对应端口Nginx 编译时报错找不到 fastdfs 头文件fastdfs-nginx-module 的 config 文件路径不对修改 src/config 中的 CORE_INCS上传成功但 HTTP 访问 404store_path0 配错、url_have_group_name 不一致、location 没匹配将 store_path0 指向 storage/data确认 location 前缀访问返回 403SELinux 开启、Nginx 用户无权限临时 setenforce 0 测试或调整目录权限ImageMagick convert 命令执行报错缺少依赖库或磁盘临时空间满安装 ImageMagick-devel清理 /tmp 空间缩略图无法访问只存了原图 file_id缩略图 file_id 未入库上传后记录缩略图 file_id前端使用正确地址7.2 几个值得单独说的避坑经验第一路径问题是最常见的坑。FastDFS 实际存储路径是/data/fastdfs/storage/data但 mod_fastdfs.conf 里写store_path0时容易写成/data/fastdfs/storage。还有 Storage 启动后会在 base_path 下生成 data 目录M00 符号链接也在这个目录里。建议配置完用ls -l /data/fastdfs/storage/data看一下确认 M00 符号链接存在再继续配 Nginx。第二不同机器时间要基本同步。FastDFS 的 Storage 和 Tracker 之间会校验时间戳时间差过大会报错。最简单的办法是安装 chrony 或 ntpd 做时间同步尤其多机部署时非常关键。第三上传测试时文件不能太大。默认配置里nginx请求体大小可能限制在 1M如果靠 Nginx 代理上传要注意client_max_body_size。我一般会这样设置client_max_body_size 50m;不然传大图时 Nginx 直接 413。第四用 ImageMagick 批量生成缩略图时单机 CPU 会明显上涨。建议上传接口做并发控制或者将缩略图生成任务丢到队列里异步执行。小团队数据量不大可以先同步处理但预留好队列扩展的接口。第五如果你在云服务器上部署记得安全组只放行必要端口。Tracker 的 22122、Storage 的 23000 只对内网开放对外只暴露 Nginx 的 8888 或 80 端口。FastDFS 本身没有完善的鉴权体系直接暴露这些端口风险很高。8. 写在最后的一点个人体会这套环境我反复搭过很多次最大的体会是FastDFS 本身很小坑基本集中在版本配套和路径配置上。libfastcommon 和 FastDFS 版本必须对应Nginx 编译模块时 config 文件要检查store_path0 要指向 data 目录。只要这三个点稳住整个搭建过程不会出太大的幺蛾子。缩略图方面我强烈建议你在项目一开始就定好方案。我见过不少项目先把原图传上去后期发现前端需要多种尺寸又开始补缩略图逻辑结果数据库里字段、文件同步、域名规则全部要改非常折腾。如果你还在调研阶段就从“上传时生成静态缩略图”入手把尺寸命名和 file_id 存储规划好后面基本一劳永逸。另外如果你的业务规模不大只有几万张图片其实完全可以用 MinIO 这类对象存储替代 FastDFS部署和客户端 SDK 都更友好。FastDFS 的优势在于多机分组同步、轻量、可控性高适合需要自建文件服务并且有一定运维基础的朋友。选型没有绝对的好坏关键是想清楚场景和团队能力。今天这篇就写到这里。如果你照着步骤搭的过程中遇到问题建议先看日志——Tracker 看/data/fastdfs/tracker/logs/trackerd.logStorage 看/data/fastdfs/storage/logs/storaged.logNginx 看/usr/local/nginx/logs/error.log。日志里通常写得比你想的清楚得多。
返回列表