VPS 上的 Redis 不能只做到返回 PONG。应用账号能不能清空数据库、内存满了会不会丢掉队列、重启后能不能读回数据,这些都要在接入业务前确认。
这篇用 Docker Compose 部署一个 Redis 8 单实例,提供 ACL 账号、SSH 隧道、AOF/RDB 持久化和独立恢复步骤。适合小网站、API 和内部工具;不是 Sentinel、Cluster 或零数据丢失方案。配置依据官方文档整理,资源数字是建议起点,不是并发实测。
| 用途 | 内存满时的思路 | 部署建议 |
|---|---|---|
| 可从数据库重建的页面缓存 | 可考虑 allkeys-lru 或 allkeys-lfu | 独立实例,应用处理缓存未命中 |
| 任务队列、重要会话 | 通常选 noeviction,让写入显式失败 | 配合持久化、容量告警和重试机制 |
| 缓存与队列混用 | 淘汰策略很难同时满足两者 | 拆成不同实例,分别限制内存 |
本例使用 noeviction。它避免因内存淘汰主动移除 key,但不能防止 TTL 到期、误删或故障丢失。业务必须处理写入错误;切换成缓存策略之前,要确认所有 key 都允许丢弃。具体行为见 Redis 内存淘汰文档。
学习或低频小服务可以从 2 vCPU、2GB RAM、30GB SSD 评估;应用与 Redis 同机时,更建议 4GB RAM 起。示例只给 Redis 设置 256MB maxmemory 和 1GB 容器上限,剩余空间用于运行开销、后台保存及宿主机。
maxmemory 不是进程内存上限,RDB 保存和 AOF 重写期间还可能出现写时复制开销。买 VPS 时看可用内存、磁盘延迟、备份容量和应用到 Redis 的网络距离,不要只看 CPU 核数。写入密集场景要测量保存时的内存峰值,参考 Redis 运维建议。
下面按 Ubuntu 24.04、root shell、常规 rootful Docker 编写,使用已安装的 Docker Engine 和 Compose 插件。尚未安装可参考 VPS Docker 部署教程。
本文固定 redis:8.6.6,这个标签在编写时可从 官方镜像列表 核对。它不是永久推荐版本;部署时仍需检查补丁公告与应用兼容性,并记录镜像 digest。不要把旧 Redis 数据目录直接交给新镜像。
在 VPS 执行:
docker version
docker compose version
ss -lntp 'sport = :6379'
install -d -m 700 /opt/redis8
cd /opt/redis8
install -d -m 700 secrets backups
install -d -m 755 config
install -d -m 750 data
docker pull redis:8.6.6
redis_uid=$(docker run --rm --entrypoint id redis:8.6.6 -u redis)
redis_gid=$(docker run --rm --entrypoint id redis:8.6.6 -g redis)
chown "$redis_uid:$redis_gid" data
docker image inspect redis:8.6.6 --format '{{json .RepoDigests}}'
确认 6379 未被已有实例占用。最后一条输出的 digest 可用来进一步固定镜像;记录 UID/GID,恢复时还会用到。不要用 chmod 777 解决目录权限。
账号分为 admin、app、health。管理员只用于维护;应用只访问 app: 前缀的部分命令;健康检查只能 PING。以下初始化代码只在全新目录运行一次:
cd /opt/redis8
umask 077
for account in admin app health; do
openssl rand -hex 32 > "secrets/${account}_password"
done
admin_hash=$(printf '%s' "$(cat secrets/admin_password)" | sha256sum | cut -d ' ' -f 1)
app_hash=$(printf '%s' "$(cat secrets/app_password)" | sha256sum | cut -d ' ' -f 1)
health_hash=$(printf '%s' "$(cat secrets/health_password)" | sha256sum | cut -d ' ' -f 1)
{
printf 'user default off\n'
printf 'user admin on #%s ~* &* +@all\n' "$admin_hash"
printf 'user app on #%s ~app:* -@all +ping +hello +select +get +set +del +unlink +exists +expire +pexpire +ttl +pttl +client|setname +client|setinfo\n' "$app_hash"
printf 'user health on #%s -@all +ping\n' "$health_hash"
} > config/users.acl
chmod 644 config/users.acl
chmod 600 secrets/admin_password secrets/app_password
chmod 444 secrets/health_password
unset admin_hash app_hash health_hash
ACL 文件保存的是高强度随机密码的 SHA-256,不是明文;明文文件保留在权限为 700 的 secrets 目录。只有低权限健康检查密码会挂入服务容器。ACL 语法和 key 范围规则见 官方 ACL 文档。
这里的应用权限只适用于示例字符串操作。BullMQ、Celery、WordPress 插件可能需要 Lua、列表、哈希、发布订阅或其他命令;按应用文档逐项放行,不要直接改成管理员权限。数据库编号也不是租户安全边界。
保存下面内容为 /opt/redis8/config/redis.conf:
bind 0.0.0.0
port 6379
protected-mode yes
daemonize no
logfile ""
aclfile /usr/local/etc/redis/users.acl
dir /data
dbfilename dump.rdb
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
appendonly yes
appenddirname appendonlydir
appendfsync everysec
maxmemory 256mb
maxmemory-policy noeviction
bind 0.0.0.0 是容器内监听地址,外部范围由 Docker 端口映射决定。本例使用 ACL、默认账号禁用和本机端口绑定,不能单独依赖 protected-mode。
保存下面内容为 /opt/redis8/compose.yaml:
name: redis8
services:
redis:
image: redis:8.6.6
restart: unless-stopped
stop_grace_period: 1m
environment:
SKIP_FIX_PERMS: "1"
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
ports:
- "127.0.0.1:6379:6379"
volumes:
- ./config:/usr/local/etc/redis:ro
- ./data:/data
secrets:
- redis_health_password
mem_limit: 1g
healthcheck:
test:
- CMD-SHELL
- 'REDISCLI_AUTH=$$(cat /run/secrets/redis_health_password) redis-cli --user health -e PING | grep -qx PONG'
interval: 15s
timeout: 5s
retries: 5
start_period: 30s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
secrets:
redis_health_password:
file: ./secrets/health_password
SKIP_FIX_PERMS 配合前面手动设置的数据目录权限,避免入口脚本修改只读配置挂载。这里仍使用镜像默认的降权流程,没有开启 SKIP_DROP_PRIVS,可核对 8.6.6 镜像入口脚本。宿主机上的配置文件要可被容器中的 Redis 用户读取:
cd /opt/redis8
chmod 644 config/redis.conf
docker compose config --quiet
docker compose up -d
docker compose ps
docker compose logs --tail=100 redis
云安全组不要开放 6379。使用受支持且不低于 28.0.0 的 Docker Engine;较早版本存在同一二层网络可访问 localhost 发布端口的例外,见 Docker 端口发布说明。同一 Docker 网络中的容器仍可访问 Redis,只加入可信应用。
先执行未认证测试:
docker compose exec -T redis redis-cli PING
预期得到 NOAUTH,而不是 PONG。接着以应用身份测试,并写入一个不设置 TTL 的恢复标记:
export REDISCLI_AUTH="$(cat secrets/app_password)"
docker compose exec -T -e REDISCLI_AUTH redis \
redis-cli --user app -e SET app:restore-probe redis8-backup-check
docker compose exec -T -e REDISCLI_AUTH redis \
redis-cli --user app -e GET app:restore-probe
unset REDISCLI_AUTH
验证越权时不用真的运行危险命令。使用管理员执行 ACL DRYRUN,它只判断权限,不执行被检查的命令:
export REDISCLI_AUTH="$(cat secrets/admin_password)"
docker compose exec -T -e REDISCLI_AUTH redis \
redis-cli --user admin ACL DRYRUN app SET app:test value
docker compose exec -T -e REDISCLI_AUTH redis \
redis-cli --user admin ACL DRYRUN app SET other:test value
docker compose exec -T -e REDISCLI_AUTH redis \
redis-cli --user admin ACL DRYRUN app FLUSHALL
unset REDISCLI_AUTH
三条应分别显示允许、key 范围不允许、命令不允许。同一个 Compose 项目里的应用连接 redis:6379,用户名填 app;密码通过应用支持的 secret 方式传入。容器里的 127.0.0.1 指向容器自身。
从电脑使用 Redis Insight 或命令行管理时,在电脑建立 SSH 隧道:
ssh -N -T -o ExitOnForwardFailure=yes \
-o ServerAliveInterval=30 -o ServerAliveCountMax=3 \
-L 127.0.0.1:16379:127.0.0.1:6379 deploy@VPS_IP
核对主机指纹后,客户端连接电脑上的 127.0.0.1:16379。这里 Redis 本身没有启用 TLS,跨公网这一段由 SSH 加密;GUI 也不要勾选 Redis TLS 后强行连接明文端口。跨主机长期连接应配置受控网络和 Redis TLS,参考 Redis TLS 文档。
GUI 通常需要更多查询权限,示例 app 账号不保证兼容所有管理界面。管理员凭据仅供本人维护,不能放入前端代码或分享给应用用户。出现 NOAUTH、WRONGPASS、连接拒绝时可接着看 Redis 连接失败排查。
RDB 是某个时点的快照;AOF 用于重放写入。appendfsync everysec 在常见故障下仍可能丢失约一秒的写入,不是零丢失保证。Redis 7 以后 AOF 可能由多个文件和 manifest 组成,不能只复制一个旧教程中的 appendonly.aof。
本例保留 /data 下的 RDB 与整个 appendonlydir。两种持久化同时启用时,重启会优先使用 AOF,因此“替换 dump.rdb 后重启”不等于成功恢复。细节见 Redis 持久化说明。
日常异地备份可以导出独立 RDB。下面脚本通过 redis-cli --rdb 获取快照,检查文件后才将 .partial 改为正式名称;这是同步复制协议导出,可能触发后台保存,建议放在低峰期。
保存为 /opt/redis8/backup.sh:
#!/usr/bin/env bash
set -euo pipefail
umask 077
cd /opt/redis8
backup_name="redis-$(date -u +%Y%m%dT%H%M%SZ).rdb"
test ! -e "backups/$backup_name"
test ! -e "backups/${backup_name}.partial"
redis_image=$(docker compose images -q redis)
test -n "$redis_image"
export REDISCLI_AUTH="$(cat secrets/admin_password)"
trap 'unset REDISCLI_AUTH' EXIT
docker compose exec -T -e REDISCLI_AUTH redis \
redis-cli --user admin --rdb "/tmp/${backup_name}.partial"
docker compose cp "redis:/tmp/${backup_name}.partial" \
"backups/${backup_name}.partial"
chmod 600 "backups/${backup_name}.partial"
docker run --rm --network none \
-v "$PWD/backups:/backup:ro" --entrypoint redis-check-rdb \
"$redis_image" "/backup/${backup_name}.partial"
mv "backups/${backup_name}.partial" "backups/$backup_name"
sha256sum "backups/$backup_name" > "backups/${backup_name}.sha256"
docker compose exec -T redis rm "/tmp/${backup_name}.partial"
printf 'Backup ready: %s\n' "backups/$backup_name"
--rdb 的行为与失败退出码见 Redis CLI 文档。它导出整个实例的数据,不受某个业务库编号限制;管理员凭据与导出文件都要保护。失败的 .partial 文件需要检查后清理,不能当作完成的备份上传。
手动执行成功后,再安排 root 定时任务,例如每天服务器时间 03:30:
chmod 700 /opt/redis8/backup.sh
/opt/redis8/backup.sh
30 3 * * * /bin/bash /opt/redis8/backup.sh >> /opt/redis8/backups/backup.log 2>&1
监控最后一次成功备份时间并轮转日志。把完成的 RDB、校验文件、配置、ACL 和镜像 digest 复制到异地加密存储;明文密码单独保管。RDB 不包含你的外部配置和 ACL 文件,VPS 本地副本也无法应对整机丢失。每天备份意味着整机丢失时可能损失接近一天的数据。
在 /opt/redis8 下另开一个 Bash 会话,替换真实备份文件名。下面不覆盖生产数据目录,也不发布任何端口:
cd /opt/redis8
backup_file='backups/redis-YYYYMMDDTHHMMSSZ.rdb'
sha256sum -c "${backup_file}.sha256"
redis_image=$(docker compose images -q redis)
test -n "$redis_image"
redis_uid=$(docker run --rm --entrypoint id "$redis_image" -u redis)
redis_gid=$(docker run --rm --entrypoint id "$redis_image" -g redis)
restore_dir=$(mktemp -d /opt/redis8/restore.XXXXXX)
install -m 640 "$backup_file" "$restore_dir/dump.rdb"
chown "$redis_uid:$redis_gid" "$restore_dir" "$restore_dir/dump.rdb"
chmod 750 "$restore_dir"
docker run -d --name redis8-restore --network none --memory 1g \
-e SKIP_FIX_PERMS=1 -v "$restore_dir:/data" \
"$redis_image" redis-server --bind 127.0.0.1 \
--dir /data --dbfilename dump.rdb --appendonly no --save ""
docker logs --tail=50 redis8-restore
redis8-restore 名称必须未被占用;已有演练容器时先换一个名字。等日志确认加载完成,再检查:
docker exec redis8-restore redis-cli -e PING
docker exec redis8-restore redis-cli -e DBSIZE
docker exec redis8-restore redis-cli -e GET app:restore-probe
docker exec redis8-restore redis-cli INFO keyspace
标记值应为 redis8-backup-check。继续核对业务 key、数据类型和关键值;不同逻辑库分别检查,过期 key 可能因恢复时间较晚而减少。队列恢复还涉及重复消费和幂等,不能只看 key 数量。
恢复实例未配置认证,仅用于 --network none 下的本地检查,不能把这组参数直接用于公网服务。演练结束可执行 docker stop redis8-restore 释放内存,目录保留供检查。
如果要用恢复后的 RDB 接管业务,先恢复 ACL 与网络配置。在没有业务写入的窗口,以关闭 AOF 的方式加载 RDB,确认数据后通过 CONFIG SET appendonly yes 生成 AOF,等待重写完成且状态正常,再把持久化配置保存为 appendonly yes。直接拿空 AOF 目录启动,可能得到与预期不同的数据。切换前建议按 VPS 备份恢复演练清单 完成应用验证。
cd /opt/redis8
export REDISCLI_AUTH="$(cat secrets/admin_password)"
docker compose exec -T -e REDISCLI_AUTH redis redis-cli --user admin INFO memory
docker compose exec -T -e REDISCLI_AUTH redis redis-cli --user admin INFO persistence
docker compose exec -T -e REDISCLI_AUTH redis redis-cli --user admin INFO stats
docker compose exec -T -e REDISCLI_AUTH redis redis-cli --user admin ACL LOG 10
unset REDISCLI_AUTH
docker stats --no-stream
df -h
重点关注 used_memory_rss、evicted_keys、rdb_last_bgsave_status、aof_last_write_status 和 aof_last_bgrewrite_status,字段含义见 INFO 文档。容器显示 healthy 只说明认证和 PING 正常,不保证持久化或业务写入正常。
看到 MISCONF 应检查磁盘、权限和后台保存失败原因;不要直接关闭 stop-writes-on-bgsave-error 掩盖故障。出现 fork 失败时,核对宿主机 vm.overcommit_memory 和可用内存;内核参数是主机级设置,应评估同机服务后按运维文档调整。
更新前保留旧镜像 digest,完成备份和恢复验证,再测试新版本。回退使用旧镜像及升级前备份重建独立实例,不假设旧程序一定能读取新版本生成的 RDB/AOF。更多容器上线检查可参考 Docker Compose 生产配置清单。
不能把其他镜像的用法直接套到官方 redis 镜像。本文通过 aclfile 配置认证,并用未认证 PING 验证;仅设置一个环境变量不足以证明密码已生效。
认证依据是 ACL 文件里的哈希。轮换时先在 ACL 中加入新密码哈希,执行 ACL LOAD 并验证新连接,再切换客户端、移除旧哈希并再次加载。修改前保留管理会话与文件备份;文件挂载只读,本例不使用 ACL SAVE 回写。行为见 ACL LOAD 文档。
它不等于 RSS 或容器上限,客户端缓冲、分配器、持久化和后台操作都有开销。先测峰值并减少大 key、检查连接,再调容量。
需要。误删可以立即进入 AOF,整机丢失也会带走本地文件。历史快照、异地副本与恢复演练各有作用。
不能仅凭开启 AOF 就这么判断。能否作为主要数据存储取决于数据模型、容错与恢复目标;本例适用于单机辅助服务,不承诺高可用或零数据丢失。
先跑通认证、权限边界和一次独立恢复,再接入正式应用。随后用实际工作负载确定 maxmemory、备份频率和是否需要拆分实例。
