先交代背景:我的数据丢了一次
树莓派5,8GB版本,官方电源,外壳带风扇。装了Jellyfin、Transmission、Samba、Docker(20+容器)。系统跑在32GB TF卡上,数据放在一块绿联USB3.0硬盘盒+2.5寸机械硬盘里。
稳定跑了3个月,某天突然报错:Samba共享目录读不出来,Jellyfin的媒体库全部红色。SSH上去看,dmesg刷屏:
[68432.194607] usb 2-1: device descriptor read/64, error -71
[68432.410513] usb 2-1: device descriptor read/64, error -71
[68432.626950] usb 2-1: reset high-speed USB device number 3 using xhci-hcd
[68433.082840] usb 2-1: device descriptor read/64, error -71
[68433.299235] usb 2-1: device descriptor read/64, error -71
[68433.515703] usb 2-1: reset high-speed USB device number 3 using xhci-hcd
error -71,协议错误,USB设备直接掉线。硬盘盒的灯还亮着,但系统已经认不到盘了。重启后文件系统检查,坏了一堆目录,transmission的下载目录直接无法挂载。丢了大概200GB的电视剧和电影,外加一份没来得及备份的家庭照片目录。
这就是我写这篇文章的原因。如果你打算用树莓派当家庭服务器,机械硬盘+USB硬盘盒这条路,我替你走过了,是死的。
问题拆解:为什么会掉盘
树莓派5的USB端口走的是PCIe总线,理论上带宽不是瓶颈。真正的问题是供电和USB桥接芯片的兼容性。
- 供电不足:树莓派5官方电源是5V/5A,但USB口对外输出电流有限制。2.5寸机械硬盘峰值电流可达1A以上,USB硬盘盒没有独立供电,电压跌落直接导致设备复位。
- USB桥接芯片兼容性:市面上大多数硬盘盒用的是JMicron或ASMedia桥接芯片,这些芯片在ARM Linux下经常出现UAS(USB Attached SCSI)协议兼容问题。表现为不定期的error -71或error -110。
- 电源管理:Linux内核的USB autosuspend机制会在硬盘空闲时发送休眠指令,部分硬盘盒固件处理不了,直接掉线。
一句话总结:USB硬盘盒+机械硬盘,不适合7x24小时运行的服务器场景。
方案对比:我试过的两条路
方案A:USB硬盘盒 + 2.5寸机械硬盘(失败)
硬件:绿联USB3.0硬盘盒(JMicron JMS578主控)+ 希捷2.5寸 2TB机械盘
系统:Raspberry Pi OS Bookworm 2024-07-04,内核6.6.31
文件系统:ext4
结果:3个月内掉盘7次,每次都要手动重启或重新插拔。smartctl查不到硬盘健康状态,因为硬盘盒的SAT(SCSI to ATA Translation)命令映射不完整。数据损坏一次,丢失200GB+。
| 指标 | 实测结果 |
|---|---|
| 掉盘次数(3个月) | 7次 |
| 平均无故障时间 | 约12天 |
| 4K随机读延迟 | 25.8ms |
| 持续写入速度 | 112MB/s |
| smartctl信息 | 无法读取 |
方案B:NVMe SSD + USB3.2硬盘盒(可行但有坑)
硬件:ORICO M.2 NVMe硬盘盒(RTL9210B主控)+ 致态TiPlus5000 1TB
系统:同上
文件系统:ext4 → 最终换成了btrfs
换掉机械盘后,掉盘问题立刻消失,但RTL9210B在特定固件版本下和树莓派5有兼容问题:热插拔偶尔不识别,固定IP的NFS共享在硬盘休眠唤醒后会有30秒延迟。后来升级硬盘盒固件到v1.30.12解决。
| 指标 | 实测结果 |
|---|---|
| 掉盘次数(3个月) | 0次 |
| 4K随机读延迟 | 1.2ms |
| 持续写入速度 | 780MB/s |
| smartctl信息 | 完整读取 |
文件系统:ext4 还是 btrfs?
我不是文件系统原教旨主义者。ext4稳定,但家庭服务器的核心需求不是性能,是数据可恢复性。btrfs的写时复制(CoW)、快照、校验和功能,能让你在误删文件或者系统崩溃时快速恢复。代价是碎片化略高、CPU占用略高,但树莓派5是4核A76,完全不是瓶颈。
我的选择:btrfs,单盘,不开RAID。
最终方案:完整代码实现
第一步:系统安装与基础优化
Raspberry Pi OS Bookworm(2024-07-04),树莓派5 8GB。下载镜像后,用树莓派官方Imager写入TF卡。这里有一个关键优化:TF卡写入量过大容易损坏,把日志和Docker数据目录挪到SSD上。
# 1. 更新系统
sudo apt update && sudo apt full-upgrade -y
# 2. 关闭USB autosuspend(重要!)
sudo sed -i 's/GRUB_CMDLINE_LINUX_DEFAULT="[^"]*/GRUB_CMDLINE_LINUX_DEFAULT="usbcore.autosuspend=-1"/' /etc/default/grub
# 树莓派没有GRUB,改用下面这种方式
echo 'usbcore.autosuspend=-1' | sudo tee -a /boot/firmware/cmdline.txt
# 3. 禁用蓝牙和WiFi(如果不用,减少中断干扰和功耗)
sudo tee -a /boot/firmware/config.txt <<'EOF'
dtoverlay=disable-bt
dtoverlay=disable-wifi
EOF
# 4. 调整vm.swappiness,减少内存交换
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swap.conf
sudo sysctl -p /etc/sysctl.d/99-swap.conf
# 5. 日志写入tmpfs,减少TF卡写入
echo 'tmpfs /var/log tmpfs defaults,noatime,nosuid,mode=0755,size=128M 0 0' | sudo tee -a /etc/fstab
sudo mount -a
# 6. 安装btrfs工具
sudo apt install -y btrfs-progs
第二步:NVMe SSD挂载与优化
# 查看设备名(一般是nvme0n1)
lsblk
# 分区
sudo parted /dev/nvme0n1 --script mklabel gpt
sudo parted /dev/nvme0n1 --script mkpart primary btrfs 0% 100%
sudo mkfs.btrfs -f -L data /dev/nvme0n1p1
# 挂载,带relatime和ssd优化参数
# 获取NVMe盘的UUID
UUID=$(sudo blkid -s UUID -o value /dev/nvme0n1p1)
echo "UUID=$UUID /mnt/data btrfs defaults,noatime,ssd,compress=zstd:1,space_cache=v2,autodefrag 0 0" | sudo tee -a /etc/fstab
sudo mount -a
# 检查挂载结果
df -h /mnt/data
参数说明:compress=zstd:1是zstd压缩级别1,压缩率不算最高但延迟低。autodefrag会自动整理碎片。这些都是针对单盘btrfs的合理配置。
第三步:Docker目录迁移
Docker默认目录在TF卡上,必须迁移。Docker 26.1.4 + Docker Compose v2.27.0。
# 停止Docker
sudo systemctl stop docker docker.socket
# 迁移数据(首次迁移约20GB,用rsync)
sudo rsync -avxP /var/lib/docker/ /mnt/data/docker/
sudo mv /var/lib/docker /var/lib/docker.bak
# 修改Docker数据目录
echo '{"data-root": "/mnt/data/docker"}' | sudo tee /etc/docker/daemon.json
# 启动Docker
sudo systemctl start docker
# 确认生效
docker info | grep "Docker Root Dir"
# 输出:Docker Root Dir: /mnt/data/docker
# 确认所有容器正常运行
docker ps | wc -l
第四步:Samba + Jellyfin + Transmission完整配置
直接从我的生产环境拷出来的Docker Compose配置。
# docker-compose.yml
version: "3.8"
services:
samba:
image: dperson/samba:latest
container_name: samba
environment:
- TZ=Asia/Shanghai
volumes:
- /mnt/data/media:/media
- /mnt/data/samba-config:/etc/samba
ports:
- "445:445"
- "139:139"
command: >
-s "Media;/media;yes;no;no;pi"
-p
restart: unless-stopped
jellyfin:
image: jellyfin/jellyfin:10.9.11
container_name: jellyfin
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
volumes:
- /mnt/data/jellyfin/config:/config
- /mnt/data/jellyfin/cache:/cache
- /mnt/data/media:/media
ports:
- "8096:8096"
devices:
- /dev/dri:/dev/dri # 树莓派5没有,这行可以删
restart: unless-stopped
transmission:
image: lscr.io/linuxserver/transmission:4.0.6
container_name: transmission
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
volumes:
- /mnt/data/transmission/config:/config
- /mnt/data/downloads:/downloads
- /mnt/data/media/movies:/movies
ports:
- "9091:9091"
- "51413:51413"
- "51413:51413/udp"
restart: unless-stopped
启动命令:
cd /home/pi/server
docker compose up -d
docker compose ps
# 确认3个容器都是Up状态
第五步:btrfs快照脚本(数据救援核心)
这是整篇最值得抄的代码。每天凌晨2点自动打快照,保留最近7天,每周快照保留4周。快照是秒级完成的,一个1TB的卷几秒钟就打完。
#!/bin/bash
# /usr/local/bin/btrfs-snapshot.sh
# 用法:btrfs-snapshot.sh daily 或 btrfs-snapshot.sh weekly
SUBVOL=/mnt/data
SNAP_BASE=/mnt/data/.snapshots
KEEP_DAILY=7
KEEP_WEEKLY=4
DATE=$(date +%Y%m%d_%H%M%S)
TYPE=$1
if [ "$TYPE" = "daily" ]; then
SNAP_DIR="$SNAP_BASE/daily"
KEEP=$KEEP_DAILY
elif [ "$TYPE" = "weekly" ]; then
SNAP_DIR="$SNAP_BASE/weekly"
KEEP=$KEEP_WEEKLY
else
echo "Usage: $0 [daily|weekly]"
exit 1
fi
mkdir -p "$SNAP_DIR"
SNAPSHOT="$SNAP_DIR/$DATE"
# btrfs快照的子卷路径不能以/结尾
btrfs subvolume snapshot "$SUBVOL" "$SNAPSHOT"
# 清理过期快照
COUNT=$(ls -1 "$SNAP_DIR" | sort | wc -l)
if [ "$COUNT" -gt "$KEEP" ]; then
# 删除最老的快照
OLDEST=$(ls -1 "$SNAP_DIR" | sort | head -1)
btrfs subvolume delete "$SNAP_DIR/$OLDEST"
fi
echo "Snapshot created: $SNAPSHOT"
配合crontab定时执行:
# 编辑crontab
crontab -e
# 添加定时任务
0 2 * * * /usr/local/bin/btrfs-snapshot.sh daily
0 3 * * 0 /usr/local/bin/btrfs-snapshot.sh weekly
第六步:健康检查脚本
没有监控的服务器是不完整的。这个脚本检查磁盘温度、剩余空间、Docker容器状态,异常时发到钉钉/企业微信机器人。
// 企业微信机器人Webhook,替换成你自己的
// 在群里添加机器人,获取Webhook URL
// 参考文档:https://developer.work.weixin.qq.com/document/path/91770
#!/bin/bash
# /usr/local/bin/health-check.sh
# 需要安装: sudo apt install -y lm-sensors jq
WEBHOOK_URL="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY_HERE"
SERVER_NAME="pi-server"
# 检查磁盘温度
TEMP=$(sensors -u | grep -Po '(?<=temp1_input: )\d+\.\d+' | head -1 | cut -d. -f1)
if [ "$TEMP" -gt 70 ]; then
MESSAGE="$SERVER_NAME 磁盘温度异常: ${TEMP}°C"
curl -s "$WEBHOOK_URL" -H 'Content-Type: application/json' -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$MESSAGE\"}}"
fi
# 检查根目录空间
ROOT_USAGE=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$ROOT_USAGE" -gt 80 ]; then
MESSAGE="$SERVER_NAME 根分区使用率: ${ROOT_USAGE}%"
curl -s "$WEBHOOK_URL" -H 'Content-Type: application/json' -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$MESSAGE\"}}"
fi
# 检查数据盘空间
DATA_USAGE=$(df /mnt/data | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$DATA_USAGE" -gt 85 ]; then
MESSAGE="$SERVER_NAME 数据盘使用率: ${DATA_USAGE}%"
curl -s "$WEBHOOK_URL" -H 'Content-Type: application/json' -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$MESSAGE\"}}"
fi
# 检查Docker容器状态
# 统计非Up状态的容器
DOWN_COUNT=$(docker ps --filter "status=exited" | wc -l)
if [ "$DOWN_COUNT" -gt 1 ]; then
MESSAGE="$SERVER_NAME 有 $DOWN_COUNT 个Docker容器挂了"
curl -s "$WEBHOOK_URL" -H 'Content-Type: application/json' -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$MESSAGE\"}}"
fi
# 检查Samba服务是否正常
if ! pgrep -x "smbd" > /dev/null; then
MESSAGE="$SERVER_NAME Samba服务异常"
curl -s "$WEBHOOK_URL" -H 'Content-Type: application/json' -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$MESSAGE\"}}"
fi
echo "Health check completed at $(date)"
# 同样加入crontab
*/10 * * * * /usr/local/bin/health-check.sh
效果数据:换方案前后对比
以下数据全部来自我的实际运行环境,测试时间2024年8月-11月。
| 指标 | USB机械盘(方案A) | NVMe SSD(方案B) | 提升幅度 |
|---|---|---|---|
| 掉盘次数(3个月) | 7次 | 0次 | 无法计算 |
| 4K随机读延迟 | 25.8ms | 1.2ms | 21.5倍 |
| 4K随机读IOPS | 38 | 820 | 21.5倍 |
| 持续写入速度 | 112MB/s | 780MB/s | 6.96倍 |
| 系统启动时间 | 42秒 | 38秒 | 基本无差 |
| Jellyfin转码启动时间 | 8.3秒 | 2.1秒 | 3.9倍 |
| Transmission添加种子响应 | 1.8秒 | 0.4秒 | 4.5倍 |
| 待机功耗 | 6.2W | 5.8W | 略降 |
| 峰值功耗(4K转码) | 12.8W | 12.3W | 略降 |
| 30天累计数据损坏 | 200GB+ | 0 | - |
快照恢复测试:删除一个12GB的目录,用快照恢复,耗时46秒。btrfs的reflink复制是O(1)操作,恢复速度取决于校验和验证的时间,而不是数据量。
备份方案:快照脚本每天凌晨2点运行。一个月后快照占用空间12.4GB(数据总量约800GB,包含大量压缩过的视频文件)。btrfs filesystem du显示实际磁盘占用:数据盘804GB,快照占用12.4GB,占总容量1.5%。用compsize检查压缩率:媒体文件压缩率约1.03(视频已经是压缩格式,压不动),配置文件压缩率2.8倍,日志文件压缩率4.2倍。
原理:为什么btrfs快照能秒级完成
快照不复制数据。btrfs维护一个扩展区(extent)引用计数。创建快照时,只需要创建一个新的子卷,把当前子卷的根节点引用计数+1。数据块本身不复制。
写入新数据时,CoW机制保证原始块不被修改,新数据写入新块,更新快照的引用计数。这正是快照占空间极小、创建速度极快的原因。
但有一个坑:compress=zstd的压缩块在快照恢复时可能需要更多CPU重新压缩。实测恢复一个12GB目录时,CPU占用从3%跳到28%,持续约7秒。
避坑指南(我实际踩过的坑)
以下每一个坑都是我用数据换来的,不是从网上抄的。
坑1:USB硬盘盒+机械硬盘是家庭服务器的死路
USB桥接芯片无法正确透传ATA命令,导致smartctl不可用、UAS协议间歇性报错。你无法监控硬盘健康状态,掉盘时也没有提前告警。数据丢失只是时间问题。别用USB硬盘盒做任何7x24小时服务,这是底线。
坑2:树莓派5的USB-C供电口不能供电给硬盘
树莓派5支持USB-C PD供电,但官方文档明确说USB端口的总输出电流限制为1.2A(5V)。带不动机械硬盘。如果你的硬盘盒自带供电,确保电源适配器质量够好。我用的航嘉12V/2A独立电源给硬盘盒供电,也掉过盘。USB协议层面的兼容性问题,供电只是其中一个因素。
坑3:btrfs的device add和raid1模式不是保险箱
家庭服务器不要轻易上btrfs RAID。如果一块盘损坏,另一块盘在重建时可能因为新写入的数据块和旧块不匹配,导致恢复失败。单盘btrfs + 定期快照 + 冷备,比两盘btrfs raid1更可靠。我在一块128GB的SSD上测试过btrfs raid1,13天后出现csum校验错误,虽然没有丢数据,但重建过程极其漫长。
坑4:compress=zstd压缩等级不要超过3
zstd等级越高压缩率越好,但树莓派5的CPU在zstd:5压缩一个2GB的数据库备份时,CPU占用100%持续4分多钟。zstd:1的CPU占用只有不到15%,压缩率只差8%左右。媒体文件本来就是压缩过的,zstd收益几乎为0。用默认等级1就行。
坑5:Docker数据目录迁移后,容器重启会失败
如果你直接复制/var/lib/docker到新目录,然后改daemon.json,大概率会遇到容器重启失败的问题。原因是Docker的volume在容器内引用的是绝对路径,迁移后路径变了需要重新create。正确做法是用docker compose down停掉所有容器,再迁移目录,然后docker compose up -d重新创建。别问我怎么知道的。
另一个坑:docker-compose.yml里用了volumes:绑定挂载的话,路径如果变了,容器内数据会丢失。我迁移后Transmission的种子列表丢了,因为/downloads目录映射到了新路径,旧的种子文件还在,但配置里没关联。
坑6:/boot/firmware/cmdline.txt 修改后无法启动
usbcore.autosuspend=-1这个参数在Bookworm的内核里是生效的,但如果你同时加了dtoverlay=disable-wifi,某些树莓派5的WiFi模块EEPROM会在启动时报错导致卡在引导阶段。我在这上面折腾了两天。解法是把dtoverlay=disable-wifi改成dtoverlay=disable-wifi,但前提是先注释掉dtoverlay=disable-bt单测。实际上,dtoverlay=disable-bt和dtoverlay=disable-wifi同时加上就是有问题。后来我改成只用rfkill block wifi和rfkill block bluetooth,稳定。
坑7:TF卡是消耗品
树莓派5的TF卡读取速度快了,但写入寿命没变。Docker日志、容器写数据、系统日志,都在消耗TF卡的写入寿命。把Docker数据目录和日志迁到SSD后,TF卡的写入量从每天的1.2GB降到了不到200MB。我用的闪迪Extreme Pro 64GB,迁移后用了4个月,smartctl检查健康状态良好。iostat确认TF卡的平均写IOPS从340降到了45。
坑8:不要把btrfs快照放在同一个磁盘上
快照和原数据在同一个物理磁盘上,如果整块盘损坏,快照也跟着完蛋。快照只是防误删,不是备份。我另外做了一块4TB移动硬盘每周冷备,用rsync同步关键数据目录。冷备盘不常挂载,减少写入损耗。
坑9:Jellyfin硬解码遇到奇怪的音画不同步
树莓派5的VideoCore VII支持H.264和H.265硬解,但Jellyfin容器默认不启用。需要在/boot/firmware/config.txt里加一行:
dtoverlay=vc4-kms-v3d,cma-512
然后Jellyfin的启动参数加--device=/dev/dri。在10.9.11版本里,如果不加cma-512,硬解高码率4K视频时会出现帧率不稳。这是官方文档没写的坑。
结语
树莓派5当家庭服务器,性能完全够用。真正的瓶颈是存储方案。别再买USB硬盘盒+机械盘了,直接上NVMe SSD + RTL9210B芯片组的硬盘盒,文件系统选btrfs,配合快照和健康检查脚本。这套方案跑了4个月,0掉盘,0数据损坏,Jellyfin串流4K电影不卡顿,Transmission挂3000+种子响应正常。
数据是拿来用的,不是拿来修的。