一、FastDFS 集群
注意:本部分与一、的 GlusterFS 是完全不同的产品。 FastDFS 不是 GlusterFS 的组件,也不能挂载成本地目录。请勿混淆二者的概念和命令。
1.1 FastDFS 定位与设计取舍
FastDFS 是由国内开发者余庆(happyfish100)用 C 语言编写的开源轻量级分布式文件存储系统,专为海量中小文件(图片、文档、小视频、附件)的存储与访问而设计。
核心定位:
- 专为中小文件优化:官方推荐的文件大小范围是 4KB ~ 500MB。对超大文件(GB 以上)和极小文件(几百字节)都不是最优选择。
- 一次写入、多次读取(WORM):FastDFS 的文件不支持随机修改(只能上传、下载、删除,以及 appender 文件的追加)。这与图片/附件类业务的访问模式完美匹配。
- 轻量级:只有 Tracker 和 Storage 两种角色,两个二进制程序、三个配置文件,部署与运维成本极低。没有复杂的元数据集群、没有 Paxos/Raft。
- 不做通用文件系统:刻意不实现 POSIX 语义,把复杂度留给应用层。
与通用分布式文件系统(GlusterFS/CephFS/HDFS)的关键差异:
| 差异点 | 通用分布式文件系统(如 GlusterFS) | FastDFS |
|---|---|---|
| 访问方式 | 可 mount 成本地目录,任何程序透明使用 | 不能 mount,只能通过客户端 API(C/Java/PHP/Python)或 HTTP 访问 |
| POSIX 语义 | 支持(open/read/write/seek/lock/chmod) | 不支持。没有 open/seek/write 语义 |
| 目录树 | 有完整的层级目录树,可 ls、mkdir、mv |
没有用户可见的目录树。文件路径由系统自动生成(file_id),应用只能保存这个 id |
| 文件名 | 用户指定 | 系统生成(file_id 编码了位置信息),原始文件名需应用自己存到数据库 |
| 随机改写 | 支持 | 不支持(普通文件不可改;appender 文件可追加/截断) |
| 元数据 | 分布式或集中式元数据管理 | 几乎没有元数据服务,位置信息编码在 file_id 里 |
| 复杂度 | 中到高 | 低 |
| 适合的文件大小 | 中大文件 | 中小文件 |
这个取舍的意义:正因为放弃了 POSIX 和目录树,FastDFS 才能做到"存文件只需一次网络往返、无需查元数据",从而在海量小文件场景下比通用文件系统快得多、也简单得多。代价是应用必须改造:上传后拿到 file_id,自己把 原始文件名 -> file_id 的映射存进数据库(MySQL/Redis)。
典型适用场景:
- 电商/社交网站的商品图片、头像、相册
- 用户上传的附件、合同、发票扫描件、报表
- 短视频、音频片段
- 一次生成多次访问的静态资源
不适合的场景:需要挂载成目录给遗留程序用、需要随机改写(数据库文件、VM 镜像)、超大文件(几十 GB 的备份包)、需要 S3 兼容 API。
1.2 架构:Tracker 与 Storage 两种角色
+----------------+ +----------------+
| Tracker 1 | <--> | Tracker 2 | (彼此不通信,无状态,客户端轮询)
| (22122/tcp) | | (22122/tcp) |
+----------------+ +----------------+
^ ^ ^
1.问"存哪"| |心跳/汇报状态 |
| v v
+-----------------+ +-------------------------------------------+
| | Storage 集群 |
| | group1 group2 |
| | +----------+ +----------+ +----------+ |
+--v---+ 2.直连上传 | |storage1-1| |storage1-2| |storage2-1| |
|客户端| --------------->| |23000/tcp | |23000/tcp | |23000/tcp | |
+------+ | +----------+ +----------+ +----------+ |
^ | ^___同组互相同步___^ |
| 3.返回 file_id | (同组内容完全相同,互为备份) |
+---------------------+-------------------------------------------+
1.2.1 Tracker(调度服务器)的职责
| 职责 | 说明 |
|---|---|
| 集群状态管理 | 接收所有 Storage 的心跳与状态汇报,维护"哪些 group、哪些 storage、各自剩余空间、是否 ACTIVE"的内存表 |
| 上传调度 | 客户端上传前先问 Tracker:"我该把文件传给谁?" Tracker 按 store_lookup 策略选出一个 group 和一个 storage 返回 |
| 下载调度 | 客户端下载前问 Tracker:"这个 file_id 我该找谁下载?" Tracker 按 download_server 策略在该 group 内选一个 storage |
| 同组同步协调 | 告知新加入的 Storage 应该从哪个源节点做全量同步 |
Tracker 是无状态的、可水平扩展的——这是 FastDFS 架构上最重要的一点:
- Tracker 不存储任何文件,也不存储文件索引。它的所有信息都是 Storage 主动汇报上来的、放在内存里的运行时状态。
- 多个 Tracker 之间彼此不通信、不选主、不同步数据。每个 Tracker 都独立地接收所有 Storage 的汇报,因此各自都拥有完整的集群视图。
- 客户端配置多个
tracker_server,随机/轮询选一个提问。任一 Tracker 宕机,客户端换一个即可,无需切换 VIP、无需选举。 - 因此 Tracker 的高可用非常廉价:部署 2 个(或以上)就完成了,不需要 keepalived。
- Tracker 挂了会怎样?已知 file_id 的下载可以不经 Tracker 直接访问 Storage(HTTP 方式),但新上传会失败(找不到 storage)。
重要澄清:源文档提到"192.168.1.221 被选为 leader 节点",这里的 leader 是指 Tracker 集群中被推选出的那个"负责协调 Storage 同步与统计的 leader tracker"(由各 Storage 汇报后按规则选出,通常是 IP/端口最小或最早的那个),不是"主 Tracker",也不涉及数据主从。所有 Tracker 都同等地对外服务,leader 只多承担同组同步的源节点分配工作。因此不要误解为"FastDFS 有主备 Tracker"。
1.2.2 Storage(存储服务器)的职责
| 职责 | 说明 |
|---|---|
| 实际存储文件 | 把上传的文件落盘到 store_path 下的两级目录中 |
| 生成 file_id | 上传成功后生成并返回 file_id |
| 提供下载 | 通过 FastDFS 协议或 HTTP(配 Nginx)提供文件读取 |
| 同组同步 | 主动把本机新增/删除的文件同步给同组的其它 Storage |
| 心跳汇报 | 定期向所有 Tracker 汇报自身状态、磁盘空间、统计信息 |
1.2.3 group(卷)的概念
group(也叫 volume)是 FastDFS 组织 Storage 的核心概念:
group1: storage1-1 (100GB) + storage1-2 (100GB) + storage1-3 (80GB)
=> 三台机器内容完全相同(互为备份)
=> group1 的可用容量 = min(100, 100, 80) = 80GB ← 取最小值!
group2: storage2-1 (500GB) + storage2-2 (500GB)
=> group2 的可用容量 = 500GB
集群总容量 = group1 + group2 = 80 + 500 = 580GB
关键规则:
- 同组内所有 Storage 存储的文件完全相同,互为备份。组内节点数即为副本数。
- 组的容量取决于组内容量最小的那个节点(木桶效应)。因此同组机器的磁盘配置必须一致,否则浪费空间。
- 组内增加节点 = 提升冗余度和读性能,不增加容量。
- 组是扩容的单位:要增加集群总容量,必须新增一个 group。
- 不同 group 之间完全独立、互不同步,一个 group 故障不影响其它 group。
- 一个 group 内 Storage 数量建议 2~3 台(副本数),不宜过多(同步开销随节点数增长)。
- file_id 的第一段就是 group 名,因此文件一旦上传,它属于哪个 group 就固定了。
1.2.4 同步机制:binlog 与主动同步
FastDFS 同组内的同步是基于 binlog 的、异步的、点对点的最终一致同步。
工作过程:
- 客户端把文件上传到 group1 中的 storage1-1(源服务器)。
- storage1-1 落盘成功后立即返回 file_id 给客户端(不等同步完成,因此是异步的)。
- storage1-1 把这次操作("新增了文件 xxx")记录到自己的 binlog 文件中:
$base_path/data/sync/binlog.000。 - group1 内的每一个其它 Storage(storage1-2、storage1-3)都各自启动一个同步线程,主动连接 storage1-1,读取它的 binlog,把自己缺的文件拉过来。这是"拉模式"(对每个源服务器一个线程)。
- 每个 Storage 用 mark 文件记录"我从某个源服务器同步到 binlog 的哪个位置了":
$base_path/data/sync/{源IP}_{端口}.mark。 - 断点续传:重启或网络恢复后,从 mark 文件记录的偏移继续同步,不会重复或漏掉。
相关文件:
# binlog 与 mark 文件所在目录
ls -l $base_path/data/sync/
# 例如 /home/dfs/data/sync/
# binlog.000 本机的操作日志
# binlog.index 当前 binlog 序号
# 192.168.1.220_23000.mark 从 220 同步的进度标记
binlog 内容格式(每行一条):
1553836564 C M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
时间戳 操作类型 文件相对路径
操作类型:C = create(源服务器上传)、c = create(同步来的)、D = delete、d = 同步删除、A = append、a = 同步 append、T = truncate、U = 更新。
mark 文件内容:
binlog_index=0
binlog_offset=1234567
need_sync_old=1
sync_old_done=1
until_timestamp=1553836564
scan_row_count=8900
sync_row_count=8900
| 字段 | 含义 |
|---|---|
binlog_index |
当前同步到第几个 binlog 文件 |
binlog_offset |
同步到该文件的字节偏移,断点续传的关键 |
need_sync_old |
是否需要同步历史文件(新节点加入时为 1) |
sync_old_done |
历史文件是否已同步完成 |
until_timestamp |
已同步到的时间点 |
scan_row_count / sync_row_count |
已扫描/已同步的记录数 |
同步的重要特性与影响:
- 异步 = 存在同步延迟。上传成功后立即用另一个节点的 IP 去下载,可能 404。这是需要
fastdfs-nginx-module的根本原因(详见 3.7)。 - 时间同步至关重要:同步进度是以时间戳为依据的,节点间时间不一致会导致同步逻辑混乱甚至数据丢失。必须部署 NTP/chrony。
- 新节点加入时的全量同步:新 Storage 加入已有 group 后,Tracker 会指定一个源节点,新节点从它做全量同步(状态显示为
SYNCING),完成后才变ACTIVE。 - 可通过
sync_start_time/sync_end_time限定同步的时间窗(如只在夜间同步),用sync_interval控制同步节奏。
1.2.5 trunk 文件与合并存储
问题:如果单个文件只有几 KB,而文件系统的块大小是 4KB,那么大量极小文件会造成:
- 严重的空间浪费(每个文件至少占一个块);
- inode 消耗巨大(可能磁盘还有空间但 inode 用尽);
- 元数据操作(创建/删除文件)开销占比过高。
解决方案:合并存储(trunk file)。把多个小文件合并写入到一个大文件(trunk 文件,默认 64MB)内部,file_id 中记录该小文件在 trunk 内的偏移量与长度。这样:
- 一个 trunk 文件只占一个 inode,承载成千上万个小文件;
- 空间按实际字节分配,几乎无浪费;
- 减少文件系统的元数据压力。
配置(在 storage.conf 中):
# 是否启用合并存储
use_trunk_file = true
# trunk 文件中最小分配单元(字节),默认 256
slot_min_size = 256
# 小于此值的文件才使用合并存储(不能超过 trunk_file_size 的 1/2),默认 16KB
slot_max_size = 16KB
# trunk 文件大小,建议 64MB
trunk_file_size = 64MB
# 是否预先创建 trunk 文件
trunk_create_file_advance = false
trunk_create_file_time_base = 02:00
trunk_create_file_interval = 86400
# 预创建 trunk 文件的总大小阈值
trunk_create_file_space_threshold = 20G
# 启动时是否检查 trunk 空闲空间(建议 true)
trunk_init_check_occupying = false
trunk_init_reload_from_binlog = false
trunk_compress_binlog_min_interval = 0
在 tracker.conf 中还需配合:
# 是否使用 trunk 文件的服务器(由 tracker 指定一台 storage 作为 trunk server 统一分配空间)
use_trunk_file = true
slot_min_size = 256
slot_max_size = 16MB
trunk_file_size = 64MB
# 是否由 tracker 统一分配 trunk 空间(推荐 true)
store_server = 0
注意:合并存储会引入 trunk server 这个中心分配者,增加复杂度;且被删除小文件的空间回收(碎片整理)不够完善。只有在文件确实非常小(< 16KB)且数量极大(亿级)时才建议启用,一般场景保持默认 false 即可。
1.3 上传与下载的完整流程
1.3.1 上传流程
客户端 Tracker Storage(group1/storage1-1)
| | |
|--1. 请求上传(问存哪)--->| |
| | 按 store_lookup 选 group |
| | 按 store_server 选 storage |
|<--2. 返回 storage 的 IP/端口 + store_path 索引 --------|(此步只与 tracker 交互)
| |
|--3. 直连 storage,发送文件内容 + 扩展名 ---------------->|
| | 4. 选 store_path
| | 5. 生成文件名(编码位置信息)
| | 6. 落盘到两级目录
| | 7. 写 binlog
|<--8. 返回 file_id: group1/M00/00/00/wKgB3Fyo... -------|
| |
| 9. 应用把 file_id 存入数据库 |--10. 同组其它 storage
| | 主动来拉取(异步)
关键点:
- 数据不经过 Tracker。Tracker 只回答"去找谁",实际文件流是客户端与 Storage 直连,因此 Tracker 不是带宽瓶颈。
- 返回 file_id 就算成功,此时同组其它节点还没有这个文件(异步同步)。
- 客户端必须保存 file_id,因为这是唯一能找回文件的凭据。原始文件名 FastDFS 不保存(需用
fdfs_upload_file的 metadata 功能或自行入库)。
1.3.2 file_id 的组成结构逐段解析
group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
| | | | |
| | | | +-- 文件名(Base64 编码了源 IP、时间戳、文件大小、CRC32 等)
| | | +----- 二级目录(00 ~ FF,十六进制)
| | +-------- 一级目录(00 ~ FF,十六进制)
| +------------ 虚拟磁盘路径(M00 对应 store_path0,M01 对应 store_path1 ...)
+------------------ group 名(卷名),决定去哪个组找这个文件
逐段说明:
| 段 | 示例 | 含义 |
|---|---|---|
| group 名 | group1 |
文件所在的组。下载时据此确定该找哪一组的 Storage。这也是为什么改了 group_name 后老文件就访问不到了 |
| 虚拟磁盘路径 | M00 |
对应 storage.conf 中的 store_path0;M01 对应 store_path1,依此类推。这是一层抽象:物理路径可以变,file_id 不变 |
| 两级目录 | 00/00 |
Storage 在 store_path 下预先创建了 256 × 256 = 65536 个目录(由 subdir_count_per_path=256 决定),用于把文件散列开,避免单目录文件数过多 |
| 文件名 | wKgB3FyovhGAY8lJAAAD3C8hfQo2554045 |
由 Storage 生成,是 Base64 编码后的结果,内部编码了:源 Storage 的 IP 地址(4字节)+ 文件创建时间戳(4字节)+ 文件大小(8字节)+ 文件 CRC32 校验值(4字节)+ 随机数(2字节),最后可能带上传时指定的扩展名 |
由于文件名自带了源 IP 与时间戳,Storage 拿到 file_id 就能自己算出"这个文件是不是我这里的、是什么时候传的",这正是 fastdfs-nginx-module 能实现"本地没有就去源节点取"的技术基础。也正因如此,FastDFS 不需要任何元数据数据库。
解析 file_id 的工具:
fdfs_file_info /etc/fdfs/client.conf group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
source storage id: 0
source ip address: 192.168.1.220
file create timestamp: 2019-03-13 14:35:29
file size: 988
file crc32: 791805962 (0x2F217D0A)
对应物理路径为:
{store_path0}/data/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
注意物理路径中 M00 被替换成了 data——这是虚拟磁盘路径与物理路径的映射关系。
1.3.3 下载流程
客户端 Tracker Storage(group1 内任一节点)
| | |
|--1. 带着 file_id 询问 --->| |
| | 解析 group1 -> 找到该组 |
| | 按 download_server 策略选一台 |
|<--2. 返回 storage 地址 --| |
| |
|--3. 直连 storage 请求文件(带 file_id)---------------->|
| | 4. M00 -> store_path0
| | 5. 定位 data/00/00/文件名
|<--6. 返回文件内容 -------------------------------------|
关键点:
- 同组任意节点都可以提供下载服务(因为同组内容相同),Tracker 会做负载均衡。
- Tracker 通过
download_server参数控制策略:0轮询(默认)、1总是从源 Storage 下载。 - 如果用 HTTP + Nginx 方式访问,则完全不需要经过 Tracker:直接
http://storage_ip/group1/M00/00/00/xxx,Nginx 通过 mod_fastdfs 模块把 URL 映射到本地文件。 - 同步延迟的问题:刚上传的文件如果去访问一个尚未同步到的节点,就会 404。
fastdfs-nginx-module通过解析 file_id 中的源 IP,在本地找不到时代理到源 Storage 去取,从而屏蔽了这个问题(详见 3.7)。
1.4 环境准备与安装
1.4.1 服务器规划
源文档的双节点规划(每台机器同时兼任 Tracker 与 Storage,属于实验/小规模部署方式):
| 角色 | IP 地址 | 主机名 |
|---|---|---|
| 节点 1 | 192.168.1.220 | server-node1 |
| 节点 2 | 192.168.1.221 | server-node2 |
系统版本要求:CentOS 7.x 以上。
详细的角色与端口规划表:
| 主机名 | IP | 角色 | 监听端口 | 数据目录 |
|---|---|---|---|---|
| server-node1 | 192.168.1.220 | Tracker + Storage(group1) + Nginx | 22122、23000、80/8888 | /home/dfs(base_path)、/home/dfs(store_path0) |
| server-node2 | 192.168.1.221 | Tracker + Storage(group1) + Nginx | 22122、23000、80/8888 | /home/dfs(base_path)、/home/dfs(store_path0) |
生产环境更规范的规划(Tracker 与 Storage 分离、多 group):
| 主机名 | IP | 角色 | group | 端口 | 数据盘 |
|---|---|---|---|---|---|
| tracker1 | 192.168.1.210 | Tracker + Nginx(反代) | - | 22122、80 | 系统盘即可 |
| tracker2 | 192.168.1.211 | Tracker + Nginx(反代) | - | 22122、80 | 系统盘即可 |
| storage1 | 192.168.1.220 | Storage + Nginx | group1 | 23000、8888 | /dev/sdb → /data/fastdfs1 |
| storage2 | 192.168.1.221 | Storage + Nginx | group1 | 23000、8888 | /dev/sdb → /data/fastdfs1 |
| storage3 | 192.168.1.222 | Storage + Nginx | group2 | 23000、8888 | /dev/sdb → /data/fastdfs1 |
| storage4 | 192.168.1.223 | Storage + Nginx | group2 | 23000、8888 | /dev/sdb → /data/fastdfs1 |
1.4.2 系统环境准备
以下操作在两台主机上均需执行(主机名配置按节点区分)。
(1)关闭 firewalld
CentOS 7(firewalld):
systemctl stop firewalld
systemctl disable firewalld
生产环境应精细放行而非关闭:
firewall-cmd --permanent --add-port=22122/tcp # Tracker
firewall-cmd --permanent --add-port=23000/tcp # Storage
firewall-cmd --permanent --add-port=8888/tcp # Storage 侧 Nginx HTTP
firewall-cmd --permanent --add-port=80/tcp # 对外统一入口 Nginx
firewall-cmd --reload
firewall-cmd --list-ports
CentOS 6(iptables,仅适用于 CentOS 6):
iptables -I INPUT -p tcp --dport 22122 -j ACCEPT
iptables -I INPUT -p tcp --dport 23000 -j ACCEPT
iptables -I INPUT -p tcp --dport 8888 -j ACCEPT
iptables -I INPUT -p tcp --dport 80 -j ACCEPT
service iptables save
service iptables restart
# 实验环境可直接关闭
service iptables stop
chkconfig iptables off
FastDFS 端口清单:
| 端口 | 服务 | 说明 |
|---|---|---|
| 22122 | fdfs_trackerd | Tracker 服务端口(tracker.conf 的 port)。Storage 和客户端都要连它 |
| 23000 | fdfs_storaged | Storage 服务端口(storage.conf 的 port)。客户端上传下载、同组同步都走这里 |
| 8888 | Nginx(Storage 侧) | HTTP 下载端口(storage.conf 的 http.server_port 与 mod_fastdfs.conf 一致) |
| 80 | Nginx(Tracker 侧反代) | 对外统一入口 |
(2)关闭 selinux
临时关闭:
setenforce 0
永久关闭:修改 /etc/sysconfig/selinux 配置文件,将:
SELINUX=enforcing
改为:
SELINUX=disabled
/etc/sysconfig/selinux通常是/etc/selinux/config的软链接,改任一个都可以。修改后需重启才完全生效,故一般配合setenforce 0立即生效。
(3)设置主机名
节点 1(192.168.1.220):
hostnamectl set-hostname server-node1 --static
节点 2(192.168.1.221):
hostnamectl set-hostname server-node2 --static
CentOS 6(仅适用于 CentOS 6):
vim /etc/sysconfig/network
NETWORKING=yes
HOSTNAME=server-node1
hostname server-node1
(4)配置 hosts
编辑 /etc/hosts,在文件末尾添加两行,建立 IP 与主机名的映射:
vim /etc/hosts
192.168.1.220 server-node1
192.168.1.221 server-node2
注意:两行分别对应各自节点,第二行应为
server-node2。两台机器的 hosts 内容应完全相同(都包含这两行)。
补充:FastDFS 的配置文件中
tracker_server建议直接写 IP而不是主机名(老版本对主机名支持有限),hosts 主要用于运维便利。
(5)时间同步(重要,不可省略)
FastDFS 的同组同步依赖时间戳判断进度,节点间时间不同步会导致同步异常、文件丢失或重复同步。
CentOS 7:
yum -y install chrony
systemctl start chronyd
systemctl enable chronyd
timedatectl set-timezone Asia/Shanghai
chronyc sources -v
date
CentOS 6(仅适用于 CentOS 6):
yum -y install ntp
service ntpd start
chkconfig ntpd on
ntpdate ntp1.aliyun.com
ntpq -p
详见 08-03-时间同步与Xinetd.md
(6)准备数据盘(生产环境)
不要把 FastDFS 的数据放在系统盘上。
# 分区、格式化、挂载
fdisk /dev/sdb # n -> p -> 1 -> 回车 -> 回车 -> w
mkfs.xfs /dev/sdb1
mkdir -p /data/fastdfs1
mount -o noatime /dev/sdb1 /data/fastdfs1
echo '/dev/sdb1 /data/fastdfs1 xfs defaults,noatime 0 0' >> /etc/fstab
mount -a
df -hT
详见 03-01-磁盘管理与文件系统.md
目录规划:
| 目录 | 对应配置项 | 用途 |
|---|---|---|
/home/dfs 或 /data/fastdfs/base |
base_path(tracker/storage 各自的) |
存放日志和数据元信息(binlog、mark 文件、pid 文件)。不存实际文件 |
/data/fastdfs1 |
store_path0 |
实际存放文件的目录。多盘时配 store_path1、store_path2... |
/etc/fdfs/ |
- | 配置文件目录 |
$base_path/logs/ |
- | 日志目录(trackerd.log、storaged.log) |
$base_path/data/ |
- | tracker 的集群状态文件 / storage 的 binlog 与 mark 文件 |
所有 base_path 和 store_path 目录必须先手工创建,FastDFS 不会自动创建,路径不存在会直接启动失败:
mkdir -p /home/dfs
mkdir -p /data/fastdfs1
chmod 755 /home/dfs /data/fastdfs1
1.4.3 安装 FastDFS
方式一:使用现成的安装脚本(源文档做法)
分别在两个节点上执行安装。
节点 1(192.168.1.220):
cd fastdfs
./install.sh
安装完成后查看相关进程:
netstat -tnlp
节点 2(192.168.1.221):
cd fastdfs
./install.sh
安装完成后查看相关进程:
netstat -tnlp
install.sh是打包好的一键安装脚本(内部完成依赖安装、编译、配置文件复制等)。下面给出手工编译安装的完整过程,便于理解脚本做了什么,以及在没有该脚本时自行部署。
方式二:手工编译安装(完整步骤)
第一步:安装编译依赖
yum -y install git gcc gcc-c++ make automake autoconf libtool pcre pcre-devel \
zlib zlib-devel openssl-devel wget vim net-tools perl unzip
第二步:编译安装 libfastcommon(FastDFS 的基础公共库,必须先装)
libfastcommon 提供了 FastDFS 依赖的基础函数库(字符串处理、socket、日志、哈希表、ini 解析等)。FastDFS 无法脱离它编译,且版本需与 FastDFS 匹配(版本不匹配是编译失败的最常见原因)。
cd /usr/local/src
# 下载(也可从 GitHub release 下载 tar 包)
git clone https://github.com/happyfish100/libfastcommon.git --depth 1
cd libfastcommon
./make.sh
./make.sh install
安装结果:
# 库文件安装到
ls -l /usr/lib64/libfastcommon.so
ls -l /usr/lib/libfastcommon.so
# 头文件安装到
ls -l /usr/include/fastcommon/
# 若报找不到 libfastcommon.so,需要建软链接或更新 ld 缓存
ln -s /usr/lib64/libfastcommon.so /usr/local/lib/libfastcommon.so
echo '/usr/lib64' > /etc/ld.so.conf.d/fastdfs.conf
ldconfig
第三步:编译安装 fastdfs
cd /usr/local/src
git clone https://github.com/happyfish100/fastdfs.git --depth 1
cd fastdfs
./make.sh
./make.sh install
安装结果:
# 可执行文件安装到 /usr/bin/
ls /usr/bin/ | grep fdfs
fdfs_appender_test
fdfs_appender_test1
fdfs_append_file
fdfs_crc32
fdfs_delete_file
fdfs_download_file
fdfs_file_info
fdfs_monitor
fdfs_storaged
fdfs_test
fdfs_test1
fdfs_trackerd
fdfs_upload_appender
fdfs_upload_file
# 配置文件样例安装到 /etc/fdfs/
ls /etc/fdfs/
# tracker.conf.sample storage.conf.sample client.conf.sample storage_ids.conf.sample
# 服务脚本安装到 /etc/init.d/
ls /etc/init.d/ | grep fdfs
# fdfs_trackerd fdfs_storaged
第四步:复制配置文件模板(去掉 .sample 后缀)
cd /etc/fdfs/
cp tracker.conf.sample tracker.conf
cp storage.conf.sample storage.conf
cp client.conf.sample client.conf
# fastdfs 源码目录下的 http.conf 和 mime.types 也需要复制(Nginx 模块要用)
cp /usr/local/src/fastdfs/conf/http.conf /etc/fdfs/
cp /usr/local/src/fastdfs/conf/mime.types /etc/fdfs/
ls -l /etc/fdfs/
第五步:创建必需的目录
mkdir -p /home/dfs # base_path(tracker 与 storage 共用或各自独立)
mkdir -p /data/fastdfs1 # store_path0(生产环境用独立数据盘)
1.5 配置文件逐项详解
1.5.1 tracker.conf(Tracker 配置)
两个节点均需配置。
编辑 tracker 配置文件:
vim /etc/fdfs/tracker.conf
源文档所做的修改:将存储组名称:
store_group=group2
修改为:
store_group=group1
节点 2(192.168.1.221)执行同样的修改。 注意:
store_group只在store_lookup=1(指定组)时才生效,且这里指定的组名必须与storage.conf中的group_name一致,否则上传会失败。
完整的 tracker.conf 关键项详解:
# ========== 基础 ==========
# 配置文件是否禁用。true 表示这个配置文件无效(用于批量管理时临时禁用)
disabled = false
# 绑定的本机 IP 地址。留空表示监听所有网卡(0.0.0.0)
# 多网卡机器若只想监听内网,填写内网 IP;填错会导致其它节点连不上
bind_addr =
# Tracker 服务监听端口,默认 22122
port = 22122
# 连接超时时间(秒),针对 socket 连接
connect_timeout = 30
# 网络超时时间(秒),发送/接收数据的超时
network_timeout = 60
# ========== 路径 ==========
# base_path:存放【日志和数据元信息】的基础路径。!!必须提前手工创建,否则启动失败!!
# 目录下会自动生成 data/ 和 logs/ 两个子目录
base_path = /home/dfs
# ========== 连接与线程 ==========
# 最大并发连接数
max_connections = 1024
# 接收/发送队列内存上限
accept_threads = 1
work_threads = 4
min_buff_size = 8KB
max_buff_size = 128KB
# ========== 上传调度策略(核心) ==========
# store_lookup:上传时选择 group(卷)的策略
# 0 = round robin 轮询所有 group(默认,推荐,各组均衡增长)
# 1 = specify group 指定一个组,由下面的 store_group 指定
# 2 = load balance 选择剩余空间最大的组(新组会被打爆,慎用)
store_lookup = 0
# 当 store_lookup=1 时生效,指定要写入的组名(必须与 storage.conf 的 group_name 一致)
store_group = group1
# store_server:选定 group 后,在组内选择哪台 storage 来接收上传
# 0 = round robin 轮询(默认)
# 1 = 按 IP 排序,取第一台(作为源,其余同步)
# 2 = 按优先级排序(upload_priority 小的优先)
store_server = 0
# store_path:选定 storage 后,选择哪个 store_path(多盘时)
# 0 = round robin 轮询(默认)
# 2 = load balance 选剩余空间最大的路径
store_path = 0
# download_server:下载时在组内选择哪台 storage
# 0 = round robin 轮询(默认,负载均衡)
# 1 = 总是从源 storage 下载(可避免同步延迟导致的 404,但源节点压力大)
download_server = 0
# ========== 空间预留(重要) ==========
# 为系统或其它用途预留的存储空间。当组内所有 storage 的剩余空间都小于此值时,
# 该组将【不再接受上传】,报 "no available storage server"
# 可写绝对值(如 10G)或百分比(如 20%)
reserved_storage_space = 20%
# ========== 日志 ==========
# 日志级别:emerg | alert | crit | error | warn | notice | info | debug
log_level = info
# 运行 tracker 的用户与组,留空表示当前用户(一般留空即以 root 运行)
run_by_group =
run_by_user =
# ========== 访问控制 ==========
# 可以连接本 tracker 的 IP 白名单。* 表示不限制
# 支持形式:* 或 192.168.1.[1-100] 或 192.168.1.* 或 host[01-08,20-25].domain.com
allow_hosts = *
# 同步内存日志到磁盘的时间间隔(秒)
sync_log_buff_interval = 10
# ========== Storage 检查与心跳 ==========
# 检测 storage 存活的超时时间(秒),超过则标记为不可用
check_active_interval = 120
# 线程栈大小,不小于 64KB
thread_stack_size = 64KB
# 是否根据 IP 调整 storage 的优先级(新 storage IP 变化时用)
storage_ip_changed_auto_adjust = true
# ========== 同步相关 ==========
# storage 同步文件的最大延迟(秒),仅用于统计与判断,默认 1 天
storage_sync_file_max_delay = 86400
# 同步单个文件的最大时间(秒)
storage_sync_file_max_time = 300
# ========== 合并存储(trunk)==========
use_trunk_file = false
slot_min_size = 256
slot_max_size = 16MB
trunk_file_size = 64MB
trunk_create_file_advance = false
trunk_create_file_time_base = 02:00
trunk_create_file_interval = 86400
trunk_create_file_space_threshold = 20G
trunk_init_check_occupying = false
trunk_init_reload_from_binlog = false
trunk_compress_binlog_min_interval = 0
# ========== storage id 模式 ==========
# 是否使用 storage server ID 代替 IP(IP 可能变化的环境用)
use_storage_id = false
storage_ids_filename = storage_ids.conf
# id 类型:ip 或 id
id_type_in_filename = ip
# ========== 文件去重与回收 ==========
# 是否将文件的 slave(缩略图等)与主文件放一起
store_slave_file_use_link = false
# 是否定时轮转 error log,true 表示每天生成一个新的错误日志文件
rotate_error_log = false
# 轮转时间点
error_log_rotate_time = 00:00
# 是否压缩旧日志
compress_old_error_log = false
compress_error_log_days_before = 7
# 按大小轮转(0 表示不按大小轮转)
rotate_error_log_size = 0
# 日志保留天数(0 表示不删除)
log_file_keep_days = 0
# ========== HTTP(tracker 内置 http 已废弃,用 nginx 代替)==========
# 是否使用 tracker 自带的 http 服务(已不推荐,保持默认)
http.server_port = 8080
http.check_alive_interval = 30
http.check_alive_type = tcp
http.check_alive_uri = /status.html
重点参数总结:
| 参数 | 取值/说明 | 影响 |
|---|---|---|
bind_addr |
留空监听全部网卡,或填指定 IP | 填错会导致 Storage/客户端连不上,是排错重点 |
port |
22122 | 需与所有 storage.conf、client.conf 中的 tracker_server 端口一致 |
base_path |
/home/dfs |
必须先手工创建,存放 logs/ 和 data/ |
store_lookup |
0 轮询 / 1 指定组 / 2 选空间最大 | 决定新文件写入哪个 group。多组扩容时用 0 保持均衡;刚加新组想快速填充可临时用 2 |
store_group |
组名 | 仅 store_lookup=1 时生效 |
store_server |
0 轮询 / 1 按 IP 首台 / 2 按优先级 | 决定组内哪台接收上传 |
store_path |
0 轮询 / 2 空间最大 | 多盘时决定写哪块盘 |
download_server |
0 轮询 / 1 从源节点 | 设为 1 可规避同步延迟导致的 404 |
reserved_storage_space |
20% 或 10G | 空间预留。磁盘快满时上传报 no available storage server 就是这个在起作用 |
run_by_group / run_by_user |
留空为当前用户 | 若指定非 root 用户,必须确保该用户对 base_path 有读写权限,否则启动失败 |
allow_hosts |
* 或 IP 段 |
访问白名单,安全加固时收紧 |
log_level |
info / debug | 排错时改 debug |
rotate_error_log |
false / true | 生产建议 true,防止日志无限增长撑满磁盘 |
check_active_interval |
120 秒 | Storage 超过此时间无心跳则被标记为不可用 |
1.5.2 storage.conf(Storage 配置)
两个节点均需配置。
编辑 storage 配置文件:
vim /etc/fdfs/storage.conf
添加一行 tracker_server 地址,指向 tracker 服务(FastDFS tracker 默认端口为 22122):
tracker_server=192.168.1.221:22122
节点 2(192.168.1.221)执行同样的配置。
重要补充:源文档只配置了一个
tracker_server,这意味着两个 Storage 都只向192.168.1.221上的 Tracker 汇报,192.168.1.220上的 Tracker 实际上没有被使用(这也是日志中"221 被选为 leader"的原因——它是唯一被汇报的 tracker)。生产环境应配置多个tracker_server以实现 Tracker 高可用:
tracker_server=192.168.1.220:22122
tracker_server=192.168.1.221:22122
tracker_server 可以写多行,每行一个,Storage 会向所有 Tracker 汇报状态,客户端也会随机选一个提问。这就是 Tracker 水平扩展的实现方式。
完整的 storage.conf 关键项详解:
# ========== 基础 ==========
disabled = false
# group_name:本 storage 所属的组名(卷名)
# !!同组的所有 storage 必须写相同的 group_name;不同组必须不同!!
# !!这个值一旦有数据后就不能改,改了老文件的 file_id 就对不上,全部访问不到!!
group_name = group1
# 绑定本机 IP,留空监听所有网卡
bind_addr =
# 连接 tracker 时是否也绑定 bind_addr(一般 false)
client_bind = true
# Storage 服务监听端口,默认 23000
# !!同一个 group 内所有 storage 的 port 必须相同!!
port = 23000
connect_timeout = 30
network_timeout = 60
# 向 tracker 发送心跳的间隔(秒)
heart_beat_interval = 30
# 向 tracker 汇报磁盘使用情况的间隔(秒)
stat_report_interval = 60
# ========== 路径(核心) ==========
# base_path:存放【日志与数据元信息】的路径(binlog、mark 文件、pid),!!必须先创建!!
# 注意:这里【不】存放实际的文件内容
base_path = /home/dfs
# store_path_count:存放文件的路径个数(要与下面 store_path 的个数一致)
store_path_count = 1
# store_path0 ~ store_pathN:实际存放文件的目录,!!必须先手工创建!!
# 从 0 开始编号,不能跳号。对应 file_id 中的 M00、M01、M02...
# 【多盘配置】:一台机器挂多块盘时,每块盘配一个 store_path,可线性提升单机容量与 IOPS
store_path0 = /home/dfs
# store_path1 = /data/fastdfs2
# store_path2 = /data/fastdfs3
# subdir_count_per_path:每个 store_path 下创建的两级子目录个数
# 会创建 N × N 个目录(默认 256 × 256 = 65536 个)
# 文件被散列到这些目录中,避免单个目录文件数过多导致的性能下降
# !!这个值一旦有数据后不能改,改了文件就找不到了!!
subdir_count_per_path = 256
# ========== Tracker 地址(可配多个,实现 Tracker 高可用) ==========
tracker_server = 192.168.1.220:22122
tracker_server = 192.168.1.221:22122
# ========== 日志 ==========
log_level = info
run_by_group =
run_by_user =
allow_hosts = *
# 文件分布方式:0 = 轮询(默认,推荐),1 = 顺序写满一个目录再下一个
file_distribute_path_mode = 0
# mode=1 时单目录最大文件数
file_distribute_rotate_count = 100
# 写入多少字节后调用 fsync(0 表示不调用,交给系统)
fsync_after_written_bytes = 0
# 同步日志缓存到磁盘的间隔(秒)
sync_log_buff_interval = 10
# 同步 binlog 缓存到磁盘的间隔(秒)
sync_binlog_buff_interval = 10
# 把 stat 信息同步到磁盘的间隔(秒)
sync_stat_file_interval = 300
thread_stack_size = 512KB
# ========== 上传优先级与磁盘预留 ==========
# 上传优先级,值越小优先级越高(tracker.conf 的 store_server=2 时生效)
upload_priority = 10
# 网卡别名前缀,留空自动获取
if_alias_prefix =
# ========== 文件去重(需要 FastDHT 支持) ==========
# check_file_duplicate:是否检测上传文件的重复(内容相同则只存一份,用符号链接指向)
# 0 = 关闭(默认)
# 1 = 开启,需要额外部署 FastDHT(一个键值数据库)来存放文件指纹
# 开启后可显著节省空间(如大量用户上传同一张图片),但增加了 FastDHT 这个依赖与故障点
check_file_duplicate = 0
# 重复文件的命名空间(check_file_duplicate=1 时必填)
key_namespace = FastDFS
# 与 FastDHT 的连接方式:0 短连接,1 长连接
keep_alive = 0
# FastDHT 服务器列表(check_file_duplicate=1 时必填)
# #include /etc/fdht/fdht_servers.conf
# ========== 同步时间窗与节奏(重要) ==========
# sync_start_time / sync_end_time:允许进行同步的时间段
# 默认 00:00 - 23:59 即全天允许同步
# 可设置为只在业务低峰同步,例如 sync_start_time=01:00, sync_end_time=05:00
# !!但这会导致白天上传的文件在夜间才有副本,冗余窗口很长,一般不建议缩窄!!
sync_start_time = 00:00
sync_end_time = 23:59
# sync_interval:同步线程每同步一个文件后的休眠时间(毫秒)
# 0 表示不休眠(最快同步,默认)
# 设为较大值可降低同步对业务 IO 的冲击,但会拉长同步延迟
sync_interval = 0
# 同步完一批后的等待时间(秒)
sync_wait_msec = 50
# ========== HTTP 服务 ==========
# http.server_port:Storage 提供 HTTP 下载服务的端口
# !!这个值必须与 Nginx 在该 Storage 上监听的端口一致(如 8888),否则 tracker 的
# http 健康检查与部分客户端行为会异常!!
http.server_port = 8888
http.domain_name =
# ========== 合并存储(trunk) ==========
use_trunk_file = false
slot_min_size = 256
slot_max_size = 16KB
trunk_file_size = 64MB
trunk_create_file_advance = false
trunk_create_file_time_base = 02:00
trunk_create_file_interval = 86400
trunk_create_file_space_threshold = 20G
trunk_init_check_occupying = false
trunk_init_reload_from_binlog = false
trunk_compress_binlog_min_interval = 0
# ========== 其它 ==========
# 同步删除操作前的等待时间(秒)
# 防止在文件还没同步过去就同步删除操作导致的问题
# rotate 日志
rotate_error_log = false
error_log_rotate_time = 00:00
compress_old_error_log = false
rotate_access_log = false
access_log_rotate_time = 00:00
log_file_keep_days = 0
file_sync_skip_invalid_record = false
use_connection_pool = false
connection_pool_max_idle_time = 3600
重点参数总结:
| 参数 | 说明 | 陷阱 |
|---|---|---|
group_name |
组名 | 有数据后绝对不能改!改了所有老 file_id 全部失效 |
bind_addr |
监听 IP,留空为全部 | 填错则 Tracker 与客户端连不上,是排错重点 |
port |
23000 | 同组内必须相同 |
tracker_server |
可写多行,每行一个 Tracker | 生产必须配 2 个以上;写错 IP/端口则 Storage 永远上不了 ACTIVE |
base_path |
日志与元信息目录 | 必须先手工创建;不存实际文件 |
store_path_count |
store_path 的个数 | 必须与实际配置的 store_path 行数严格一致,否则启动失败 |
store_path0..N |
实际存文件的目录 | 必须先手工创建;编号不能跳号;有数据后不能改顺序 |
subdir_count_per_path |
256(即 65536 个目录) | 有数据后不能改;文件数极大时可考虑加大 |
sync_start_time/sync_end_time |
同步时间窗 | 缩窄会拉长冗余窗口 |
sync_interval |
同步间隔(毫秒) | 设太大导致同步延迟明显,出现"另一个节点 404" |
check_file_duplicate |
0/1 文件去重 | 设 1 必须部署 FastDHT,否则启动失败 |
http.server_port |
8888 | 必须与 Nginx 监听端口一致 |
upload_priority |
上传优先级 | 值越小越优先 |
reserved_storage_space |
在 tracker.conf 中 | 磁盘预留,触发后拒绝上传 |
1.5.3 client.conf(客户端配置)
两个节点均需配置,在 client.conf 中添加 tracker_server 地址:
vim /etc/fdfs/client.conf
tracker_server=192.168.1.221:22122
完整的关键项:
# 连接与网络超时
connect_timeout = 30
network_timeout = 60
# base_path:客户端的日志存放路径,!!必须先手工创建!!
base_path = /home/dfs
# tracker_server:可配置多个,客户端会随机选择一个(实现 Tracker 高可用)
tracker_server = 192.168.1.220:22122
tracker_server = 192.168.1.221:22122
# 日志级别
log_level = info
# 是否使用连接池
use_connection_pool = false
connection_pool_max_idle_time = 3600
# 是否从 tracker 加载 FastDFS 参数
load_fdfs_parameters_from_tracker = false
# 使用 storage id 而非 IP
use_storage_id = false
storage_ids_filename = storage_ids.conf
# HTTP 相关(用于生成访问 URL)
http.tracker_server_port = 80
#include http.conf
client.conf 只有两项是必须改的:tracker_server(指向 Tracker)和 base_path(客户端日志目录,必须存在)。所有 fdfs_* 命令行工具都要用这个文件作为第一个参数。
1.5.4 常见坑:配置文件中的路径必须先手工创建
这是 FastDFS 最高频的启动失败原因。FastDFS 不会自动创建 base_path 和 store_path 目录,一旦路径不存在,服务直接启动失败。
# 典型报错(在 base_path/logs/ 里根本看不到,因为日志目录都建不出来,只能在前台看到)
[ERROR] file: shared_func.c, line: xxx, "/home/dfs" can't be accessed, error info: No such file or directory
[ERROR] file: storage_func.c, line: xxx, mkdir "/data/fastdfs1/data" fail, errno: 13, error info: Permission denied
正确的准备步骤:
# 1. 从配置文件里把所有路径提取出来
grep -E '^(base_path|store_path[0-9]+)' /etc/fdfs/tracker.conf /etc/fdfs/storage.conf /etc/fdfs/client.conf
# 2. 逐个创建并确认权限
mkdir -p /home/dfs
mkdir -p /data/fastdfs1
chmod 755 /home/dfs /data/fastdfs1
# 3. 如果配置了 run_by_user / run_by_group(非 root 运行),必须把目录属主改过去
# chown -R fastdfs:fastdfs /home/dfs /data/fastdfs1
# 4. 确认磁盘有空间与 inode
df -h /home/dfs /data/fastdfs1
df -i /home/dfs /data/fastdfs1
# 5. 验证配置文件语法(用前台启动方式看报错最直观)
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf
其它相关的配置陷阱:
store_path_count与实际store_path行数不一致 → 启动失败或只用了部分盘。store_path是软链接指向未挂载的盘 → 数据会写进系统盘,磁盘满后灾难。- 修改了
store_path0的路径但没迁移数据 → 老文件全部 404。 - SELinux 开启时对
/home下的自定义目录有 context 限制 → 表现为 Permission denied,需setenforce 0或restorecon。
1.6 启动与验证
1.6.1 重启 fdfs 服务生效
两个节点都要执行以下重启操作。
重启 tracker 服务:
service fdfs_trackerd restart
重启 storage 服务:
service fdfs_storaged restart
源文档使用
service命令(FastDFS 安装包自带/etc/init.d/fdfs_trackerd、/etc/init.d/fdfs_storagedSysV 脚本,在 CentOS 6 和 CentOS 7 上都可用;CentOS 7 会通过 systemd 的兼容层转发)。
启动顺序:必须先 Tracker,后 Storage。 因为 Storage 启动时要向 Tracker 注册并汇报状态、获取 group 信息。如果 Tracker 没起来,Storage 会不断重试(不会退出,但状态无法变 ACTIVE)。
CentOS 7(推荐用 systemd 管理):
# 方式一:使用自带的 SysV 脚本
service fdfs_trackerd start
service fdfs_storaged start
service fdfs_trackerd status
chkconfig fdfs_trackerd on
chkconfig fdfs_storaged on
# 方式二:直接调用二进制(支持 start/stop/restart 参数)
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf start
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf start
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf stop
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf restart
方式三:自定义 systemd 单元(生产推荐,仅 CentOS 7+)
/usr/lib/systemd/system/fdfs_trackerd.service:
[Unit]
Description=FastDFS Tracker Server
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
PIDFile=/home/dfs/data/fdfs_trackerd.pid
ExecStart=/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf start
ExecStop=/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf stop
ExecReload=/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
/usr/lib/systemd/system/fdfs_storaged.service:
[Unit]
Description=FastDFS Storage Server
After=network.target remote-fs.target nss-lookup.target fdfs_trackerd.service
Requires=fdfs_trackerd.service
[Service]
Type=forking
PIDFile=/home/dfs/data/fdfs_storaged.pid
ExecStart=/usr/bin/fdfs_storaged /etc/fdfs/storage.conf start
ExecStop=/usr/bin/fdfs_storaged /etc/fdfs/storage.conf stop
ExecReload=/usr/bin/fdfs_storaged /etc/fdfs/storage.conf restart
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl start fdfs_trackerd
systemctl start fdfs_storaged
systemctl enable fdfs_trackerd fdfs_storaged
systemctl status fdfs_trackerd fdfs_storaged
CentOS 6(仅适用于 CentOS 6,无 systemd):
service fdfs_trackerd start
service fdfs_storaged start
chkconfig fdfs_trackerd on
chkconfig fdfs_storaged on
service fdfs_trackerd status
验证进程与端口:
ps -ef | grep fdfs
netstat -tnlp | grep -E '22122|23000'
ss -tunlp | grep -E '22122|23000'
期望看到:
tcp 0 0 0.0.0.0:22122 0.0.0.0:* LISTEN 1234/fdfs_trackerd
tcp 0 0 0.0.0.0:23000 0.0.0.0:* LISTEN 1256/fdfs_storaged
1.6.2 查看监控状态(fdfs_monitor)
fdfs_monitor /etc/fdfs/storage.conf
查看输出,确认两个 storage 均处于 ACTIVE 状态(第二个 storage 同样为 ACTIVE)。
典型完整输出与字段解读:
[2019-03-13 15:20:11] DEBUG - base_path=/home/dfs, connect_timeout=30, network_timeout=60, ...
server_count=2, server_index=0
tracker server is 192.168.1.221:22122
group count: 1
Group 1:
group name = group1
disk total space = 51200 MB
disk free space = 43008 MB
trunk free space = 0 MB
storage server count = 2
active server count = 2
storage server port = 23000
storage HTTP port = 8888
store path count = 1
subdir count per path = 256
current write server index = 0
current trunk file id = 0
Storage 1:
id = 192.168.1.220
ip_addr = 192.168.1.220 (server-node1) ACTIVE
http domain =
version = 6.06
join time = 2019-03-13 14:10:22
up time = 2019-03-13 15:02:10
total storage = 51200 MB
free storage = 43008 MB
upload priority = 10
store_path_count = 1
subdir_count_per_path = 256
storage_port = 23000
storage_http_port = 8888
current_write_path = 0
source storage id =
if_trunk_server = 0
connection.alloc_count = 256
connection.current_count = 1
connection.max_count = 2
total_upload_count = 12
success_upload_count = 12
total_append_count = 0
success_append_count = 0
total_modify_count = 0
success_modify_count = 0
total_truncate_count = 0
success_truncate_count = 0
total_set_meta_count = 0
success_set_meta_count = 0
total_delete_count = 1
success_delete_count = 1
total_download_count = 8
success_download_count = 8
total_get_meta_count = 0
success_get_meta_count = 0
total_create_link_count = 0
success_create_link_count = 0
total_delete_link_count = 0
success_delete_link_count = 0
total_upload_bytes = 118656
success_upload_bytes = 118656
total_download_bytes = 7904
success_download_bytes = 7904
total_sync_in_bytes = 3952
success_sync_in_bytes = 3952
total_sync_out_bytes = 11856
success_sync_out_bytes = 11856
total_file_write_count = 15
success_file_write_count = 15
total_file_read_count = 8
success_file_read_count = 8
last_heart_beat_time = 2019-03-13 15:20:05
last_source_update = 2019-03-13 15:18:44
last_sync_update = 2019-03-13 15:18:46
last_synced_timestamp = 2019-03-13 15:18:44 (0s delay)
Storage 2:
id = 192.168.1.221
ip_addr = 192.168.1.221 (server-node2) ACTIVE
...
关键字段解读:
| 字段 | 含义 | 排错价值 |
|---|---|---|
active server count |
组内 ACTIVE 的节点数 | 必须等于 storage server count,否则有节点异常 |
ip_addr = x.x.x.x ACTIVE |
节点状态,见下方状态表 | 最重要的字段,非 ACTIVE 说明有问题 |
disk total/free space |
组的总/剩余空间(取组内最小值) | 印证"组容量取决于最小节点" |
current write server index |
当前正在被写入的节点索引 | 观察 store_server 策略是否生效 |
current_write_path |
当前写入的 store_path 索引 | 多盘时观察轮询 |
total_upload_count / success_upload_count |
累计上传次数 / 成功次数 | 二者差值即失败次数;也可用于观察负载是否均衡 |
total_download_count / success_download_count |
累计/成功下载次数 | 观察读负载分布 |
total_delete_count / success_delete_count |
累计/成功删除次数 | - |
total_upload_bytes / success_upload_bytes |
上传字节数 | 容量增长趋势 |
total_sync_in_bytes |
从其它节点同步进来的字节数 | 判断同步是否在工作 |
total_sync_out_bytes |
同步出去的字节数 | 源节点的同步输出量 |
last_heart_beat_time |
最后一次心跳时间 | 与当前时间差过大说明节点失联 |
last_source_update |
本节点最后一次作为源被写入的时间 | - |
last_sync_update |
本节点最后一次同步数据的时间 | - |
last_synced_timestamp |
已同步到的时间点 + 延迟秒数,如 (0s delay) |
判断同步延迟的关键指标。delay 持续增大说明同步跟不上或已中断 |
if_trunk_server |
是否是 trunk server | 启用合并存储时相关 |
source storage id |
全量同步的源节点 | 新节点加入时会填这个 |
version |
Storage 版本 | 版本不一致排查 |
join time / up time |
加入集群时间 / 本次启动时间 | up time 频繁变化说明进程在反复重启 |
Storage 状态一览(极其重要):
| 状态 | 含义 | 处理 |
|---|---|---|
FAKE_INIT |
刚创建,尚未初始化 | 等待 |
INIT |
已初始化,但尚未获得同步源(还不知道从哪同步历史数据) | 组内第一个节点会短暂停留后直接变 ACTIVE;非首节点长期停在这里说明组内没有可用的源节点 |
WAIT_SYNC |
等待同步(已确定源,排队中) | 等待,或检查源节点是否 ACTIVE |
SYNCING |
正在做全量同步(新节点加入时的正常状态) | 等待完成,可用 total_sync_in_bytes 观察进度 |
DELETED |
已被从集群删除 | 用 fdfs_monitor ... delete 清理残留 |
OFFLINE |
离线(进程停了或网络断了) | 检查进程与网络 |
ONLINE |
在线但还不能提供服务 | 短暂过渡状态 |
ACTIVE |
正常工作,可提供上传下载服务 | 这是唯一的正常状态 |
fdfs_monitor 的其它用法:
# 查看集群状态(可用 storage.conf 或 client.conf,只要里面有 tracker_server)
fdfs_monitor /etc/fdfs/storage.conf
fdfs_monitor /etc/fdfs/client.conf
# 从集群中删除一个 storage(下线节点时用,需先停掉该 storage 进程)
fdfs_monitor /etc/fdfs/client.conf delete group1 192.168.1.222
# 设置 storage 状态为可用/不可用(临时摘除流量)
fdfs_monitor /etc/fdfs/client.conf set_trunk_server group1 192.168.1.220
# 结合 grep 快速看状态
fdfs_monitor /etc/fdfs/storage.conf 2>/dev/null | grep -E 'ip_addr|active server count|last_synced'
1.6.3 上传文件测试
fdfs_upload_file /etc/fdfs/client.conf README
上传成功后返回一个字符串,例如:
group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
该字符串是文件存放在服务器上的有效访问路径,通过浏览器访问对应地址即可找到上传的文件。
必须记录这个 file_id——它是唯一能找回文件的凭据。实际应用中要把它存进数据库。
验证文件确实落盘:
# 根据 file_id 推算物理路径:M00 -> store_path0 的 data 目录
ls -l /home/dfs/data/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
# 用 find 直接找
find /home/dfs/data -name 'wKgB3Fyo*'
1.6.4 命令行工具完整用法
(1)fdfs_upload_file 上传普通文件
fdfs_upload_file <config_file> <local_filename> [storage_ip:port] [store_path_index]
# 基本用法
fdfs_upload_file /etc/fdfs/client.conf README
fdfs_upload_file /etc/fdfs/client.conf /tmp/test.jpg
# 指定上传到某台 storage 的某个 store_path(跳过 tracker 的调度)
fdfs_upload_file /etc/fdfs/client.conf /tmp/test.jpg 192.168.1.220:23000 0
(2)fdfs_download_file 下载文件
fdfs_download_file <config_file> <file_id> [local_filename]
fdfs_download_file /etc/fdfs/client.conf group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
# 不指定本地文件名时,保存为 file_id 中的文件名部分(在当前目录)
fdfs_download_file /etc/fdfs/client.conf \
group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045 /tmp/downloaded.txt
(3)fdfs_delete_file 删除文件
fdfs_delete_file <config_file> <file_id>
fdfs_delete_file /etc/fdfs/client.conf group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
删除是同步到组内所有节点的(通过 binlog 的 D 记录)。删除后 file_id 立即失效,不可恢复。
(4)fdfs_file_info 查看文件元信息(解析 file_id)
fdfs_file_info <config_file> <file_id>
fdfs_file_info /etc/fdfs/client.conf group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
source storage id: 0
source ip address: 192.168.1.220
file create timestamp: 2019-03-13 14:35:29
file size: 988
file crc32: 791805962 (0x2F217D0A)
这是排错神器:能立刻告诉你"这个文件当初是传到哪台机器的、什么时候传的、多大"。当文件访问 404 时,先用它确认源节点,再去源节点看文件是否存在、是否同步出去了。
注意:fdfs_file_info 只做本地的 file_id 解码,不联网验证文件是否真实存在。要验证存在性用 fdfs_download_file 或 HTTP 访问。
(5)fdfs_test 综合测试工具
fdfs_test <config_file> <operation> [arg1 [arg2 ...]]
# 上传(会同时输出更详细的信息,包括 HTTP 访问 URL 和 master/slave 文件)
fdfs_test /etc/fdfs/client.conf upload /tmp/test.jpg
# 下载
fdfs_test /etc/fdfs/client.conf download group1 M00/00/00/wKgB3Fyo...
# 删除
fdfs_test /etc/fdfs/client.conf delete group1 M00/00/00/wKgB3Fyo...
# 查询文件信息
fdfs_test /etc/fdfs/client.conf query_file_info group1 M00/00/00/wKgB3Fyo...
# 设置/获取元数据(metadata,可用来存原始文件名等信息)
fdfs_test /etc/fdfs/client.conf upload_slave <master_file_id> <prefix> <local_file>
fdfs_test upload 输出示例(比 fdfs_upload_file 信息更丰富):
This is FastDFS client test program v6.06
...
group_name=group1, ip_addr=192.168.1.220, port=23000
storage_upload_by_filename
group_name=group1, remote_filename=M00/00/00/wKgB3Fyo....jpg
source ip address: 192.168.1.220
file timestamp=2019-03-13 14:35:29
file size=988
file crc32=791805962
example file url: http://192.168.1.220:8888/group1/M00/00/00/wKgB3Fyo....jpg
storage_upload_slave_by_filename
group_name=group1, remote_filename=M00/00/00/wKgB3Fyo..._big.jpg
example file url: http://192.168.1.220:8888/group1/M00/00/00/wKgB3Fyo..._big.jpg
注意 example file url 一行——它直接给出了 HTTP 访问地址,可以用来验证 Nginx 配置。
(6)fdfs_append_file / fdfs_upload_appender 追加文件
普通 FastDFS 文件不可修改,但 appender 文件支持追加写(用于日志、边录边传的视频等场景)。
# 上传一个 appender 文件(返回的 file_id 支持后续追加)
fdfs_upload_appender /etc/fdfs/client.conf /tmp/part1.log
# 返回 group1/M00/00/00/wKgB3Fyo...
# 向已有的 appender 文件追加内容
fdfs_append_file /etc/fdfs/client.conf group1/M00/00/00/wKgB3Fyo... /tmp/part2.log
# 修改 appender 文件的指定位置(从 offset 开始覆盖)
# fdfs_modify_file <config> <file_id> <offset> <local_filename>
# 截断 appender 文件到指定长度
# fdfs_truncate_file <config> <file_id> <truncated_file_size>
# 相关测试工具
fdfs_appender_test /etc/fdfs/client.conf /tmp/test.log
fdfs_appender_test1 /etc/fdfs/client.conf /tmp/test.log
注意:appender 文件不能启用合并存储(trunk),且追加操作也会通过 binlog 同步到同组其它节点。
(7)其它工具
# 计算文件的 CRC32(FastDFS 用它做文件校验)
fdfs_crc32 /tmp/test.jpg
# 集群监控(见 3.6.2)
fdfs_monitor /etc/fdfs/client.conf
命令行工具汇总表:
| 命令 | 用途 |
|---|---|
fdfs_upload_file <conf> <本地文件> |
上传普通文件,返回 file_id |
fdfs_download_file <conf> <file_id> [本地文件名] |
下载文件 |
fdfs_delete_file <conf> <file_id> |
删除文件 |
fdfs_file_info <conf> <file_id> |
解析 file_id:源 IP、创建时间、大小、CRC32 |
fdfs_test <conf> upload\|download\|delete\|query_file_info ... |
综合测试,输出信息最全(含 HTTP URL) |
fdfs_upload_appender <conf> <本地文件> |
上传 appender 文件(可追加) |
fdfs_append_file <conf> <file_id> <本地文件> |
向 appender 文件追加 |
fdfs_monitor <conf> [delete group ip] |
查看集群状态 / 删除节点 |
fdfs_crc32 <文件> |
计算 CRC32 |
fdfs_trackerd <conf> start\|stop\|restart |
Tracker 服务控制 |
fdfs_storaged <conf> start\|stop\|restart |
Storage 服务控制 |
1.6.5 下载与跨节点同步验证
在节点 1(192.168.1.220)上下载文件:
curl -O http://192.168.1.220/group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
然后在节点 2(192.168.1.221)上使用相同的文件路径、仅替换 IP 进行下载:
curl -O http://192.168.1.221/group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
对比两个节点下载到的文件内容是否一致。
上传一次文件,可在不同节点下载到相同内容,即证明集群安装成功、文件同步成功。
更严谨的验证方式(用 md5 对比):
# 1. 上传
FID=$(fdfs_upload_file /etc/fdfs/client.conf /tmp/test.jpg)
echo $FID
# 2. 从节点 1 和节点 2 分别下载
curl -s -o /tmp/from220 http://192.168.1.220/$FID
curl -s -o /tmp/from221 http://192.168.1.221/$FID
# 3. 对比三者的 md5(原文件 + 两个节点下载的)
md5sum /tmp/test.jpg /tmp/from220 /tmp/from221
# 三个 md5 值必须完全相同
# 4. 也可直接在两个节点的磁盘上验证文件确实存在
ssh 192.168.1.220 "find /home/dfs/data -name '${FID##*/}'"
ssh 192.168.1.221 "find /home/dfs/data -name '${FID##*/}'"
# 5. 用 FastDFS 协议下载(不经 HTTP)
fdfs_download_file /etc/fdfs/client.conf $FID /tmp/from_fdfs
md5sum /tmp/from_fdfs
上述
curl使用的是 80 端口,说明该环境已在两台机器上配好了 Nginx(见 3.7)。若尚未配置 Nginx,直接用 HTTP 访问会连接失败,此时应先用fdfs_download_file验证。
1.6.6 查看 storage.log 日志
查看 storage 日志,可观察到集群选举与同步情况:
tail -f /etc/fdfs/logs/storaged.log
日志中可看到:
- 192.168.1.221 被选为 leader 节点;
- 文件从 192.168.1.220 节点同步成功。
路径说明:源文档写的是
/etc/fdfs/logs/storaged.log,实际日志路径由配置文件中的base_path决定,即$base_path/logs/storaged.log。如果base_path=/home/dfs,那么真实路径是/home/dfs/logs/storaged.log。只有在把base_path设成/etc/fdfs时才会是源文档写的那个路径(不推荐这样配置,配置目录不该混放日志和数据)。
正确的日志查看方式:
# 先确认 base_path
grep '^base_path' /etc/fdfs/storage.conf
grep '^base_path' /etc/fdfs/tracker.conf
# 再看对应日志
tail -f /home/dfs/logs/storaged.log
tail -f /home/dfs/logs/trackerd.log
ls -l /home/dfs/logs/
典型的关键日志行:
[2019-03-13 15:02:10] INFO - local_host_ip_count: 2, 127.0.0.1 192.168.1.220
[2019-03-13 15:02:10] INFO - file: storage_func.c, line: xxx, tracker_client_ip: 192.168.1.220, my_server_id_str: 192.168.1.220, g_server_id_in_filename: 3691247296
[2019-03-13 15:02:11] INFO - file: tracker_client_thread.c, line: xxx, successfully connect to tracker server 192.168.1.221:22122, as a tracker client, my ip is 192.168.1.220
[2019-03-13 15:02:11] INFO - file: tracker_client_thread.c, line: xxx, tracker server 192.168.1.221:22122, set tracker leader: 192.168.1.221:22122
[2019-03-13 15:02:12] INFO - file: storage_sync.c, line: xxx, successfully connect to storage server 192.168.1.221:23000
[2019-03-13 15:18:46] INFO - file: storage_sync.c, line: xxx, sync thread to storage server 192.168.1.221:23000 started
[2019-03-13 15:18:46] INFO - file: storage_sync.c, line: xxx, file: M00/00/00/wKgB3Fyo... sync from 192.168.1.220 success
日志文件清单:
| 日志文件 | 位置 | 用途 |
|---|---|---|
trackerd.log |
$base_path/logs/(tracker 的 base_path) |
Tracker 运行日志:storage 注册、leader 选定、心跳 |
storaged.log |
$base_path/logs/(storage 的 base_path) |
Storage 运行日志:连接 tracker、同步进展、文件读写错误,最常看的日志 |
client.log |
$base_path/logs/(client 的 base_path) |
客户端工具日志 |
mod_fastdfs.log |
/tmp/(或 mod_fastdfs.conf 的 base_path) |
Nginx 模块日志,HTTP 访问 404 时必看 |
binlog.000 |
$base_path/data/sync/ |
同步操作日志(不是错误日志,是同步依据) |
1.7 HTTP 访问与 Nginx 集成
1.7.1 为什么需要 fastdfs-nginx-module
FastDFS 自带的 HTTP 服务(http.server_port)功能弱、性能差,早已不推荐使用。生产上统一用 Nginx + fastdfs-nginx-module 提供 HTTP 访问。
但为什么不能只用普通的 Nginx root 静态文件服务?因为有一个绕不过去的问题:
核心问题:同组节点间的同步延迟。
时刻 T0:客户端上传文件到 storage1-1(192.168.1.220),立即返回 file_id
时刻 T0:应用把 file_id 存入数据库,页面立刻渲染出图片 URL
时刻 T0+10ms:用户浏览器请求这张图片,负载均衡把请求打到了 storage1-2(192.168.1.221)
但此时 storage1-2 上还【没有】这个文件(异步同步还没完成!)
结果:HTTP 404,用户看到裂图
这个窗口通常只有几十毫秒到几秒,但在"上传后立即查看"的场景(发朋友圈、上传头像后马上显示)中必然会命中。文件越大、网络越慢、sync_interval 设得越大,窗口越长。
fastdfs-nginx-module 的解决原理:
浏览器 --> Nginx(storage1-2) --> mod_fastdfs 模块
|
+-- 1. 解析 URL 得到 group1 / M00 / 00/00/文件名
+-- 2. 检查 group_name 是否是本机所属的组
+-- 3. 把 M00 映射到本机的 store_path0
+-- 4. 检查本地文件是否存在?
|
+-- 存在 --> 直接返回本地文件(快)
|
+-- 不存在 --> 【解码 file_id 拿到源 storage 的 IP】
--> 用 HTTP 代理(proxy)到源 storage 取回
--> 返回给浏览器(用户无感知)
关键在于第 4 步的"不存在"分支:file_id 的文件名部分编码了源 Storage 的 IP(见 3.3.2),所以模块无需查任何数据库就能知道"这个文件的原始副本在哪台机器上",从而直接去那里取。
另外模块还会做:
- 校验
group_name是否属于本机(防止访问到错误的组); - 支持
M00/M01到多个store_path的映射; - 支持 trunk 文件的偏移读取(合并存储时,普通 Nginx 根本读不了 trunk 内部的小文件);
- 支持防盗链(
http.conf中的 token 机制)。
结论:只要是多副本(同组多节点)的 FastDFS,就必须用 fastdfs-nginx-module,否则一定会遇到偶发 404。
1.7.2 模块的编译安装与 Nginx 重新编译
Nginx 的模块是编译期静态链入的(fastdfs-nginx-module 不支持动态模块加载),因此必须重新编译 Nginx。
第一步:下载模块源码
cd /usr/local/src
git clone https://github.com/happyfish100/fastdfs-nginx-module.git --depth 1
# 或下载 tar 包
# wget https://github.com/happyfish100/fastdfs-nginx-module/archive/master.zip
ls /usr/local/src/fastdfs-nginx-module/src/
# config common.c common.h ngx_http_fastdfs_module.c mod_fastdfs.conf
第二步:修改模块的 config 文件(老版本必做)
老版本的 src/config 中路径写死为 /usr/local/include,会导致编译时找不到头文件:
vim /usr/local/src/fastdfs-nginx-module/src/config
把:
CORE_INCS="$CORE_INCS /usr/local/include/fastdfs /usr/local/include/fastcommon/"
改为(去掉 local,与实际安装位置一致):
CORE_INCS="$CORE_INCS /usr/include/fastdfs /usr/include/fastcommon/"
新版本已修正此问题,可先检查再决定是否修改。
第三步:编译 Nginx 并加入模块
情况 A:全新安装 Nginx
cd /usr/local/src
wget http://nginx.org/download/nginx-1.20.2.tar.gz
tar zxf nginx-1.20.2.tar.gz
cd nginx-1.20.2
# 安装编译依赖
yum -y install gcc gcc-c++ make pcre pcre-devel zlib zlib-devel openssl openssl-devel
./configure \
--prefix=/usr/local/nginx \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--with-http_stub_status_module \
--with-http_ssl_module \
--with-http_gzip_static_module \
--add-module=/usr/local/src/fastdfs-nginx-module/src
make
make install
nginx -V # 输出中应能看到 --add-module=.../fastdfs-nginx-module/src
情况 B:已有 Nginx,需要加模块(平滑升级,不中断服务)
# 1. 查看现有 Nginx 的编译参数,必须完整保留!
nginx -V
# 输出示例:configure arguments: --prefix=/usr/local/nginx --with-http_ssl_module ...
# 2. 下载完全相同版本的源码
nginx -v # 确认版本号,如 1.20.2
cd /usr/local/src
wget http://nginx.org/download/nginx-1.20.2.tar.gz
tar zxf nginx-1.20.2.tar.gz
cd nginx-1.20.2
# 3. 用【原有的全部参数】+ 新的 --add-module 重新 configure
./configure <把 nginx -V 输出的所有参数原样粘贴到这里> \
--add-module=/usr/local/src/fastdfs-nginx-module/src
# 4. 只 make,【不要 make install】(避免覆盖配置文件)
make
# 5. 备份旧的二进制
cp /usr/sbin/nginx /usr/sbin/nginx.bak.$(date +%F)
# 6. 替换二进制
cp -f objs/nginx /usr/sbin/nginx
# 若提示 Text file busy,用:
# mv /usr/sbin/nginx /usr/sbin/nginx.old && cp objs/nginx /usr/sbin/nginx
# 7. 验证
nginx -t
nginx -V
# 8. 平滑重启(不中断连接)
kill -USR2 $(cat /var/run/nginx.pid) # 启动新版本 worker
sleep 3
kill -WINCH $(cat /var/run/nginx.pid.oldbin) # 优雅关闭旧 worker
kill -QUIT $(cat /var/run/nginx.pid.oldbin) # 退出旧 master
# 或简单地
systemctl restart nginx
第四步:复制模块配置文件
cp /usr/local/src/fastdfs-nginx-module/src/mod_fastdfs.conf /etc/fdfs/
# 若前面没做过,还需要 fastdfs 源码 conf 下的这两个文件
cp /usr/local/src/fastdfs/conf/http.conf /etc/fdfs/
cp /usr/local/src/fastdfs/conf/mime.types /etc/fdfs/
ls -l /etc/fdfs/
第五步:建立 M00 到 store_path0 的软链接
模块要求存在 $store_path0/data 到 M00 的映射,创建软链接以便调试与部分场景访问:
ln -s /home/dfs/data /home/dfs/data/M00
# 若 store_path0 = /data/fastdfs1
# ln -s /data/fastdfs1/data /data/fastdfs1/data/M00
ls -l /home/dfs/data/M00
注意:使用
ngx_fastdfs_module时这个软链接并非严格必需(模块自己会做 M00 → store_path 的映射),但许多教程都会创建它,且在用普通root指令兜底时有用。
详见 10-02-Nginx服务与优化.md
1.7.3 mod_fastdfs.conf 逐项配置
vim /etc/fdfs/mod_fastdfs.conf
# ========== 基础 ==========
# 连接超时(秒)
connect_timeout = 10
# 网络超时(秒)
network_timeout = 30
# base_path:模块的日志存放目录(!!必须存在!!)
base_path = /tmp
# 是否从 tracker 加载 FastDFS 参数(建议 true,可自动获取 store_path 等信息)
load_fdfs_parameters_from_tracker = true
# 是否使用 storage id 代替 IP
use_storage_id = false
storage_ids_filename = storage_ids.conf
# ========== Tracker 地址(关键) ==========
# 必须与 storage.conf 中的 tracker_server 一致,可配多个
tracker_server = 192.168.1.220:22122
tracker_server = 192.168.1.221:22122
# ========== 端口与日志 ==========
# 本 Nginx 提供 HTTP 服务的端口,!!必须与 nginx.conf 中 listen 的端口一致!!
# 也应与 storage.conf 的 http.server_port 一致
storage_server_port = 23000
# 模块日志级别
log_level = info
log_filename =
# 文件不存在时的处理方式:
# proxy = 代理到源 storage 去取(!!推荐,这是解决同步延迟的关键!!)
# redirect = 302 重定向到源 storage(浏览器会看到跳转)
response_mode = proxy
# ========== group 配置(最容易配错的地方) ==========
# group_name:本 Storage 所属的组名,!!必须与 storage.conf 的 group_name 完全一致!!
group_name = group1
# url_have_group_name:URL 中是否包含 group 名
# true = URL 形如 http://ip/group1/M00/00/00/xxx(!!推荐,多 group 时必须 true!!)
# false = URL 形如 http://ip/M00/00/00/xxx
# !!这个值配错是 404 的第一大原因!!它必须与 Nginx 的 location 规则匹配
url_have_group_name = true
# store_path_count 与 store_pathN:
# !!必须与 storage.conf 中的完全一致(个数、顺序、路径)!!
store_path_count = 1
store_path0 = /home/dfs
# store_path1 = /data/fastdfs2
# ========== 多 group 场景(一台 Nginx 服务多个组时) ==========
# group_count:本机涉及的 group 数量。0 表示单组(用上面的 group_name/store_path 配置)
# 若一台机器上跑了多个 group 的 storage(不推荐),需设置为实际组数并补充 [group1]/[group2] 段
group_count = 0
# 单机多组时的写法示例(group_count = 2):
# [group1]
# group_name = group1
# storage_server_port = 23000
# store_path_count = 1
# store_path0 = /home/dfs
#
# [group2]
# group_name = group2
# storage_server_port = 23000
# store_path_count = 1
# store_path0 = /data/fastdfs2
必改的 4 项(其余可保持默认):
| 参数 | 必须设为 | 配错的后果 |
|---|---|---|
tracker_server |
与 storage.conf 一致(可多行) | 模块无法获取集群信息,访问全部失败 |
group_name |
与本机 storage.conf 的 group_name 完全一致 |
404,模块认为"这个 group 不是我的" |
url_have_group_name |
通常 true(URL 含 group1) |
404 最常见原因,必须与 Nginx location 匹配 |
store_path0(及 count) |
与 storage.conf 的 store_path0 完全一致 |
404,模块在错误的目录下找文件 |
配置完成后验证:
# 检查三个文件的关键项是否一致(排错必做的三方对照)
grep -E '^(group_name|store_path0|store_path_count|tracker_server|http.server_port)' /etc/fdfs/storage.conf
grep -E '^(group_name|store_path0|store_path_count|tracker_server|url_have_group_name)' /etc/fdfs/mod_fastdfs.conf
1.7.4 Storage 节点上的 Nginx 配置
在每个 Storage 节点上配置 Nginx,用 ngx_fastdfs_module 处理文件请求。
vim /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 10240;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
client_max_body_size 300m; # 允许的最大上传体积,按业务调整
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
server {
# 监听端口必须与 storage.conf 的 http.server_port 一致
listen 8888;
server_name localhost;
# 核心:把 /group1/M00/ 开头的请求交给 fastdfs 模块处理
# 注意 location 必须与 mod_fastdfs.conf 的 url_have_group_name 匹配:
# url_have_group_name = true --> location ~/group[0-9]+/M00
# url_have_group_name = false --> location ~/M00
location ~/group[0-9]+/M00 {
# 可选:把 M00 直接映射到本地目录(模块会优先处理,此行主要用于兜底调试)
# root /home/dfs/data;
ngx_fastdfs_module;
}
# 只服务单个 group 时也可写成
# location /group1/M00 {
# ngx_fastdfs_module;
# }
# 状态页,便于监控
location /nginx_status {
stub_status on;
access_log off;
allow 192.168.1.0/24;
deny all;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
启动并验证:
nginx -t
systemctl start nginx # CentOS 7
systemctl enable nginx
service nginx start # CentOS 6
chkconfig nginx on
# 查看模块日志(配置有问题时这里会有明确报错)
tail -f /tmp/mod_fastdfs.log
# 直接访问验证(用之前上传得到的 file_id)
curl -I http://192.168.1.220:8888/group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
# 期望 HTTP/1.1 200 OK
curl -o /tmp/t.jpg http://192.168.1.220:8888/group1/M00/00/00/wKgB3Fyo...
启动时的正常日志(/tmp/mod_fastdfs.log):
[2019-03-13 16:00:01] INFO - local_host_ip_count: 2, 127.0.0.1 192.168.1.220
[2019-03-13 16:00:01] INFO - file: /usr/local/src/fastdfs-nginx-module/src/common.c, line: xxx, fastdfs apache / nginx module v1.22, response_mode=proxy, base_path=/tmp, connect_timeout=10, network_timeout=30, tracker_server_count=2, storage_server_port=23000, group_name=group1, url_have_group_name=1, group_count=0, ...
1.7.5 Tracker 节点上的 Nginx 反向代理与统一入口
为什么需要统一入口? 直接把 Storage 的 IP 暴露给前端有几个问题:
- 前端需要知道每个 group 在哪些 IP 上 —— 耦合严重,加节点就要改前端。
- Storage 节点宕机后前端无法自动切换。
- 无法做统一的缓存、限流、防盗链、HTTPS 卸载。
- 多 group 时,前端需要根据 file_id 里的 group 名去找对应的机器 —— 逻辑不该在前端。
解决方案:在 Tracker 节点(或独立的接入层)上用 Nginx 做反向代理 + upstream 负载均衡,对外只暴露一个域名/VIP。
在 Tracker 节点(192.168.1.220 / 192.168.1.221)上配置:
vim /etc/nginx/conf.d/fastdfs.conf
# ===== 定义各 group 的后端 Storage 集群 =====
upstream fdfs_group1 {
server 192.168.1.220:8888 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.1.221:8888 weight=1 max_fails=2 fail_timeout=30s;
keepalive 32;
# 可选:按 URI 哈希,让同一文件固定落到同一后端,提升缓存命中率
# hash $request_uri consistent;
}
upstream fdfs_group2 {
server 192.168.1.222:8888 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.1.223:8888 weight=1 max_fails=2 fail_timeout=30s;
keepalive 32;
}
# ===== 缓存区(可选但强烈推荐,能极大降低后端压力)=====
proxy_cache_path /var/cache/nginx/fastdfs
levels=1:2
keys_zone=fdfs_cache:200m
inactive=30d
max_size=20g;
server {
listen 80;
server_name img.example.com;
access_log /var/log/nginx/fastdfs_access.log;
error_log /var/log/nginx/fastdfs_error.log;
# ===== 按 group 名分流到不同的 upstream(多 group 场景的核心)=====
location /group1/M00 {
proxy_pass http://fdfs_group1;
include /etc/nginx/proxy_fastdfs.conf;
}
location /group2/M00 {
proxy_pass http://fdfs_group2;
include /etc/nginx/proxy_fastdfs.conf;
}
# ===== 或者用正则统一匹配(单 group 或 group 数固定时更简洁)=====
# location ~ ^/group([0-9]+)/M00 {
# proxy_pass http://fdfs_group$1; # 注意:变量形式的 proxy_pass 需要 resolver
# include /etc/nginx/proxy_fastdfs.conf;
# }
location = /healthz {
return 200 "ok\n";
access_log off;
}
}
公共的代理参数 /etc/nginx/proxy_fastdfs.conf:
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;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 16 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 128k;
proxy_max_temp_file_size 256m;
# 后端故障时自动切换到下一台(配合 upstream 的 max_fails)
proxy_next_upstream error timeout http_500 http_502 http_503 http_504 http_404;
# 缓存配置
proxy_cache fdfs_cache;
proxy_cache_valid 200 302 30d;
proxy_cache_valid 404 1m;
proxy_cache_key $host$uri;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;
# 静态资源缓存头
expires 30d;
add_header Cache-Control "public, max-age=2592000";
创建缓存目录并启动:
mkdir -p /var/cache/nginx/fastdfs
chown -R nginx:nginx /var/cache/nginx
nginx -t
systemctl reload nginx
# 验证统一入口
curl -I http://192.168.1.220/group1/M00/00/00/wKgB3Fyo...
curl -I -H 'Host: img.example.com' http://192.168.1.220/group1/M00/00/00/wKgB3Fyo...
# 观察 X-Cache-Status 头:MISS -> HIT 说明缓存生效
Tracker 层自身的高可用:两台 Tracker 上的 Nginx 配置完全相同,前面再用 keepalived 做 VIP 或用 LVS/HAProxy/云 SLB 做四层负载,即可消除接入层单点。
# keepalived 简要示例(在两台 tracker 上)
yum -y install keepalived
# 配置 VIP 192.168.1.200,MASTER/BACKUP 各一台,检查脚本检测 nginx 进程
# 前端统一访问 http://192.168.1.200/group1/M00/...
多 group 场景的完整访问链路:
浏览器 http://img.example.com/group2/M00/00/00/xxx
|
v
VIP 192.168.1.200(keepalived)
|
v
Nginx(tracker1 或 tracker2) —— location /group2/M00 匹配
|
v proxy_pass http://fdfs_group2(负载均衡到 222 或 223)
|
v
Nginx(storage 192.168.1.222:8888) —— location ~/group[0-9]+/M00
|
v ngx_fastdfs_module
|
+-- 本地 /data/fastdfs1/data/00/00/xxx 存在 --> 直接返回
+-- 不存在 --> 解析 file_id 得到源 IP --> proxy 到源 storage 取回
详见 10-02-Nginx服务与优化.md
1.8 扩容与运维
1.8.1 在组内增加 Storage(提升冗余与读性能)
目的:增加副本数、提升读并发能力。注意:不增加容量!(同组内容相同,容量取最小值)
步骤:
# 在新机器 storage3(192.168.1.222)上:
# 1. 完成所有环境准备(关防火墙/放行端口、关 SELinux、主机名、hosts、时间同步、数据盘)
# 2. 安装 libfastcommon + fastdfs(版本必须与现有节点一致!)
# 3. 创建目录
mkdir -p /home/dfs /data/fastdfs1
# 4. 配置 storage.conf —— 关键是 group_name 要与目标组相同
vim /etc/fdfs/storage.conf
group_name = group1 # 与要加入的组相同
port = 23000 # 与同组其它节点相同
base_path = /home/dfs
store_path_count = 1
store_path0 = /data/fastdfs1 # 与同组其它节点【路径最好也一致】,便于运维
subdir_count_per_path = 256 # 必须与同组一致
tracker_server = 192.168.1.220:22122
tracker_server = 192.168.1.221:22122
http.server_port = 8888
# 5. 启动
systemctl start fdfs_storaged
# 6. 观察状态变化:INIT -> WAIT_SYNC -> SYNCING -> ACTIVE
watch -n 5 "fdfs_monitor /etc/fdfs/storage.conf 2>/dev/null | grep -E 'ip_addr|last_synced'"
# 7. 观察全量同步进度
tail -f /home/dfs/logs/storaged.log
cat /home/dfs/data/sync/*.mark # 看 need_sync_old / sync_old_done
fdfs_monitor /etc/fdfs/storage.conf | grep -E 'total_sync_in_bytes|source storage id'
# 8. 同步完成(状态变 ACTIVE)后,部署 Nginx + fastdfs-nginx-module
# 9. 把新节点加入 Tracker 侧 Nginx 的 upstream
vim /etc/nginx/conf.d/fastdfs.conf # 在 upstream fdfs_group1 中加一行
nginx -t && systemctl reload nginx
重要:新节点的状态在同步完成前是 SYNCING,此时 Tracker 不会把上传请求调度给它(避免它成为源),但同步完成变 ACTIVE 后就会参与上传与下载。全量同步时间取决于已有数据量与网络带宽,TB 级数据可能需要数小时到数天。
1.8.2 增加新 group(实现容量扩展)
这是 FastDFS 唯一的容量扩容方式。
# 在两台新机器 storage3(222)、storage4(223) 上分别配置:
vim /etc/fdfs/storage.conf
group_name = group2 # 【新的组名】
port = 23000
base_path = /home/dfs
store_path_count = 1
store_path0 = /data/fastdfs1
tracker_server = 192.168.1.220:22122
tracker_server = 192.168.1.221:22122
http.server_port = 8888
# 启动
systemctl start fdfs_storaged
# 验证:fdfs_monitor 中应出现 group count: 2
fdfs_monitor /etc/fdfs/storage.conf | grep -E 'group count|group name|active server count'
group count: 2
Group 1:
group name = group1
active server count = 2
Group 2:
group name = group2
active server count = 2
新组加入后的行为:
store_lookup = 0(轮询):新上传会在 group1 和 group2 之间轮流,两组均衡增长。但 group1 可能已经快满了,还在往里写,这时需要注意。store_lookup = 2(选空间最大):新上传会全部涌向新的 group2(因为它空间最大),直到与 group1 持平。扩容后想快速缓解老组压力时可临时切成 2。store_lookup = 1(指定组):配合store_group = group2强制只往新组写,最可控。
# 修改 tracker.conf 后需重启 tracker(两台都要改)
vim /etc/fdfs/tracker.conf # store_lookup = 2 或 1 + store_group = group2
systemctl restart fdfs_trackerd
别忘了配置 Nginx:新 group 的 Storage 上要装 fastdfs-nginx-module(mod_fastdfs.conf 中 group_name = group2),Tracker 侧 Nginx 要加 upstream fdfs_group2 与 location /group2/M00(见 3.7.5)。
注意:老文件不会迁移到新组。 file_id 里的 group 名是固定的,group1 的文件永远在 group1。FastDFS 没有 rebalance 机制(这与 GlusterFS 不同),只能靠新写入来均衡。
1.8.3 单节点多 store_path 挂多盘
目的:在不加机器的前提下扩大单节点容量、提升单机 IOPS。
# 1. 挂载新盘
mkfs.xfs /dev/sdc1
mkdir -p /data/fastdfs2
mount -o noatime /dev/sdc1 /data/fastdfs2
echo '/dev/sdc1 /data/fastdfs2 xfs defaults,noatime 0 0' >> /etc/fstab
# 2. 修改 storage.conf
vim /etc/fdfs/storage.conf
store_path_count = 2 # 【必须同步改成 2】
store_path0 = /data/fastdfs1
store_path1 = /data/fastdfs2 # 对应 file_id 中的 M01
# 3. 同步修改 mod_fastdfs.conf(必须与 storage.conf 完全一致!)
vim /etc/fdfs/mod_fastdfs.conf
store_path_count = 2
store_path0 = /data/fastdfs1
store_path1 = /data/fastdfs2
# 4. 重启服务
systemctl restart fdfs_storaged
systemctl reload nginx
# 5. 验证:monitor 中 store path count 变为 2
fdfs_monitor /etc/fdfs/storage.conf | grep -E 'store path count|current_write_path'
# 6. 上传若干文件,观察 file_id 中出现 M01
for i in $(seq 1 10); do fdfs_upload_file /etc/fdfs/client.conf /etc/hosts; done
# 应能看到 group1/M00/... 与 group1/M01/... 交替出现(store_path = 0 轮询)
关键要点:
- 同组内所有节点的
store_path_count与路径顺序应保持一致,否则同步时 M01 的文件在另一节点上找不到对应路径。 store_path_count只能增加不能减少(减少会导致已有 M0N 文件无法访问)。mod_fastdfs.conf必须同步修改,否则 M01 的文件通过 HTTP 访问会 404。- Tracker 的
store_path = 0(轮询)或2(选空间最大)决定新文件落到哪块盘。
1.8.4 Storage 节点下线与数据迁移
# 场景 A:组内有多个节点,下线其中一个(数据在其它节点上有副本,可安全下线)
# 1. 先从 Tracker 侧 Nginx 的 upstream 中摘除,停止新流量
vim /etc/nginx/conf.d/fastdfs.conf # 注释掉该 server 行,或加 down 标记
# server 192.168.1.221:8888 down;
nginx -t && systemctl reload nginx
# 2. 确认组内其它节点数据完整(同步无延迟)
fdfs_monitor /etc/fdfs/storage.conf | grep -E 'ip_addr|last_synced_timestamp'
# 所有节点的 delay 应为 0s
# 3. 停止该节点的 storage 服务
systemctl stop fdfs_storaged
# 4. 从 Tracker 的集群列表中删除该节点(否则会一直显示 OFFLINE)
fdfs_monitor /etc/fdfs/client.conf delete group1 192.168.1.221
# 5. 验证
fdfs_monitor /etc/fdfs/storage.conf | grep -E 'storage server count|active server count'
# 场景 B:下线整个 group(该组是唯一持有这些文件的地方,必须先迁移数据!)
# FastDFS 没有内置的跨 group 迁移工具,只能应用层重传:
# 1. 从数据库中取出所有属于 group1 的 file_id
# 2. 逐个从 group1 下载,再上传到 group2(tracker.conf 设 store_lookup=1, store_group=group2)
# 3. 更新数据库中的 file_id
# 4. 全部验证无误后再下线 group1
# 迁移脚本示例思路
while read fid; do
fdfs_download_file /etc/fdfs/client.conf "$fid" /tmp/migrate.tmp || { echo "FAIL DL $fid" >> /tmp/fail.log; continue; }
newfid=$(fdfs_upload_file /etc/fdfs/client.conf /tmp/migrate.tmp)
[ -n "$newfid" ] && echo "$fid $newfid" >> /tmp/mapping.txt || echo "FAIL UP $fid" >> /tmp/fail.log
done < /tmp/group1_file_ids.txt
这是 FastDFS 的重要短板:没有 rebalance、没有跨 group 数据迁移能力。因此规划阶段就要想好 group 的划分,避免后期大规模搬迁。
1.8.5 同步状态检查与 binlog
# 1. 从 monitor 看同步延迟(最直观)
fdfs_monitor /etc/fdfs/storage.conf | grep -E 'ip_addr|last_source_update|last_sync_update|last_synced_timestamp'
# last_synced_timestamp 后面的 (Ns delay) 就是延迟秒数,正常应为 0s 或很小
# 2. 看 sync 目录下的 binlog 与 mark 文件
ls -lh /home/dfs/data/sync/
-rw-r--r-- 1 root root 1.2M Mar 13 16:20 binlog.000 # 本机操作日志
-rw-r--r-- 1 root root 16 Mar 13 16:20 binlog.index # 当前 binlog 序号
-rw-r--r-- 1 root root 256 Mar 13 16:20 192.168.1.221_23000.mark # 从 221 同步的进度
# 3. 查看 binlog 内容(末尾即最近的操作)
tail -20 /home/dfs/data/sync/binlog.000
1553836564 C M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
1553836601 c M00/00/00/wKgB3VyovlmAaBcRAAAD3EJmFwo1119203
1553836702 D M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
操作类型速查:C 源创建、c 同步创建、D 源删除、d 同步删除、A 源追加、a 同步追加、T 源截断、t 同步截断、U 源更新。大写 = 本机作为源的操作,小写 = 从别处同步来的操作。
# 4. 查看 mark 文件,确认同步进度
cat /home/dfs/data/sync/192.168.1.221_23000.mark
binlog_index=0
binlog_offset=1245678
need_sync_old=0
sync_old_done=1
until_timestamp=1553836702
scan_row_count=8900
sync_row_count=8900
判断标准:
| 现象 | 结论 |
|---|---|
binlog_offset 接近 binlog.000 的文件大小,且在持续增长 |
同步正常 |
binlog_offset 长时间不变,但 binlog 在增长 |
同步已停止,查网络与对端进程 |
need_sync_old=1 且 sync_old_done=0 |
正在做全量同步(新节点加入时正常) |
scan_row_count 远大于 sync_row_count |
大量记录被跳过,需查日志原因 |
last_synced_timestamp 的 delay 持续增大 |
同步跟不上写入速度,考虑调小 sync_interval、加带宽 |
# 5. 对比两个节点的文件数量(终极验证)
find /home/dfs/data -type f ! -name 'binlog*' ! -name '*.mark' | wc -l # 在每个节点上执行
# 同组节点的数量应基本一致(允许有几秒延迟的差异)
# 6. 对比磁盘占用
du -sh /home/dfs/data/
.fdfs_storage 相关说明:某些版本会在 store_path 下生成 .fdfs_storage_mark 文件,用于记录该路径是否已被 FastDFS 初始化(防止误用他人目录)。不要手工删除它,否则 Storage 启动时会认为该目录未初始化而报错。
1.8.6 磁盘空间预留 reserved_storage_space
在 tracker.conf 中配置:
reserved_storage_space = 20%
# 也可写绝对值
# reserved_storage_space = 10G
作用:为操作系统与 FastDFS 自身(日志、binlog、临时文件)预留空间。当组内所有 Storage 的剩余空间都低于此阈值时,Tracker 会认为该组不可写,新上传请求得到:
[ERROR] file: tracker_proto.c, line: xxx, ... no available storage server
为什么必须预留:
- 磁盘 100% 写满会导致 binlog 无法写入 → 同步机制彻底失效,且已写的数据可能损坏。
- 日志无法写入,故障时无从排查。
- XFS/ext4 在接近满时性能急剧下降、可能出现文件系统错误。
- 系统盘写满会导致整机不可用。
推荐配置:
| 场景 | 建议值 |
|---|---|
| 小容量盘(< 500GB) | 20% |
| 大容量盘(几 TB 以上) | 10% 或绝对值 50G(百分比会预留过多) |
| 独立数据盘、有完善监控 | 10% |
触发后的处理:
# 1. 确认是否真的是空间问题
df -h
fdfs_monitor /etc/fdfs/storage.conf | grep -E 'disk total space|disk free space'
# 2. 短期缓解:清理无用文件、下调 reserved(临时,不推荐)
vim /etc/fdfs/tracker.conf # reserved_storage_space = 10%
systemctl restart fdfs_trackerd
# 3. 正确做法:新增 group 扩容(见 3.8.2),或给节点加盘(见 3.8.3)
1.8.7 备份策略
核心认知:FastDFS 的同组多副本不等于备份!
| 风险 | 同组多副本能否防护 |
|---|---|
| 单块磁盘损坏 | 能(其它节点有副本) |
| 单台服务器宕机 | 能 |
误删文件(fdfs_delete_file 或应用 bug) |
不能!删除操作会通过 binlog 同步到组内所有节点,所有副本一起消失 |
误删数据目录(rm -rf) |
只删一个节点能恢复;脚本批量误删所有节点则不能 |
| 机房级灾难(火灾、断电、水淹) | 不能(同组通常在同一机房) |
| 勒索病毒/入侵 | 不能 |
| 逻辑错误(业务写坏了内容) | 不能 |
备份方案:
# 方案 1:rsync 定期同步到独立的备份服务器(最简单实用)
rsync -avz --delete --exclude='sync/' \
192.168.1.220:/data/fastdfs1/data/ /backup/fastdfs/group1/
# 加入 crontab,每天凌晨执行
echo '0 2 * * * /usr/bin/rsync -az --exclude=sync/ 192.168.1.220:/data/fastdfs1/data/ /backup/fastdfs/group1/ >> /var/log/fdfs_backup.log 2>&1' >> /var/spool/cron/root
# 方案 2:应用层双写到对象存储(推荐,异地容灾)
# 上传时同时写 FastDFS 和 OSS/S3/MinIO,数据库记录两个地址
# 方案 3:底层存储做快照(LVM 快照 / 云盘快照)
lvcreate -L 100G -s -n fdfs_snap /dev/vg_data/lv_fastdfs1
# 方案 4:备份配置与元信息(必做,成本极低)
tar czf /backup/fdfs_conf_$(date +%F).tar.gz /etc/fdfs/ /etc/nginx/
# 数据库中的 file_id 映射表必须一起备份!否则有文件也找不回来
mysqldump -uroot -p mydb file_mapping > /backup/file_mapping_$(date +%F).sql
特别强调:数据库中的 原始文件名 -> file_id 映射表和文件本体同等重要。丢了映射表,磁盘上的文件就成了一堆无法索引的乱码文件名,等于全部丢失。这个映射表必须与业务数据库一起做完整的备份与恢复演练。
1.8.8 监控要点
| 监控项 | 采集方式 | 告警阈值/条件 |
|---|---|---|
| Storage 状态是否 ACTIVE | fdfs_monitor ... \| grep ip_addr |
出现非 ACTIVE 立即告警(P0) |
| active server count | 同上 | 小于 storage server count 时告警 |
| 同步延迟 last_synced_timestamp 的 delay | fdfs_monitor 解析 |
> 60s 告警 |
| 磁盘剩余空间 | df 或 monitor 的 disk free space |
< reserved_storage_space + 10% 时告警 |
| inode 剩余 | df -i |
使用率 > 80% 告警(小文件场景极易先耗尽 inode) |
| Tracker 进程与端口 22122 | systemctl is-active / nc -z |
不存活告警 |
| Storage 进程与端口 23000 | 同上 | 不存活告警 |
| Nginx 进程与端口 8888/80 | 同上 | 不存活告警 |
| 上传成功率 | total_upload_count 与 success_upload_count 的差值增长 |
差值增长告警 |
| 下载成功率 / 404 率 | Nginx access_log 中的 4xx/5xx 比例 | 404 率突增告警(可能是同步问题或 mod_fastdfs 配错) |
| 上传/下载耗时 | Nginx $request_time |
P99 超阈值告警 |
| binlog 增长速率 | binlog.000 文件大小变化 |
异常暴增可能是死循环写入 |
| 日志中的 ERROR | grep ERROR $base_path/logs/*.log |
出现即告警 |
| 时间偏差 | chronyc tracking / ntpq -p |
偏差 > 1s 告警(影响同步!) |
监控脚本示例:
#!/bin/bash
# /usr/local/bin/check_fastdfs.sh
CONF=/etc/fdfs/client.conf
OUT=$(fdfs_monitor $CONF 2>/dev/null)
# 检查是否有非 ACTIVE 的节点
BAD=$(echo "$OUT" | grep 'ip_addr' | grep -v 'ACTIVE')
if [ -n "$BAD" ]; then
echo "CRITICAL: non-active storage found:"; echo "$BAD"; exit 2
fi
# 检查同步延迟
DELAY=$(echo "$OUT" | grep -oP '\(\K[0-9]+(?=s delay\))' | sort -rn | head -1)
if [ -n "$DELAY" ] && [ "$DELAY" -gt 60 ]; then
echo "WARNING: sync delay ${DELAY}s"; exit 1
fi
echo "OK: all storage ACTIVE, max sync delay ${DELAY:-0}s"; exit 0
1.9 性能与容量规划
1.9.1 小文件存储的优势原理
FastDFS 为什么在小文件上比通用分布式文件系统快?
| 优化点 | 原理 |
|---|---|
| 无元数据查询 | 位置信息编码在 file_id 里,下载时直接算出物理路径。GlusterFS 需要哈希计算 + 可能的 linkto 跳转 + LOOKUP 广播;HDFS 需要问 NameNode |
| 一次网络往返完成上传 | 客户端问一次 Tracker(可缓存),然后直连 Storage 一次传完。没有多副本同步等待(异步) |
| 不实现 POSIX | 没有 open/seek/write/lock 的语义开销,没有目录树维护,没有 inode 分配的复杂性 |
| 两级目录预创建 | 65536 个目录预先建好,文件均匀散列,单目录文件数可控,底层文件系统的目录查找始终高效 |
| 异步同步 | 上传立即返回,不等副本落盘,写延迟等于单机写延迟 |
| C 语言实现,极致轻量 | 单进程内存占用几十 MB,无 JVM 开销(对比 HDFS) |
| 顺序写盘 | 文件一次性写入,不做随机改写,对 HDD 友好 |
1.9.2 subdir_count_per_path 与容量上限估算
subdir_count_per_path = 256
含义:在每个 store_path 下创建 两级目录,每级 256 个(00 ~ FF),共 256 × 256 = 65536 个末级目录。
/data/fastdfs1/data/
├── 00/
│ ├── 00/ ← 文件存在这一层
│ ├── 01/
│ ├── ...
│ └── FF/
├── 01/
├── ...
└── FF/
容量上限估算:
设单个末级目录建议存放的文件数上限为 N(受底层文件系统的目录查找性能限制,XFS 上建议 N ≤ 10000,超过后 readdir/lookup 明显变慢):
单个 store_path 可容纳的文件数 = 65536 × N
N = 1000 -> 6553.6 万个文件(约 6500 万)
N = 5000 -> 3.28 亿个文件
N = 10000 -> 6.55 亿个文件
N = 30000 -> 19.7 亿个文件(性能已明显下降,不推荐)
容量估算示例:
| 平均文件大小 | 单目录 5000 文件(3.28 亿个) | 需要的磁盘空间 |
|---|---|---|
| 10KB | 3.28 亿 | 约 3.3 TB |
| 100KB | 3.28 亿 | 约 33 TB(单盘装不下,需多 store_path 或多 group) |
| 1MB | 3.28 亿 | 约 328 TB(必须多 group) |
实际约束往往先来自磁盘空间和 inode,而非目录数:
# 检查 inode 是否够用(小文件场景的隐形杀手)
df -i /data/fastdfs1
# 一个文件消耗一个 inode。若 inode 用尽,即使还有空间也无法创建文件
# XFS 是动态分配 inode 的,风险较小;ext4 在 mkfs 时固定,必须提前规划:
# mkfs.ext4 -i 4096 /dev/sdb1 # 每 4KB 一个 inode(适合海量小文件)
# mkfs.ext4 -N 200000000 /dev/sdb1 # 直接指定 inode 数量
调整建议:
subdir_count_per_path有数据后绝对不能修改(改了文件路径就对不上,全部 404)。必须在初次部署时就规划好。- 默认 256(65536 目录)对大多数场景(亿级文件)已经足够。
- 文件数预计超过 5 亿的单 store_path,考虑改用多 store_path 或多 group 分摊,而不是加大 subdir_count。
- 加大 subdir_count 会导致启动时创建/扫描大量目录,启动变慢。
- 更实际的做法是用 group 做水平扩展,每个 group 承载可控的文件数量。
1.9.3 合并存储 trunk 的适用场景
适用条件(三者需同时满足):
- 文件非常小(平均 < 16KB,如缩略图、图标、小 JSON);
- 文件数量极大(亿级以上);
- 文件很少删除(trunk 的空间回收不完善,删除多会产生碎片)。
配置(storage.conf 与 tracker.conf 都要配,见 3.2.5):
use_trunk_file = true
slot_min_size = 256
slot_max_size = 16KB
trunk_file_size = 64MB
trunk_create_file_advance = true
trunk_create_file_time_base = 02:00
trunk_create_file_interval = 86400
trunk_create_file_space_threshold = 20G
trunk_init_check_occupying = true
收益:
- inode 消耗从"每文件一个"降到"每 64MB 一个",节省 99.99%;
- 消除文件系统块对齐造成的空间浪费(4KB 块存 1KB 文件浪费 75%);
- 减少元数据操作,创建文件更快。
代价与风险:
- 引入 trunk server(由 Tracker 指定一台 Storage 统一分配 trunk 空间),成为新的中心节点与单点;
- 删除文件后的空间回收不完善,长期运行会产生碎片,且没有官方的碎片整理工具;
- appender 文件不支持 trunk;
- 出问题时排查难度显著上升(文件"不在"独立的路径上);
- 社区实践较少,坑较多。
结论:除非确实遇到 inode 耗尽或空间浪费严重的实际问题,否则保持 use_trunk_file = false。现代方案更倾向于在应用层做小文件合并,或直接使用对象存储。
1.9.4 缓存与 CDN 前置
FastDFS 存的是"一次写入多次读取"的静态资源,读多写少的特征使缓存收益极高。
用户 --> CDN 边缘节点(命中率 90%+)
| 未命中
v
Nginx 反代 + proxy_cache(本地磁盘缓存,命中率再吃掉一部分)
| 未命中
v
Storage 上的 Nginx + mod_fastdfs
|
v
磁盘
分层缓存配置要点:
# 1. Storage/Tracker 侧 Nginx 加长缓存头,让 CDN 与浏览器都能缓存
location ~/group[0-9]+/M00 {
ngx_fastdfs_module;
expires 365d; # 文件内容不可变,可设很长
add_header Cache-Control "public, immutable, max-age=31536000";
}
# 2. Tracker 侧反代加本地磁盘缓存(见 3.7.5 的 proxy_cache 配置)
proxy_cache fdfs_cache;
proxy_cache_valid 200 302 30d;
proxy_cache_lock on; # 防止缓存击穿
proxy_cache_use_stale error timeout updating; # 后端故障时用过期缓存兜底
为什么 FastDFS 特别适合配 CDN:
- file_id 一经生成永不改变,且文件内容不可修改,因此 URL 与内容一一对应,可以设置极长的缓存时间且永不需要刷新(这是最理想的 CDN 场景)。
- 删除文件后需要主动刷新 CDN 缓存(或依赖 TTL 过期)。
- 建议前端 URL 使用独立域名(如
img.example.com),便于 CDN 接入与 Cookie 隔离。
其它优化:
- 图片处理(缩略图、水印)应在上传时预生成多个尺寸分别存储,或在 CDN/Nginx 层用
image_filter模块实时处理,不要每次回源到 Storage 计算。 - 使用 FastDFS 的 slave 文件机制存放同一主文件的不同尺寸(
fdfs_test upload_slave),file_id 形如xxx_120x120.jpg。 - 开启 Nginx 的
sendfile on与tcp_nopush on,零拷贝发送静态文件。 - 开启
gzip(对 JSON/文本有效,对已压缩的 JPEG/PNG 无意义)。
1.9.5 与对象存储的取舍
| 维度 | FastDFS 自建 | MinIO 自建 | 云对象存储(OSS/S3/COS) |
|---|---|---|---|
| 接口标准 | 私有协议 + HTTP | S3 兼容(生态巨大) | S3 兼容 |
| 部署运维成本 | 低(但需自己搞定 Nginx、监控、备份) | 低到中 | 零(全托管) |
| 硬件成本 | 需自购服务器与磁盘 | 同 FastDFS | 按量付费,小规模更便宜,超大规模可能更贵 |
| 冗余机制 | 同组多副本(2~3 倍空间) | 纠删码(利用率 ~75%) | 多可用区多副本/EC(对用户透明) |
| 扩容 | 只能加 group,无 rebalance | 加节点/加盘,支持自动均衡 | 无感知,无上限 |
| 元数据能力 | 几乎没有(需自建数据库存映射) | 完整(对象自定义元数据、标签、版本) | 完整 + 生命周期、跨区复制 |
| 版本控制 | 无 | 有 | 有 |
| 权限控制 | 无(靠 Nginx/应用层做) | 有(IAM、Bucket Policy、预签名 URL) | 完善(RAM、STS、防盗链、鉴权) |
| 生命周期管理 | 无 | 有 | 有(自动转冷/归档/删除,大幅省钱) |
| CDN 集成 | 需自己接 | 需自己接 | 原生集成,一键开启 |
| 社区活跃度 | 低(近年更新极少) | 高 | 商业支持 |
| 图片处理等增值能力 | 无 | 无 | 有(实时缩放、水印、格式转换、AI 识别) |
| 数据可靠性承诺 | 自己负责 | 自己负责 | SLA 保证(如 11 个 9) |
| 适用规模 | 中小规模(几十 TB 内) | 中大规模 | 任意规模 |
选型建议:
- 新项目、能上云:直接用云对象存储(OSS/S3/COS)。省掉全部运维,还自带 CDN、图片处理、生命周期。这是 2020 年后的默认答案。
- 必须私有化部署、需要 S3 兼容:选 MinIO。生态好、纠删码省空间、K8s 友好、社区活跃。
- 已有 FastDFS 集群且运行稳定:可以继续用,不必强行迁移。但不要在新项目里引入 FastDFS。
- 必须私有化且规模巨大、需要块+文件+对象统一:选 Ceph。
- 需要像本地目录一样挂载:选 GlusterFS 或 CephFS(对象存储做不到)。
FastDFS 的现状(客观评价):项目从 2008 年开始,在国内互联网(尤其是 2012-2018 年的电商、社交)有大量生产应用,稳定性经过验证,是很好的学习材料。但近年社区活跃度大幅下降、更新极少、缺少版本管理/权限/S3 接口等现代特性、没有 rebalance 能力、生态工具匮乏。在云原生与对象存储成为标准的今天,新项目已不推荐选用。学习它的价值在于理解"如何用最简单的设计解决海量小文件问题"这一经典架构思路。
1.10 FastDFS 排错清单
1.10.1 Storage 状态不是 ACTIVE
fdfs_monitor /etc/fdfs/storage.conf | grep -E 'ip_addr|status'
按状态定位:
| 状态 | 含义 | 处理 |
|---|---|---|
FAKE_INIT (0) |
刚被 Tracker 记录,还未真正连上 | 检查 Storage 进程是否启动、端口是否可达 |
INIT (1) |
已连上 Tracker,尚未确定同步源 | 稍等;组内首个节点会直接变 ACTIVE |
WAIT_SYNC (2) |
等待同步 | 检查组内是否有 ACTIVE 的源节点;源节点挂了会一直卡住 |
SYNCING (3) |
正在同步存量数据 | 正常,等待完成;看 storaged.log 与 mark 文件进度 |
DELETED (4) |
已被从集群删除 | 被 fdfs_monitor delete 删过;重启 Storage 会重新注册 |
OFFLINE (5) |
离线 | 进程已停或网络不通 |
ONLINE (6) |
在线但不可用(未完成初始化) | 等待或查日志 |
ACTIVE (7) |
正常可服务 | 目标状态 |
# 通用排查顺序
systemctl status fdfs_storaged # CentOS 7
service fdfs_storaged status # CentOS 6
ps -ef | grep fdfs_storaged
netstat -tnlp | grep 23000 # 或 ss -tnlp | grep 23000
tail -100 /home/dfs/logs/storaged.log # $base_path/logs/
# 从任一 Tracker 上测试到 Storage 的连通性
telnet 192.168.1.221 23000
长期卡在 WAIT_SYNC 的典型原因:组内唯一的其它节点不是 ACTIVE(互相等待),或两个节点 group_name 拼写不一致导致各自成了独立组(看 group count 是不是变成了 2)。
1.10.2 Storage 连不上 Tracker
# 症状:storaged.log 中反复出现
# ERROR - file: tracker_client_thread.c, line: xxx, connect to tracker server
# 192.168.1.220:22122 fail, errno: 110, error info: Connection timed out
逐项检查:
# 1. Tracker 进程与端口
systemctl status fdfs_trackerd
netstat -tnlp | grep 22122
# 2. bind_addr 配置错误(最常见的坑!)
grep bind_addr /etc/fdfs/tracker.conf
# 如果 bind_addr 被设成了 127.0.0.1,则只监听本地回环,远程绝对连不上!
# 正确做法:留空(监听所有网卡),或填本机的对外 IP
# bind_addr =
# 3. Storage 侧的 tracker_server 地址写错
grep tracker_server /etc/fdfs/storage.conf
# 必须是【IP:端口】格式,不能只写 IP;不要写 127.0.0.1(Tracker 在别的机器上)
# 主机名要保证能解析(建议直接写 IP)
# 4. 防火墙
systemctl status firewalld # CentOS 7
firewall-cmd --list-ports
firewall-cmd --permanent --add-port=22122/tcp && firewall-cmd --reload
iptables -L -n | grep 22122 # CentOS 6
iptables -I INPUT -p tcp --dport 22122 -j ACCEPT && service iptables save
# 5. allow_hosts 白名单(tracker.conf)
grep allow_hosts /etc/fdfs/tracker.conf
# 默认 allow_hosts = *(全部允许);若被改成具体 IP 段,Storage 的 IP 必须在内
# 6. SELinux
getenforce # 应为 Disabled 或 Permissive
# 7. 网络连通性(分层验证)
ping 192.168.1.220
telnet 192.168.1.220 22122
nc -zv 192.168.1.220 22122
# 8. 两台机器的 fastdfs 版本是否一致(协议不兼容会连上后立即断开)
fdfs_trackerd -V
fdfs_storaged -V
1.10.3 上传失败:connect timed out / no available storage server
fdfs_upload_file /etc/fdfs/client.conf /etc/hosts
| 报错 | 原因与处理 |
|---|---|
connect to tracker server ... fail |
client.conf 中的 tracker_server 写错,或 Tracker 未启动、端口不通(同 3.10.2) |
no available storage server |
① 组内没有 ACTIVE 的 Storage;② 磁盘剩余空间低于 reserved_storage_space(见 3.8.6);③ store_lookup=1 但 store_group 指定了一个不存在的组名(常见!如源文档中 store_group=group2 而实际只有 group1) |
connect to storage server ... timed out |
Tracker 返回的 Storage IP 客户端访问不到:Storage 的 23000 端口未放行;或 Storage 配了内网 IP 而客户端在外网(需正确配置 Storage 的 bind_addr/网络) |
errno: 28, No space left on device |
磁盘真的满了 |
errno: 13, Permission denied |
store_path 目录权限不足(检查 run_by_user/run_by_group 与目录 owner) |
errno: 2, No such file or directory |
base_path 或 store_path 目录不存在(见 3.10.4) |
| 上传成功但返回的 group 名不对 | store_lookup/store_group 配置问题 |
# 快速定位三连
fdfs_monitor /etc/fdfs/client.conf | grep -E 'group name|active server count|ip_addr|disk free space'
df -h /data/fastdfs1
grep -E 'store_lookup|store_group|reserved_storage_space' /etc/fdfs/tracker.conf
1.10.4 启动失败:base_path / store_path 不存在或权限不对
FastDFS 不会自动创建这些目录(这是新手最常踩的坑)。
# 症状:启动瞬间退出,日志中
# ERROR - file: shared_func.c, line: xxx, "/home/dfs" can't be accessed, error info: No such file or directory
# 或
# [ERROR] file: storage_func.c, line: xxx, mkdir "/data/fastdfs1/data/00" fail
# 解决:手工创建全部目录
mkdir -p /home/dfs # base_path(tracker 与 storage 可分开)
mkdir -p /data/fastdfs1 # store_path0
# 多 store_path 时每个都要建
mkdir -p /data/fastdfs2
# 检查权限(若配置了 run_by_user/run_by_group)
grep -E 'run_by_user|run_by_group' /etc/fdfs/storage.conf
chown -R fastdfs:fastdfs /home/dfs /data/fastdfs1 # 若以 fastdfs 用户运行
chmod 755 /home/dfs /data/fastdfs1
# 检查配置里的路径拼写(不要有多余空格、不要用 ~ 、必须绝对路径)
grep -nE '^base_path|^store_path' /etc/fdfs/tracker.conf /etc/fdfs/storage.conf
# 检查 SELinux 上下文(若未关闭 SELinux)
ls -Zd /data/fastdfs1
restorecon -Rv /data/fastdfs1
首次启动成功的标志:store_path0 下自动生成了 data/00/00 ~ data/FF/FF 共 65536 个目录,以及 data/sync/、data/.data_init_flag。
ls /data/fastdfs1/data/ | head
find /data/fastdfs1/data -maxdepth 2 -type d | wc -l # 应为 65536 + 256 + 1 附近
1.10.5 同组文件不同步
# 1. 先确认双方状态与延迟
fdfs_monitor /etc/fdfs/storage.conf | grep -E 'ip_addr|last_synced_timestamp'
# 2. 检查 sync_interval / sync_start_time / sync_end_time
grep -E 'sync_interval|sync_start_time|sync_end_time|sync_wait_msec' /etc/fdfs/storage.conf
sync_start_time = 00:00 # 允许同步的时间段起点
sync_end_time = 23:59 # 终点 —— 若被限制成 01:00-05:00,白天就【完全不同步】!
sync_interval = 0 # 同步线程每次同步后休息的毫秒数,0 = 不休息(最快)
sync_wait_msec = 50 # 没有新 binlog 时的等待毫秒数
# 3. 【时间不同步】是最隐蔽的原因
# binlog 用时间戳判断同步位点,两节点时间偏差过大会导致记录被跳过或重复
date # 各节点对比
chronyc tracking # CentOS 7
ntpq -p # CentOS 6
systemctl restart chronyd && chronyc makestep # 强制立即校时
详见 08-03-时间同步与Xinetd.md
# 4. 检查 binlog 是否在增长
ls -l --time-style=full-iso /home/dfs/data/sync/
watch -n 2 'ls -l /home/dfs/data/sync/binlog.000'
tail -f /home/dfs/data/sync/binlog.000
# 5. 检查 mark 文件的 binlog_offset 是否在推进(见 3.8.5 的判断表)
cat /home/dfs/data/sync/192.168.1.221_23000.mark
# 6. 检查 Storage 之间的 23000 端口互通(同步是 Storage 直连 Storage,不经过 Tracker!)
telnet 192.168.1.221 23000 # 从 220 上执行
telnet 192.168.1.220 23000 # 从 221 上执行
# 【常见错误】只在 Tracker 上放行了端口,忘记放行 Storage 之间的 23000
# 7. 查日志中的同步错误
grep -iE 'sync|error' /home/dfs/logs/storaged.log | tail -50
# 8. 检查 group_name 是否真的一致(不一致会变成两个独立组,各自都"正常"但互不同步)
fdfs_monitor /etc/fdfs/storage.conf | grep -E 'group count|group name'
grep group_name /etc/fdfs/storage.conf # 在每个节点上比对
# 9. 终极手段:重置同步(谨慎!会触发全量重扫)
systemctl stop fdfs_storaged
mv /home/dfs/data/sync/192.168.1.221_23000.mark{,.bak}
systemctl start fdfs_storaged
1.10.6 Nginx 访问 404
这是集成阶段最高频的问题,按下面顺序逐项核对:
# 1. 先绕过 Nginx,确认文件在磁盘上真的存在
fdfs_file_info /etc/fdfs/client.conf group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
ls -l /data/fastdfs1/data/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
# 若磁盘上没有 -> 是同步问题(见 3.10.5)或文件已被删除,不是 Nginx 问题
# 2. 核对 url_have_group_name(最常见原因!)
grep url_have_group_name /etc/fdfs/mod_fastdfs.conf
url_have_group_name = true # URL 中【带】group 名:http://ip:8888/group1/M00/00/00/xxx
# url_have_group_name = false # URL 中【不带】group 名:http://ip:8888/M00/00/00/xxx
规则:URL 里带 group1/ 就必须设 true,不带就设 false。设错必 404。多 group 场景下必须设为 true(否则 Tracker 侧无法按 group 路由)。
# 3. 核对 group_name 与 group_count
grep -E 'group_name|group_count' /etc/fdfs/mod_fastdfs.conf
# 单 group:group_count = 0,group_name = group1(与 storage.conf 一致)
# 多 group 共用一份配置:group_count = 2,并配置 [group1]/[group2] 段
# 4. 核对 store_path 是否与 storage.conf 完全一致(顺序也要一致!)
grep -E 'store_path' /etc/fdfs/mod_fastdfs.conf /etc/fdfs/storage.conf
# mod_fastdfs 靠这个把 M00 映射到 /data/fastdfs1,写错就找不到文件
# 5. 核对 M00 软链接(当 store_path 不是 base_path 时必需)
ls -l /data/fastdfs1/data/M00
# 应为:M00 -> /data/fastdfs1/data
ln -s /data/fastdfs1/data /data/fastdfs1/data/M00 # 若缺失则创建
# 6. 核对 Nginx 的 location 正则是否匹配
grep -A3 'M00' /etc/nginx/conf.d/*.conf /etc/nginx/nginx.conf
# location ~/group[0-9]+/M00 { ngx_fastdfs_module; }
# 若 url_have_group_name=false,则应为 location ~/M00
# 7. 确认模块真的编译进去了
nginx -V 2>&1 | tr ' ' '\n' | grep fastdfs
# 应看到 --add-module=/usr/local/src/fastdfs-nginx-module/src
# 8. 确认 mod_fastdfs.conf 被正确加载(Nginx 启动日志)
tail -50 /var/log/nginx/error.log
grep -i fastdfs /var/log/nginx/error.log
# 正常启动会有:ngx_http_fastdfs_process_init pid=xxx
# 9. 确认 mod_fastdfs.conf 的 tracker_server 可达
grep tracker_server /etc/fdfs/mod_fastdfs.conf
# 模块要向 Tracker 查询文件所在节点,这里配错会导致跨节点回源失败
# 10. 检查 mod_fastdfs 自己的日志
grep -E 'base_path|log_level' /etc/fdfs/mod_fastdfs.conf
tail -50 /tmp/mod_fastdfs.log # 或 $base_path/logs/mod_fastdfs.log
# 11. 目录权限:Nginx 的运行用户要能读 store_path
ps -ef | grep 'nginx: worker'
namei -l /data/fastdfs1/data/00/00/ # 逐级检查路径上的权限
404 原因速查表:
| 现象 | 最可能的原因 |
|---|---|
| 所有文件都 404 | url_have_group_name 设错,或 location 正则不匹配,或模块没编进去 |
| 刚上传的文件在某一个节点上 404,另一节点正常 | 同步延迟(正常,模块会回源代理;若持续 404 则是模块的 tracker_server 配错,回源失败) |
| 只有 M01 的文件 404 | mod_fastdfs.conf 里漏配 store_path1/store_path_count |
| 只有 group2 的文件 404 | 多 group 配置(group_count、[group2] 段)或 Tracker 侧 Nginx 缺 location /group2/M00 |
| 返回 403 而非 404 | 目录权限问题,或 Nginx 的 autoindex/deny 规则 |
| 返回 500 | 模块与 Nginx 版本不兼容,或 mod_fastdfs.conf 语法/路径错误 |
1.10.7 磁盘满与空间预留
df -h; df -i # 空间与 inode 都要看
fdfs_monitor /etc/fdfs/storage.conf | grep -E 'disk total space|disk free space'
grep reserved_storage_space /etc/fdfs/tracker.conf
详细处理见 3.8.6。紧急止血:把 store_lookup 改为 1 + store_group 指向有空间的组,或立刻新增 group。切勿把 reserved_storage_space 调到 0 来"解决"问题。
1.10.8 file_id 解析与 fdfs_file_info
fdfs_file_info /etc/fdfs/client.conf group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
source storage id: 0
source ip address: 192.168.1.220
file create timestamp: 2019-03-13 16:16:17
file size: 988
file crc32: 790822154 (0x2F217D0A)
用途:
- 确认文件真实存在于集群中(比直接
ls更权威,它会向 Tracker/Storage 查询); - 定位文件的源节点(
source ip address),便于去对应机器上查日志/查磁盘; - 核对创建时间与大小,判断是否是期望的那个文件;
- 校验 CRC32,配合
fdfs_crc32比对本地文件,确认内容未损坏。
# 本地计算 CRC32 与之比对
fdfs_crc32 /path/to/local/file
# 物理路径推算:group1/M00/00/00/xxx
# group1 -> 该组任一 Storage
# M00 -> store_path0(即 /data/fastdfs1/data)
# 00/00 -> 两级目录
# => /data/fastdfs1/data/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
1.10.9 日志位置与查看
| 日志 | 路径 | 内容 |
|---|---|---|
| Tracker 日志 | $base_path/logs/trackerd.log(如 /home/dfs/logs/trackerd.log) |
启动、Storage 注册与状态变更、调度选组 |
| Storage 日志 | $base_path/logs/storaged.log |
启动、连接 Tracker、上传下载、同步过程 |
| Storage 出错日志 | $base_path/logs/storaged.log(rotate_error_log 控制切割) |
同上 |
| 客户端日志 | client.conf 的 base_path 下 logs/ |
命令行工具的输出 |
| mod_fastdfs 日志 | mod_fastdfs.conf 的 base_path 下 logs/mod_fastdfs.log(常见 /tmp/mod_fastdfs.log) |
HTTP 请求处理、回源代理 |
| Nginx 日志 | /var/log/nginx/{access,error}.log |
HTTP 状态码、404 详情 |
# 注意:源文档写的 /etc/fdfs/logs/ 是不准确的(除非 base_path 恰好设成 /etc/fdfs)
# 正确定位方式:先看配置里的 base_path
grep '^base_path' /etc/fdfs/tracker.conf /etc/fdfs/storage.conf
tail -f $(grep '^base_path' /etc/fdfs/storage.conf | cut -d= -f2 | tr -d ' ')/logs/storaged.log
# 提高日志级别以便排查(排查完记得改回 info,debug 会产生海量日志)
vim /etc/fdfs/storage.conf # log_level = debug
systemctl restart fdfs_storaged
# 快速筛错
grep -iE 'error|fail|warn' /home/dfs/logs/storaged.log | tail -50
1.10.10 重装或修改 group_name 后数据不可访问
这是最容易造成"数据丢失"错觉的坑。
| 误操作 | 后果 | 处理 |
|---|---|---|
修改 group_name |
原有 file_id 里的组名(group1)在集群中不再存在 → 全部文件无法访问;同时该节点变成一个"新组"的成员 |
改回原来的 group_name 并重启,即可恢复。生产环境绝不要改 group_name |
修改 store_path0 的路径 |
M00 映射到了新的空目录 → 全部 404 | 改回原路径,或把老数据移动到新路径下(连同目录结构) |
修改 subdir_count_per_path |
两级目录数量变了,老文件的路径算不出来 → 全部 404 | 改回原值。有数据后绝对不能改 |
修改 port(23000) |
Tracker 会把它当成一台新的 Storage(Storage 的唯一标识是 IP:port) |
改回原端口 |
| 重装系统但保留数据盘 | base_path 下的 data/sync/*.mark、.data_init_flag 丢失 → 被当作新节点,触发全量同步 |
若数据盘完好,恢复 /etc/fdfs/*.conf 后启动即可;同步会自动补齐 |
删除 .data_init_flag |
启动时认为路径未初始化,可能重建目录结构 | 不要删;已删则从日志确认是否影响,通常重启后会重新生成 |
删除 store_path 下的 M00 软链接 |
HTTP 访问 404(fdfs_download_file 仍可用) |
重建软链接(见 3.10.6 第 5 步) |
同组两节点的 store_path 数量/顺序不一致 |
M01 文件在另一节点上同步失败 | 统一配置 |
# 恢复前先备份配置,便于回退
cp -a /etc/fdfs /etc/fdfs.bak.$(date +%F)
# 核对当前集群中实际存在的组名
fdfs_monitor /etc/fdfs/client.conf | grep 'group name'
# 若不小心创建了多余的组,从 Tracker 中删除该节点记录
fdfs_monitor /etc/fdfs/client.conf delete group2 192.168.1.221
运维纪律:/etc/fdfs/*.conf 应纳入配置管理(Ansible/Git),任何修改走评审;group_name、port、store_path、subdir_count_per_path 四项一经上线视为不可变。
二、GlusterFS 与 FastDFS 对比与选型
前面两部分讲的是两个完全不同的产品:GlusterFS 是分布式文件系统(可以像本地目录一样挂载使用),FastDFS 是分布式文件存储服务(只能通过 API/HTTP 访问)。二者不是竞品,解决的问题域不同。本节做系统性对比,帮助在实际项目中做出正确选择。
4.1 详细对照表
| 对比维度 | GlusterFS | FastDFS |
|---|---|---|
| 产品定位 | 通用分布式文件系统(Scale-Out NAS) | 专用海量中小文件存储服务 |
| 架构模式 | 完全对称、无中心节点(无元数据服务器) | 有中心节点:Tracker(调度)+ Storage(存储) |
| 元数据管理 | 无独立元数据服务,用弹性哈希(Davies-Meyer)算法根据文件路径名实时计算位置 | 位置信息编码在 file_id 中由客户端持有;Tracker 只管调度不存文件元数据 |
| 元数据单点风险 | 无 | Tracker 无状态可水平扩展,多台 Tracker 互为冗余,无单点 |
| 访问方式 | POSIX 挂载:FUSE(原生,推荐)、NFS、Samba/CIFS、libgfapi | 不支持挂载:客户端 API(C/Java/PHP/Python)、命令行工具、HTTP(需 Nginx 模块) |
能否 cd/ls/vim 直接操作 |
能,挂载后与本地目录无差别 | 不能,必须通过接口 |
| 文件名 | 用户自己决定,保留原始目录结构与文件名 | 由系统生成 file_id,原始文件名需应用自己存数据库 |
| 目录结构 | 完整支持任意层级目录 | 无目录概念(内部两级散列目录对用户不可见) |
| 擅长的文件大小 | 中大文件(MB ~ GB ~ TB),大文件可用条带/分片(shard) | 中小文件(4KB ~ 500MB,最佳 4KB~100MB) |
| 小文件性能 | 较差(每次访问需哈希计算 + LOOKUP,元数据操作放大) | 优秀(这是它的设计目标) |
| 大文件支持 | 优秀(分布式卷/条带卷/分片,单文件可超单盘容量) | 一般(单文件必须完整放在一个 Storage 的一块盘上,受单盘容量限制) |
| 随机读写/修改 | 支持(POSIX 语义完整,可 seek、可覆写、可锁) | 不支持修改,只支持追加(appender 文件);一次写入多次读取 |
| 冗余机制 | 复制卷(replica N,多副本)、纠删码卷(dispersed,空间利用率高)、arbiter 仲裁盘 | 同组多副本(组内所有节点数据完全相同,副本数 = 组内节点数) |
| 空间利用率 | 分布式卷 100%;复制卷 1/N;纠删码卷可达 (k)/(k+m),如 4+2 为 67% | 1/N(N = 组内节点数),没有纠删码 |
| 一致性模型 | 强一致(同步复制,写入所有副本才返回)+ self-heal 修复 | 最终一致(异步 binlog 同步,秒级延迟窗口) |
| 一致性风险 | 网络分区可能导致脑裂(需 quorum 防护、需人工修复) | 无脑裂概念,但有同步延迟窗口(刚上传的文件在其它节点上暂不可读,由 Nginx 模块回源解决) |
| 扩容方式 | add-brick + rebalance 自动迁移数据重新均衡 |
只能加新 group(增加容量)或加 store_path(加盘);没有 rebalance,老数据不迁移 |
| 缩容/数据迁移 | remove-brick start/status/commit 三段式自动迁移数据 |
无内置迁移能力,需应用层重传 |
| 在线扩容对业务影响 | 可在线扩容,rebalance 期间有 I/O 压力 | 加组对业务无影响(新写入才用新组) |
| 配额 | 支持(目录级 quota limit-usage) |
不支持(只有全局 reserved_storage_space) |
| 快照 | 支持(需 LVM 精简卷底层) | 不支持 |
| 单文件容量上限 | 理论上受卷总容量限制(条带/分片可跨盘) | 受单块盘容量限制 |
| 集群规模 | 几台到上百台(元数据无瓶颈,但节点越多 LOOKUP 广播开销越大) | Tracker 2~3 台足够;Storage 可几十到上百台(按 group 划分) |
| 部署复杂度 | 中(yum 安装即可,但卷类型选择、quorum、调优需要理解) | 中(需编译安装、配置文件项多、Nginx 模块集成较繁琐) |
| 运维复杂度 | 较高(脑裂修复、rebalance、self-heal 都需要理解与人工介入) | 较低(架构简单、状态少;但缺工具,出问题靠日志) |
| 排错难度 | 较高(日志分散、xattr 层面问题需要 getfattr) |
中(日志集中、状态机清晰) |
| 依赖组件 | 无(自包含);Samba/NFS 导出时需对应服务 | 强依赖 Nginx + fastdfs-nginx-module 才能提供 HTTP 访问 |
| 权限控制 | POSIX 权限 + ACL(挂载后与本地文件系统一致) | 无(需在 Nginx/应用层自己做鉴权、防盗链) |
| 加密/传输安全 | 支持 SSL/TLS(管理与数据通道) | 无(需靠网络隔离/HTTPS 前端) |
| 社区与现状 | 活跃度已下降(Red Hat 2019 后重心转向 Ceph,2023 年宣布 Gluster 进入维护模式),但仍在维护,文档完善 | 活跃度很低(近年更新极少,社区基本停滞) |
| 商业支持 | Red Hat Gluster Storage(已逐步退场) | 无 |
| 主要开发语言 | C | C |
| 资源占用 | 中(每 brick 一个 glusterfsd 进程;FUSE 有上下文切换开销) | 低(单进程,几十 MB 内存) |
| 典型场景 | 虚拟机镜像存储、视频/日志/备份归档、共享 Home 目录、Web 集群共享目录、HPC 数据集 | 电商/社交的图片、头像、缩略图、附件、短视频、静态资源分发 |
| 典型部署年代 | 2010~2020 | 2010~2018(国内互联网) |
4.2 关键差异的直观理解
用一句话概括:
- GlusterFS:把多台机器的磁盘拼成一个"网络硬盘",挂载上去当本地目录用。你写
/mnt/data/2026/report.pdf,它就在那儿,路径和文件名都是你的。 - FastDFS:一个"文件寄存柜",你交给它一个文件,它还你一张票(file_id),凭票取件。票必须自己保管好(存数据库),文件叫什么名字它不关心。
具体差异带来的实际影响:
# 场景:Web 集群三台机器要共享上传的图片目录
# 用 GlusterFS:
应用代码几乎不用改 —— 只要把 /var/www/uploads 挂载成 GlusterFS 卷
$ mount -t glusterfs node1:/web-volume /var/www/uploads
$ # 原来的 move_uploaded_file()、file_get_contents() 全部照常工作
优点:零改造成本
缺点:小图片多时性能一般;Nginx 直接读 FUSE 挂载点有额外开销
# 用 FastDFS:
必须改应用代码 —— 上传时调 SDK,把返回的 file_id 存进数据库
$ // $fid = $fdfs->upload_by_filename($tmp_file);
$ // INSERT INTO images (name, fid) VALUES ('头像.jpg', 'group1/M00/00/00/wKg...');
$ // 前端 URL: http://img.example.com/group1/M00/00/00/wKg...
优点:小文件性能好,天然适合 CDN,水平扩展简单
缺点:需要改造应用;文件名与目录结构的语义完全丢失
| 你的需求 | 选择 |
|---|---|
| 应用不想改代码,要求像本地目录一样读写 | GlusterFS |
| 需要保留原始目录结构与文件名 | GlusterFS |
| 需要随机读写/修改已有文件(数据库文件、日志追加、虚拟机磁盘) | GlusterFS |
| 存大文件(视频原片、备份包、镜像、数据集) | GlusterFS |
| 需要目录配额、快照 | GlusterFS |
多台 Web 服务器共享 uploads/session/静态资源目录 |
GlusterFS(简单)或对象存储(更现代) |
| 存海量中小图片,由应用通过接口访问 | FastDFS 或对象存储 |
| 文件一次写入、多次读取、永不修改 | FastDFS 或对象存储 |
| 前端要挂 CDN 加速静态资源 | FastDFS 或对象存储(URL 天然不变) |
| 应用愿意改造为接口访问 | 对象存储优先,其次 FastDFS |
| 要求空间利用率高(不愿意付 2~3 倍副本成本) | GlusterFS 纠删码卷 / MinIO / Ceph |
| 要求 S3 兼容接口 | MinIO / 云对象存储(GlusterFS 与 FastDFS 都不行) |
| 要求 块存储给虚拟机/数据库用 | Ceph RBD(两者都不合适) |
| 全新项目、可以上云 | 云对象存储(OSS/S3/COS) |
4.3 明确的选型建议
结论一:需要"像本地目录一样挂载"、需要处理大文件、需要 POSIX 语义 → 选 GlusterFS。
典型判断依据:
- 应用是遗留系统,用的是标准文件 I/O,改造成 API 访问成本太高;
- 存的是虚拟机镜像、视频原片、备份文件、科学计算数据集等大文件;
- 需要多台服务器同时读写同一份数据(共享存储语义);
- 需要目录级配额或卷快照。
推荐配置:分布式复制卷(replica 2 + arbiter,或 replica 3) 用于一般场景;分布式纠删码卷(如 4+2) 用于对空间利用率敏感的归档场景;万兆网络;独立数据盘做 brick。
结论二:海量图片等小文件、应用愿意通过接口访问 → 选 FastDFS,或直接上对象存储。
典型判断依据:
- 文件是"写一次读很多次、永不修改"的静态资源(图片、头像、附件、短视频);
- 单个文件不大(几 KB 到几十 MB),但数量在千万到亿级;
- 前端要走 CDN;
- 应用可以接受"上传拿 file_id、下载凭 file_id"的模式,并有地方(数据库)存映射关系。
但在 2020 年之后的新项目中,这个场景的首选答案已经变成对象存储(云上用 OSS/S3/COS,私有化用 MinIO),原因见结论三。
推荐配置(若确定用 FastDFS):2 台 Tracker + 每组 2 台 Storage;Tracker 前置 Nginx/Keepalived 做统一入口;url_have_group_name = true;提前规划好 group 划分;配好 reserved_storage_space 与完整监控;独立做备份(同组副本不是备份)。
结论三:云原生时代的处境与替代趋势。
| 时期 | 主流选择 | 原因 |
|---|---|---|
| 2008~2013 | FastDFS、MooseFS、TFS、GlusterFS 兴起 | 缺少成熟的开源对象存储;机房自建为主 |
| 2013~2018 | GlusterFS(文件)、FastDFS(图片)、Ceph 崛起、HDFS(大数据) | 各自占据细分场景;国内互联网大量落地 FastDFS |
| 2018~2022 | Ceph 统一存储、MinIO、云对象存储快速取代前两者 | S3 接口成为事实标准;纠删码降低成本;K8s 需要 CSI 与对象存储 |
| 2022 至今 | 云对象存储为默认答案;私有化选 MinIO/Ceph;K8s 场景 Longhorn/Rook-Ceph/OpenEBS | 全托管、零运维、按量付费、自带 CDN/生命周期/版本控制 |
具体的替代关系:
- FastDFS → MinIO / 云对象存储。MinIO 提供 S3 兼容接口、纠删码(空间利用率约 75% 对比 FastDFS 的 50%)、完整的权限与版本控制、活跃社区、K8s 原生支持。云对象存储更进一步,连运维都不需要。
- GlusterFS → CephFS / 云文件存储(NAS)/ JuiceFS。需要 POSIX 挂载时,CephFS 功能更完整且社区更活跃;云上直接买 NAS/EFS;JuiceFS 则把对象存储包装成 POSIX 文件系统,兼具两者优点。
- 两者共同的替代者:云对象存储。对绝大多数"存文件"的需求,OSS/S3/COS 是成本、可靠性、运维成本综合最优的方案。
那还有必要学吗?非常有必要:
- 架构思想具有普适性:GlusterFS 的"无元数据服务器 + 一致性哈希"是理解 Cassandra、Dynamo、Ceph CRUSH 的基础;FastDFS 的"位置信息编码进 ID"是理解各类短链接、CDN 回源、分片路由设计的经典案例。
- 存量系统大量存在:国内很多 2015~2019 年上线的系统仍跑在 FastDFS/GlusterFS 上,运维岗位仍需要能接手、能排错、能迁移。
- 面试与考试的高频考点:卷类型、脑裂、rebalance、file_id 结构都是常见考题。
- 理解权衡:为什么 FastDFS 不做 POSIX?为什么 GlusterFS 小文件慢?搞懂这些"取舍",才能在面对新技术时做出正确判断。
4.4 混合使用与迁移建议
混合部署是常见的现实选择:
业务应用
/ \
大文件 / 需要挂载 海量小图片 / CDN 分发
| |
GlusterFS 卷 对象存储(或 FastDFS)
/mnt/videos /mnt/backup img.example.com
从 FastDFS 迁移到对象存储的可行路径(无停机):
- 双写阶段:应用上传时同时写 FastDFS 和对象存储,数据库同时记录两个地址;读仍走 FastDFS。
- 历史数据搬迁:后台任务遍历数据库中的 file_id,从 FastDFS 下载并上传到对象存储,回填新地址。
- 切读:读优先走对象存储,失败时回落到 FastDFS(灰度放量)。
- 停写:确认新链路稳定后,停止写 FastDFS。
- 观察期:保留 FastDFS 集群 1~3 个月只读,确认无回源请求。
- 下线:备份一份冷数据后下线集群。
从 GlusterFS 迁移相对简单:因为它是 POSIX 挂载,可以直接用 rsync 把数据同步到新的 NFS/CephFS/JuiceFS 挂载点,然后切换挂载点即可,应用无需改造。
# GlusterFS 迁移示例:先全量后增量,最后短暂停机做最后一次增量并切挂载点
rsync -avHAX --numeric-ids --info=progress2 /mnt/gluster-vol/ /mnt/new-storage/
# 多轮增量
rsync -avHAX --delete /mnt/gluster-vol/ /mnt/new-storage/
# 停业务 -> 最后一次 rsync -> 修改 /etc/fstab -> 重新挂载 -> 启业务
练习
练习四 搭建双 Tracker + 双 Storage 的 FastDFS 集群
题目:用 4 台(或 2 台复用)CentOS 7 主机搭建 FastDFS 集群,要求:
- 2 个 Tracker 互为冗余,2 个 Storage 同组互备;
- 完成上传、下载、删除、查看文件信息;
- 集成 Nginx,实现通过 HTTP 访问文件;
- 在 Tracker 侧配置统一入口,任意一个入口都能访问所有文件。
参考答案:
# ===== 规划 =====
# 192.168.1.220 tracker1 + storage1(group1)
# 192.168.1.221 tracker2 + storage2(group1)
# ===== 1. 系统准备(两台都做)=====
systemctl stop firewalld && systemctl disable firewalld
setenforce 0
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/sysconfig/selinux
hostnamectl set-hostname --static server-node1 # 另一台 server-node2
cat >> /etc/hosts << 'EOF'
192.168.1.220 server-node1
192.168.1.221 server-node2
EOF
systemctl start chronyd && systemctl enable chronyd
mkdir -p /home/dfs /data/fastdfs1 # 目录必须手工创建!
# ===== 2. 安装(两台都做)=====
cd fastdfs && ./install.sh # 或手工编译 libfastcommon + fastdfs
netstat -tnlp | grep -E '22122|23000'
# ===== 3. tracker.conf(两台都改)=====
vim /etc/fdfs/tracker.conf
# bind_addr = (留空,不要写 127.0.0.1)
# port = 22122
# base_path = /home/dfs
# store_lookup = 0 (轮询;若只有一组也可用 2)
# store_group = group1 (store_lookup=1 时才生效,注意不要写成不存在的组名)
# reserved_storage_space = 20%
# ===== 4. storage.conf(两台都改,配置【两个】tracker)=====
vim /etc/fdfs/storage.conf
# group_name = group1 (两台必须一致!)
# port = 23000
# base_path = /home/dfs
# store_path_count = 1
# store_path0 = /data/fastdfs1
# subdir_count_per_path = 256
# tracker_server = 192.168.1.220:22122
# tracker_server = 192.168.1.221:22122
# http.server_port = 8888
# ===== 5. client.conf =====
vim /etc/fdfs/client.conf
# base_path = /home/dfs
# tracker_server = 192.168.1.220:22122
# tracker_server = 192.168.1.221:22122
# ===== 6. 启动(先 Tracker 后 Storage)=====
systemctl restart fdfs_trackerd && systemctl enable fdfs_trackerd
systemctl restart fdfs_storaged && systemctl enable fdfs_storaged
# CentOS 6:service fdfs_trackerd restart / service fdfs_storaged restart
# ===== 7. 验证集群 =====
fdfs_monitor /etc/fdfs/storage.conf
# 要点:tracker server count = 2;group count = 1;
# storage server count = 2;active server count = 2;
# 两个节点的 status 都必须是 ACTIVE (7)
# ===== 8. 上传/下载/信息/删除 =====
fdfs_upload_file /etc/fdfs/client.conf README
# 返回:group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
FID=group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
fdfs_file_info /etc/fdfs/client.conf $FID # 看源 IP、时间、大小、CRC32
fdfs_download_file /etc/fdfs/client.conf $FID /tmp/down.README
md5sum README /tmp/down.README # 两个 md5 必须一致
fdfs_delete_file /etc/fdfs/client.conf $FID # 测试完再删
# ===== 9. Nginx 集成(两台 Storage 都做)=====
# 编译(fastdfs-nginx-module)
vim /usr/local/src/fastdfs-nginx-module/src/config # 去掉路径中的 /local
cd /usr/local/src/nginx-1.20.2
./configure --prefix=/usr/local/nginx \
--add-module=/usr/local/src/fastdfs-nginx-module/src
make && make install
cp /usr/local/src/fastdfs-nginx-module/src/mod_fastdfs.conf /etc/fdfs/
cp /usr/local/src/fastdfs-5.11/conf/{http.conf,mime.types} /etc/fdfs/
ln -s /data/fastdfs1/data /data/fastdfs1/data/M00
vim /etc/fdfs/mod_fastdfs.conf
# base_path = /tmp
# tracker_server = 192.168.1.220:22122
# tracker_server = 192.168.1.221:22122
# group_name = group1
# url_have_group_name = true (URL 里带 group1,必须为 true!)
# store_path_count = 1
# store_path0 = /data/fastdfs1
# response_mode = proxy (同步未完成时代理回源,避免 404)
# Storage 侧 nginx.conf
# server { listen 8888;
# location ~/group[0-9]+/M00 { ngx_fastdfs_module; } }
/usr/local/nginx/sbin/nginx -t && /usr/local/nginx/sbin/nginx
# ===== 10. Tracker 侧统一入口 =====
# server { listen 80;
# location /group1/M00 { proxy_pass http://fdfs_group1; proxy_cache fdfs_cache; } }
# upstream fdfs_group1 { server 192.168.1.220:8888; server 192.168.1.221:8888; }
nginx -t && nginx -s reload
# ===== 11. 验证 HTTP 访问 =====
FID=$(fdfs_upload_file /etc/fdfs/client.conf /etc/hosts)
curl -I http://192.168.1.220:8888/$FID # 直连 storage1
curl -I http://192.168.1.221:8888/$FID # 直连 storage2(可能触发回源代理)
curl -I http://192.168.1.220/$FID # 统一入口 1
curl -I http://192.168.1.221/$FID # 统一入口 2
# 四个都应返回 200 OK
故障演练(必做):
# 停掉 tracker1,验证上传仍然成功(Tracker 无状态、可冗余)
systemctl stop fdfs_trackerd # 在 220 上
fdfs_upload_file /etc/fdfs/client.conf /etc/hosts # 仍应成功
systemctl start fdfs_trackerd
# 停掉 storage1,验证下载仍然成功(同组互备)
systemctl stop fdfs_storaged # 在 220 上
curl -I http://192.168.1.221/$FID # 仍应 200
fdfs_monitor /etc/fdfs/client.conf | grep -E 'ip_addr|active server count' # 220 变 OFFLINE
systemctl start fdfs_storaged # 恢复后自动补同步
练习五 验证 FastDFS 同组同步
题目:验证同组两个 Storage 之间的文件同步,说明同步机制与观察方法。
参考答案:
# ===== 1. 在 node1(220)上上传文件 =====
fdfs_upload_file /etc/fdfs/client.conf README
# group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
# ===== 2. 立即在两个节点上分别 curl 下载并对比 =====
# 在 220 上
curl -O http://192.168.1.220:8888/group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
# 在 221 上
curl -O http://192.168.1.221:8888/group1/M00/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
# 两个文件内容应完全相同
md5sum wKgB3FyovhGAY8lJAAAD3C8hfQo2554045 README
# ===== 3. 直接检查物理磁盘(最直观的证据)=====
# 在两个节点上分别执行
ls -l /data/fastdfs1/data/00/00/wKgB3FyovhGAY8lJAAAD3C8hfQo2554045
# 两边都应存在,大小相同
# ===== 4. 观察同步日志 =====
tail -f /home/dfs/logs/storaged.log
# 注意:源文档写的是 /etc/fdfs/logs/storaged.log,实际路径是 $base_path/logs/
# 可以看到:
# [INFO] ... selection tracker server is 192.168.1.221:22122 <-- 221 被选为 leader tracker
# [INFO] ... successfully connect to storage server 192.168.1.220:23000
# [INFO] ... sync file to 192.168.1.221:23000 successfully <-- 文件从 220 同步到 221
# ===== 5. 检查同步延迟 =====
fdfs_monitor /etc/fdfs/storage.conf | grep -E 'ip_addr|last_source_update|last_sync_update|last_synced_timestamp'
# last_synced_timestamp 后的 (0s delay) 表示无延迟
# ===== 6. 检查 binlog 与 mark 文件 =====
tail -5 /home/dfs/data/sync/binlog.000
# 1553836564 C M00/00/00/wKgB3Fyov... <-- 大写 C:本机作为源创建
# 1553836601 c M00/00/00/wKgB3Vyov... <-- 小写 c:从别处同步来的创建
cat /home/dfs/data/sync/192.168.1.221_23000.mark
# binlog_offset 应持续推进;need_sync_old=0 且 sync_old_done=1 表示存量已同步完
# ===== 7. 批量压测同步 =====
for i in $(seq 1 200); do fdfs_upload_file /etc/fdfs/client.conf /etc/hosts >> /tmp/fids.txt; done
# 在两个节点上对比文件总数
find /data/fastdfs1/data -type f ! -name 'binlog*' ! -name '*.mark' | wc -l
关于"leader"的重要澄清:日志中出现的 selection tracker server is 192.168.1.221 指的是 Storage 从多个 Tracker 中选出一个作为同步协调的 leader tracker(用于协调组内同步的时间基准),不是说 221 这台机器成了"主节点"、220 成了"从节点"。FastDFS 的同组 Storage 是完全对等的,任何节点都可以接收上传,也都可以作为同步源,没有主从之分。
同步机制要点:
| 要点 | 说明 |
|---|---|
| 同步方向 | Storage 直连 Storage(走 23000 端口),不经过 Tracker。所以 Storage 之间的 23000 也必须放行 |
| 同步方式 | 异步:上传写完本地即返回,后台线程读 binlog 推送到同组其它节点 |
| 一致性 | 最终一致,存在秒级延迟窗口 |
| 延迟窗口的问题 | 刚上传的文件立即从另一节点读取可能 404 —— 由 fastdfs-nginx-module 的 response_mode = proxy 解析 file_id 中的源 IP 回源代理来解决 |
| 断点续传 | mark 文件记录 binlog_offset,节点重启或网络恢复后从断点继续,不会重头开始 |
| 新节点加入 | 触发全量同步(need_sync_old=1),状态经 WAIT_SYNC → SYNCING → ACTIVE |
| 时间同步的重要性 | binlog 用时间戳判断位点,节点间时间偏差会导致同步异常,必须配好 NTP/chrony |
此处评论已关闭