上周帮同事迁移一台 Windows 开发机,他的日常环境是 WSL2 里跑 Ubuntu,Redis 装在 Docker 容器里,VSCode 用 Remote-WSL 连进去写代码。平时这套组合确实舒服,直到要换机器才发现问题:数据到底存在哪?怎么带出去?新机器怎么接住?折腾了一个下午,中间还踩了 docker commit 的坑——镜像打出来了,里面 Redis 却是空的。回来之后我把验证过的方案完整梳理了一遍,这篇内容就是针对“WSL + Docker + Redis”这个场景,讲清楚备份怎么选、文件从哪取、往新机器迁移怎么做、以及最后怎么验证备份确实可用。适合那些在 Windows 上用 WSL 做开发、用 Docker 起中间件,但还没真正演练过备份和恢复的朋友。
1. WSL2、Docker Desktop 与 Redis 容器:数据到底藏在哪
1.1 三层“看不见的墙”导致备份无从下手
很多人在 Windows 上装完 Docker Desktop,又开了 WSL 集成,就以为“Docker 跑在 WSL 里”,Redis 数据也顺理成章在 Ubuntu 里。这个认知在备份场景下会害死人。
从文件系统视角看,这套环境其实是三层结构:
第一层,WSL2 的每个发行版都是一个 ext4.vhdx 虚拟磁盘文件,比如 Ubuntu 对应 C:\Users\<用户名>\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu...\LocalState\ext4.vhdx。你在 Windows 资源管理器里看不到这个磁盘内部的文件。
第二层,Docker Desktop 自己也有一个名为 docker-desktop 的 WSL 发行版。你用 docker 命令创建的所有镜像、容器、数据卷,实际上都落在 docker-desktop 发行版的虚拟磁盘里,而不是你日常使用的 Ubuntu 发行版里。这也是很多人打开 Ubuntu 的目录结构,怎么都找不到 Redis 数据文件的原因。
第三层,Redis 官方镜像把容器内的 /data 目录声明为数据卷。不指定挂载时,Docker 会自动创建一个匿名卷来承接数据写入。
所以你在 Windows 上看到的 docker-desktop-data 或者 docker-desktop 对应的 ext4.vhdx,才是 Docker 数据的物理承载方。只复制 Ubuntu 的文件,根本拿不到容器数据。
1.2 两种常见部署形态,备份策略完全不同
我建议在一开始就分清你到底属于哪种情况:
| 部署形态 | 说明 | Docker 数据实际位置 | 推荐的迁移方式 |
|---|---|---|---|
| 形态 A:Docker Desktop + WSL 集成 | Windows 上装 Docker Desktop,Ubuntu 里不装 docker 命令,只是通过集成调用 | docker-desktop 发行版的 ext4.vhdx 内 |
用 docker save + 数据卷备份,目标机器重装 Docker Desktop |
| 形态 B:在 Ubuntu 里直接安装 docker-ce | WSL 里跑的是原生 Linux Docker 引擎 | Ubuntu 发行版的 ext4.vhdx,实际路径在 /var/lib/docker |
可以直接考虑 wsl --export 整体搬迁 |
如果连自己的环境属于哪种形态都不清楚,先敲一句:
bash复制docker context ls
如果显示的是 desktop-linux,那就是形态 A。如果显示的是 default,而且 docker ps 能在 WSL 里直接执行,多半是形态 B。这个判断决定了后续迁移走哪条路。
1.3 先确认 Redis 容器的数据卷挂载方式
无论哪种形态,备份 Redis 的第一步都是确认容器启动时是怎么挂载数据的。用这条命令看:
bash复制docker inspect redis --format '{{json .Mounts}}'
输出里能看到两类信息:
Type: volume,说明用的是命名卷,比如redis-data:/data。Type: bind,说明用的是宿主机目录直接绑定,比如/home/user/redis-data:/data。
如果什么都没挂,那就是匿名卷。匿名卷在 docker rm 的时候不一定立刻删,但容器重建后很难再关联回去,所以越是匿名卷越要尽快备份。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 备份方案选型:为什么 docker commit 不靠谱,推荐 BGSAVE + 数据卷备份
2.1 RDB 和 AOF:Redis 自己给的两种保底
备份 Redis 之前,先理解 Redis 自己提供的两种持久化机制,因为你最后拿到的备份本质上就是这两个文件之一。
RDB 是内存数据的二进制快照,默认开启,策略是 save 3600 1 300 100 60 10000,满足条件时自动生成 dump.rdb。RDB 的优点是一个文件就是全量快照,迁移时干净利落;缺点是两次快照之间的数据可能丢。
AOF 是追加写日志,默认关闭。开启后每条写命令都追加到 appendonly.aof,恢复时重放日志。AOF 数据更完整,但文件大,恢复慢。Redis 7.x 之后 AOF 变成了 multi-part 结构,除了 appendonly.aof 还会有 base 文件和 incr 文件,备份时不能只拷其中一个。
很多人纠结“备份到底选 RDB 还是 AOF”。我的建议是:日常全量备份以 RDB 为主,理由很简单——迁移时只需要一个文件,而且 dump.rdb 可以跨小版本恢复。如果业务对数据丢失极其敏感,那就提前开启 AOF,但备份时仍然先做一次 RDB 快照,而不是直接去拷贝 AOF 文件。AOF 文件在 Redis 运行时处于持续写入状态,直接拷贝可能拿到不一致的内容。
2.2 docker commit 备份 Redis:这个坑我替你们踩过了
docker commit 可以把容器打包成新镜像,看起来很适合做“整机备份”。但 Redis 官方镜像在 Dockerfile 里声明了 VOLUME /data,而 docker commit 有一个硬性规则:不会包含挂载卷里的数据。
同事当时就是这么干的:
bash复制docker commit redis redis-backup-image
docker run -d --name redis-new redis-backup-image
redis-cli KEYS '*'
结果 (empty array)。他第一反应是“数据丢了”,其实数据还在原来的匿名卷里,只是新镜像里根本没有卷的内容。这个坑很隐蔽,因为命令执行不报任何错误,镜像也打出来了,但数据就是没进去。
所以在 Redis 容器这种“数据放在卷里”的场景下,docker commit 只能用于备份镜像本身和自定义配置,不能用来备份数据。
2.3 我推荐的全量备份组合
经过多次验证,我固定使用的备份方案是:手动触发 BGSAVE 生成 RDB 快照,再用 docker cp 或卷打包把文件取出来。
具体选 A 还是选 B,看你的容器挂载方式:
- 如果 Redis 数据在命名卷或匿名卷里,用
docker cp取/data/dump.rdb最简单。 - 如果 Redis 数据是 bind mount 挂载的,直接到宿主机挂载目录拷文件就行。
- 如果想把数据和目录结构整体带走,用
--volumes-from打一个 tar 包最完整。
下面一章就是实际操作。
3. 全量备份实操:把 Redis 数据从 WSL 的“黑盒”里接出来
3.1 动手前先确认四件事
备份前我会在 WSL 终端里依次确认:
bash复制docker ps
docker inspect redis --format '{{.Name}} {{.HostConfig.Binds}} {{.Mounts}}'
docker exec redis redis-cli INFO persistence
INFO persistence 的输出里重点看两行:
code复制rdb_bgsave_in_progress:0
rdb_last_bgsave_status:ok
rdb_bgsave_in_progress 为 0 说明当前没有正在进行的后台保存,可以安全触发下一次;rdb_last_bgsave_status 为 ok 说明上一次快照成功。这两项确认后再动手,避免在 Redis 还在写盘的时候重复触发。
3.2 触发 BGSAVE,而不是 SAVE
执行:
bash复制docker exec redis redis-cli BGSAVE
输出 Background saving started 就对了。这里为什么要用 BGSAVE 而不是 SAVE?BGSAVE 是 fork 子进程在后台执行快照,主进程继续服务请求;SAVE 是主进程直接阻塞写盘,数据量大的时候会出现明显卡顿。生产环境别用 SAVE。
BGSAVE 是异步的,命令返回不代表写完。等它完成的稳妥方式是对比触发前后的 LASTSAVE 时间戳:
bash复制BEFORE=$(docker exec redis redis-cli LASTSAVE)
docker exec redis redis-cli BGSAVE > /dev/null
for i in $(seq 1 30); do
AFTER=$(docker exec redis redis-cli LASTSAVE)
if [ "$AFTER" -gt "$BEFORE" ]; then
echo "RDB 快照完成"
break
fi
sleep 1
done
LASTSAVE 返回的是 Unix 时间戳,只要它变了,说明新的 dump.rdb 已经落盘。
3.3 把 dump.rdb 取出来
构建一个带日期的备份目录:
bash复制mkdir -p ~/redis-backups
docker cp redis:/data/dump.rdb ~/redis-backups/redis-dump-$(date +%Y%m%d-%H%M%S).rdb
docker cp 的路径要和容器内 Redis 的数据目录一致。官方镜像默认是 /data,如果你在启动时指定过 --dir 参数,要按实际路径来。
如果你更想直接打包整个数据目录,用这个命令:
bash复制BACKUP_DIR=~/redis-backups
mkdir -p "$BACKUP_DIR"
docker run --rm --volumes-from redis \
-v "$BACKUP_DIR":/backup \
alpine tar czf /backup/redis-data-$(date +%Y%m%d-%H%M%S).tar.gz /data
这里用 --volumes-from redis 把 Redis 容器的数据卷挂载到临时 alpine 容器,再用 tar 打包到宿主机备份目录。好处是不需要关心卷是命名卷还是匿名卷,整体结构都带走了;坏处是 tar 包里文件的所有者是 Redis 容器里的 uid(通常是 999),恢复时要注意权限。
3.4 备份文件放 WSL 里还是 Windows 盘
我见过有人直接把备份输出到 /mnt/c,然后整个操作卡死。WSL2 的跨文件系统 I/O 性能是出了名的差,尤其当 tar 包里有大量小文件时,逐文件写入 NTFS 会非常慢。
正确的做法是:先在 WSL 内部的 ext4 文件系统上把备份准备好,得到一个 tar 或 rdb 单文件,再通过资源管理器地址栏输入 \\wsl.localhost\Ubuntu\home\<用户名>\redis-backups,把这个单文件拖到 Windows 桌面或者外接盘。
备份文件的存放位置同样有讲究:不要只存在 WSL 发行版内部。虚拟磁盘一旦损坏或误删,备份也跟着没了。可靠的备份至少有一份落在 Windows 磁盘或外接存储上。
4. 迁移实战:从旧机器到新机器的四条路径
4.1 路径一:wsl --export / --import 整体搬迁
如果你的环境是形态 B(Ubuntu 里直接装了 docker-ce),而且希望把整套开发环境原封不动搬过去,这是最省事的方式。
在 PowerShell 里执行:
powershell复制wsl --shutdown
wsl --export Ubuntu D:\wsl-backup\ubuntu-full.tar
注意 wsl --shutdown 一定要做。它会把 WSL2 虚拟磁盘里的缓存落盘,导出的 tar 才是一致的。导出时间取决于虚拟磁盘实际使用量,十几 GB 的发行版可能要跑十几分钟。
目标机器上执行:
powershell复制wsl --import Ubuntu D:\wsl\Ubuntu D:\wsl-backup\ubuntu-full.tar
导入后有个坑:默认用户变成 root。需要在 WSL 里修改 /etc/wsl.conf:
ini复制[user]
default=你的用户名
然后 wsl --shutdown 再重进。如果原来的发行版里装了 Docker 引擎,还需要确认 Docker 服务能正常启动。WSL 环境没有 systemd 时,可能需要手动 sudo service docker start 或配置好自启。
4.2 路径二:docker save + 数据卷备份,单独迁 Redis
如果只是迁 Redis,没必要把整个 WSL 发行版搬走。用 docker save 带走镜像,用备份的 rdb 或 tar 带走数据。
旧机器上:
bash复制docker save -o ~/redis-backups/redis-image.tar redis:7
新机器上加载:
bash复制docker load -i redis-image.tar
然后把旧机器备份出来的 dump.rdb 放到新机器的数据目录,再启动容器:
bash复制docker run -d \
--name redis \
-p 6379:6379 \
-v /home/user/redis-data:/data \
redis:7 \
redis-server --appendonly no
这里要注意:目标机器的 /home/user/redis-data 目录权限要改成容器内 Redis 用户可写。官方镜像的 Redis 用户 uid 是 999,所以:
bash复制sudo chown -R 999:999 /home/user/redis-data
不处理权限的话,容器启动后可能报类似 Can't open the log file: Permission denied 或无法写 dump.rdb 的错误。
4.3 路径三:redis-cli --rdb 在线拉取
如果 Redis 跑在远程服务器上,不方便直接进宿主机操作,可以用 redis-cli --rdb 直接拉一份 RDB 快照到本地:
bash复制redis-cli -h <目标IP> -p 6379 -a <密码> --rdb ./dump.rdb
这个命令的原理是向服务器发送 SYNC/PSYNC 请求,服务器会 fork 一个子进程生成 RDB 快照,然后通过连接传给你。命令自身不阻塞主进程,但 fork 瞬间会有内存开销,数据量大的实例要留意内存水位。执行完还会输出一句类似 SYNC sent to master,看到它基本就成功了。
不过既然 Redis 是用 Docker 部署的,我通常还是优先用 docker exec 进容器执行 BGSAVE,因为 redis-cli --rdb 本质上是走主从同步协议的远程导出,对单机小实例来说有点多余。
4.4 迁移后最常见的四个故障
我把实际迁移中遇到过的状况整理成一张表,排查时对着看:
| 现象 | 原因 | 解决办法 |
|---|---|---|
容器启动后 Could not connect to Redis |
端口被占,或新容器没起来 | docker ps -a 看容器状态,lsof -i :6379 查端口占用 |
Can't open the log file: Permission denied |
挂载目录所有权不是 uid 999 | chown -R 999:999 /path/to/redis-data |
Redis 启动成功但 KEYS 为空 |
加载的是空目录,或 AOF/RDB 路径不对 | 确认 /data 下确实有 dump.rdb,检查启动参数 --dir |
| 老版本 Redis 读不了迁移后的 RDB | RDB 文件由更高版本 Redis 生成 | 升级目标环境的 Redis 版本,RDB 向后兼容,但旧版本读不了新格式 |
4.5 迁移前给 WSL 瘦身
如果你的 WSL 发行版的 ext4.vhdx 已经膨胀到几十 GB,迁移前最好先清理。先看 Docker 的空间占用:
bash复制docker system df
如果发现一堆 <none> 镜像和停止的容器,执行:
bash复制docker system prune
注意:不要加 --volumes 参数。docker system prune -a --volumes 会把所有未被容器使用的数据卷一起删掉,Redis 的数据卷如果当前没被引用,直接就没了。这个参数必须在确认备份落盘之后才能用,否则就是自毁行为。
清理之后如果虚拟磁盘文件还是很大,可以压缩:
powershell复制wsl --shutdown
diskpart
select vdisk file="C:\Users\<用户名>\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu...\LocalState\ext4.vhdx"
attach vdisk readonly
compact vdisk
detach vdisk
exit
compact vdisk 会把已删除数据占用的空间释放出来。这一步不是必须的,但如果你发现迁移文件异常大,值得做一次。
5. 验证与自动化:让定期全量备份真正可靠
5.1 用 redis-check-rdb 和临时容器验证备份
备份文件拿到手,第一件事是验证它能不能用。redis-check-rdb 是 Redis 自带的 RDB 文件检查工具:
bash复制docker run --rm -v ~/redis-backups:/backup redis:7 redis-check-rdb /backup/redis-dump-20250701-120000.rdb
看到输出末尾有类似 [offset] checksum OK 和总键数信息,说明文件没有损坏。这还不够,我强烈建议再起一个临时容器,用备份文件做一次真正的恢复测试:
bash复制docker run -d --name redis-verify \
-v ~/redis-backups/redis-dump-20250701-120000.rdb:/data/dump.rdb \
-p 6380:6379 \
redis:7 redis-server --appendonly no
然后在新端口上验证:
bash复制redis-cli -p 6380 DBSIZE
redis-cli -p 6380 KEYS '*'
DBSIZE 和源实例对得上,说明备份可用。验证完删除临时容器:
bash复制docker rm -f redis-verify
这一步的关键点在于:永远不要在没有验证的情况下,就把旧环境销毁。没验证过的备份,等同于没有备份。
5.2 把备份做成脚本和 cron 任务
手动备份只能应急,日常还是得靠脚本。我现在的备份脚本长这样:
bash复制#!/bin/bash
set -euo pipefail
CONTAINER=redis
BACKUP_DIR=/home/user/redis-backups
KEEP_DAYS=7
mkdir -p "$BACKUP_DIR"
BEFORE=$(docker exec "$CONTAINER" redis-cli LASTSAVE)
docker exec "$CONTAINER" redis-cli BGSAVE > /dev/null
for i in $(seq 1 30); do
AFTER=$(docker exec "$CONTAINER" redis-cli LASTSAVE)
if [ "$AFTER" -gt "$BEFORE" ]; then
break
fi
sleep 1
done
docker cp "$CONTAINER":/data/dump.rdb "$BACKUP_DIR/redis-dump-$(date +%Y%m%d-%H%M%S).rdb"
find "$BACKUP_DIR" -name "redis-dump-*.rdb" -mtime "+${KEEP_DAYS}" -delete
然后在 WSL Ubuntu 里配置定时任务:
bash复制crontab -e
加一行:
cron复制0 */3 * * * /home/user/scripts/backup-redis.sh >> /tmp/backup-redis.log 2>&1
每三小时做一次全量备份,保留 7 天。这个频率对大多数开发环境已经够用。日志写到 /tmp 下面,方便日后排查脚本是否正常执行。
5.3 恢复演练:备份链路的最后一块拼图
脚本跑起来之后,我还会每季度做一次完整恢复演练:把备份文件放到一台临时环境,执行 docker load、挂载数据卷、验证 DBSIZE,确认整条链路是通的。这个习惯帮我在一次真实事故里保住了数据——当时目标机器的 Redis 版本比源环境低,如果没演练过,恢复的时候根本不会想到去检查 RDB 版本兼容性。
对于 WSL 里用 Docker 跑 Redis 的场景,主从复制可以作为高可用层面的补充(直接拉一个 Redis 从节点起来,主节点挂了自动顶上),但主从复制解决不了误删数据的问题。全量备份备份的是一份独立于运行环境的数据快照,这才是任何时候都能兜底的方案。
那台开发机迁移完到现在跑了两个多月,redis-check-rdb 每次通过,临时容器的验证也都能对上键值。备份这件事,说到底就是一条规则:先确认数据能从备份里恢复出来,再让脚本定期替你重复这件事。命令不复杂,复杂的是在数据真的丢了之前,把这条链路完整演练一遍。
