Redis 容器化与持久化安全:内存、RDB/AOF、ACL与高可用边界
Redis 容器很容易启动,但“进程能PING”不等于可以安全上线。生产必须理解:官方镜像最终如何执行 redis-server,配置文件与命令行谁覆盖谁,/data 中RDB和AOF如何恢复,maxmemory 为什么不能等于容器内存,BGSAVE/AOF rewrite的fork为什么引发Copy-on-Write峰值,ACL和protected mode保护什么,以及在同一宿主机启动三个Redis为什么不等于哨兵高可用。
本页聚焦Redis如何安全运行在Docker中。数据结构、缓存穿透/击穿/雪崩、大Key、热Key、淘汰、主从、哨兵、Cluster和分布式锁的完整原理继续学习 Redis从零到生产级掌握。容器化不会自动解决缓存一致性和高可用。
学习目标
学完后,应能:
- 解释镜像 Entrypoint、
redis-server、配置文件、命令参数和容器PID 1关系。 - 使用只读redis.conf、ACL Secret、Named Volume和最小网络暴露运行Redis,不关闭保护模式、不使用privileged。
- 解释RDB、AOF、混合持久化的文件、刷盘、fork、rewrite和数据丢失窗口。
- 区分Redis
maxmemory、used_memory、RSS、内存碎片、复制/客户端Buffer和fork COW峰值。 - 为容器cgroup限制、maxmemory和宿主机overcommit/THP设计可验证预算。
- 完成RDB/AOF备份、隔离恢复、校验和RPO/RTO说明。
- 解释单容器、主从、哨兵、Cluster在Docker网络和故障域中的边界。
- 定位NOAUTH、连接失败、OOMKilled、AOF错误、持久化抖动、大Key、热Key和数据重启丢失。
- 制定Redis升级、模块兼容、持久化格式验证和回退方案。
一、Redis进入容器后的完整链路
flowchart TD
A["应用Redis客户端"] --> B["Docker网络与容器6379"]
B --> C["redis-server网络事件循环"]
C --> D["ACL认证与命令权限检查"]
D --> E["内存数据结构执行命令"]
E --> F["RDB快照或AOF追加"]
F --> G["/data中的持久化文件"]
G --> H["Named Volume或受控存储"]Docker负责:
- 镜像、进程、网络、Volume、Secret。
- CPU、内存、PID和文件描述符限制。
- 日志和容器生命周期。
Redis负责:
- 数据结构与命令执行。
- TTL、过期删除、内存淘汰。
- RDB、AOF、复制。
- ACL、慢日志、延迟和统计。
- Sentinel/Cluster协议。
二、镜像版本、Tag和模块兼容
学习可以使用:
docker pull redis:7.27.2仍是可变Tag,生产应固定已验证补丁Tag或Digest:
docker image inspect redis:7.2
docker image ls --digests redis升级时考虑:
- RDB/AOF格式兼容。
- ACL和配置项变化。
- 默认行为和弃用项。
- Redis Module ABI/版本,例如RedisJSON、RediSearch。
- 客户端协议RESP2/RESP3兼容。
- CPU架构。
不要在生产直接使用 latest 并依赖自动拉取。必须记录当前运行容器的 Image ID 和 RepoDigest。
三、官方镜像如何启动Redis
常见镜像入口会处理传入命令,最终执行类似:
redis-server或:
redis-server /usr/local/etc/redis/redis.conf容器启动过程:
flowchart TD
A["docker run或Compose创建容器"] --> B["准备网络、cgroup和/data挂载"]
B --> C["执行镜像Entrypoint"]
C --> D["合并redis-server命令和参数"]
D --> E["读取redis.conf和ACL文件"]
E --> F["加载RDB或AOF"]
F --> G["监听端口并接收命令"]检查最终命令:
docker image inspect \
--format 'entry={{json .Config.Entrypoint}} cmd={{json .Config.Cmd}}' \
redis:7.2
docker inspect \
--format 'path={{json .Path}} args={{json .Args}}' \
order-redis3.1 为什么daemonize no
容器生命周期由PID 1决定。Redis若自行转为后台守护进程,前台主进程退出后容器可能被认为结束。容器中应让 redis-server 前台运行,日志进入stdout/stderr。
3.2 配置文件与命令参数
配置文件:
redis-server /usr/local/etc/redis/redis.conf命令参数:
redis-server \
--appendonly yes \
--maxmemory 512mb \
--maxmemory-policy allkeys-lfu命令行通常可以覆盖同名配置。排查必须看最终容器Args,并通过 CONFIG GET 或 INFO 验证实际值。
四、配置文件路径和只读挂载
官方镜像不保证自动读取你宿主机任意 redis.conf。必须在启动命令明确指定:
command:
- redis-server
- /usr/local/etc/redis/redis.conf只读挂载:
volumes:
- type: bind
source: ./redis.conf
target: /usr/local/etc/redis/redis.conf
read_only: true检查:
docker inspect --format '{{json .Mounts}}' order-redis
docker inspect --format 'path={{json .Path}} args={{json .Args}}' order-redis挂载成功但启动命令仍是裸 redis-server 时,Redis可能继续使用默认配置。
五、安全网络:不要关闭protected mode后暴露公网
旧教程常见组合:
protected-mode no
bind 0.0.0.0
-p 6379:6379
无ACL或弱密码这可能让Redis暴露给局域网或公网扫描。正确原则:
- 只让应用和Redis加入同一个受控Docker网络。
- 不需要宿主机访问时不配置
ports。 - 本地调试只绑定
127.0.0.1:16379:6379。 - 保持 protected mode,并配置ACL。
- 宿主机防火墙和云安全组继续限制来源。
- 敏感网络使用TLS或受控网络隧道。
Compose内部访问:
redis:6379不是:
localhost:6379因为应用容器中的localhost只表示应用容器自己。
六、Redis 6+ ACL与Secret
传统 requirepass 只有单一密码思路,Redis 6+ ACL可以定义用户、Key Pattern和命令权限。
users.acl示例:
user default off
user order_app on >替换为受控应用密码 ~order:* +@read +@write +ping -@dangerous
user health on >替换为独立健康密码 ~* +ping含义:
- 关闭default用户。
order_app只访问order:*Key。- 允许读写命令和PING。
- 显式排除危险命令族。
- health用户只能PING。
实际命令分类和业务需要应通过 ACL CAT、ACL DRYRUN 等按版本验证,不能只凭示例假定所有命令都覆盖。
Redis配置:
aclfile /run/secrets/users.acl6.1 ACL文件作为只读Secret的边界
只读挂载适合由部署平台管理ACL。容器内执行 ACL SETUSER 后若要 ACL SAVE,只读Secret通常无法写回。生产应选择:
- 由配置/Secret系统生成ACL文件并滚动重启。
- 使用受控可写ACL文件并严格权限。
- 企业Secret集成。
不要在命令行直接使用:
redis-cli -a 明文密码密码可能出现在Shell历史、进程参数和日志。可使用 REDISCLI_AUTH 的短生命周期环境变量、受控配置文件或Secret注入,仍要保护进程环境和诊断信息。
七、为什么不使用--privileged
Redis写 /data Permission denied 通常是Volume UID/GID、SELinux或只读挂载问题,与特权容器无关。
检查:
docker image inspect --format 'user={{.Config.User}}' redis:7.2
docker inspect --format '{{json .Mounts}}' order-redis
docker logs --tail 200 order-redis修复固定UID/GID、宿主机目录所有权或SELinux标签,不使用 --privileged,不使用 chmod 777。
八、/data与持久化文件
官方镜像常把工作目录/持久化目录设为:
/data使用命名卷:
volumes:
- redis-data:/data目录中可能有:
dump.rdb。- AOF文件或Redis 7多部件AOF目录与manifest。
- 临时重写文件。
文件名和布局受Redis版本与配置影响。升级、备份和恢复必须按目标版本验证,不能只找一个旧教程中的 appendonly.aof。
九、RDB原理与容器内存峰值
RDB生成某个时间点的内存快照。
flowchart TD
A["触发BGSAVE"] --> B["Redis主进程fork子进程"]
B --> C["子进程遍历快照视图"]
C --> D["写临时RDB文件"]
D --> E["完成后原子替换dump.rdb"]
B --> F["主进程继续处理写命令"]
F --> G["被修改内存页触发Copy-on-Write"]9.1 fork不等于立刻复制全部内存
父子进程初始共享物理页。持久化期间父进程修改某页,内核为保持子进程快照视图而复制该页,这就是Copy-on-Write。
写流量越高、快照越久,COW额外内存可能越大。容器限制过紧时可能在BGSAVE期间OOMKilled。
9.2 RDB丢失窗口
若每5分钟快照一次,宕机可能丢失最近一次快照之后的数据。缓存可从数据库重建时可能可接受;核心事实数据不能只依赖RDB。
十、AOF、fsync与rewrite
AOF记录写命令,重启时重放恢复。
常见刷盘策略:
| 策略 | 性能 | 典型丢失窗口 |
|---|---|---|
always | 每次写都fsync,最慢 | 更小,但仍需理解OS/磁盘故障边界 |
everysec | 常用折中 | 通常可能丢最近约1秒数据 |
no | 由OS决定刷盘 | 窗口更不可控 |
10.1 AOF为什么要rewrite
对同一个Key反复修改会留下很多历史命令。Rewrite根据当前数据状态生成更紧凑的新AOF。
flowchart TD
A["AOF文件持续增长"] --> B["触发BGREWRITEAOF"]
B --> C["fork重写子进程"]
C --> D["生成新的基础AOF内容"]
B --> E["主进程继续接收写入"]
E --> F["记录重写期间增量"]
D --> G["合并并切换新AOF"]
F --> GRewrite也有fork、COW、CPU和磁盘IO成本。大Key会让AOF、rewrite和恢复更慢。
10.2 Redis 7多部件AOF
现代Redis可能使用base、incremental文件和manifest组织AOF,不再只是单个 appendonly.aof。备份时必须保存一组一致文件和manifest,不能在切换过程中只复制某一个文件。
十一、混合持久化怎么理解
混合持久化常用RDB格式作为AOF基础,加后续增量命令,兼顾恢复速度和数据丢失窗口。它仍然不是强一致账本,也不能消除:
- fsync窗口。
- 主从异步复制丢失。
- 文件损坏。
- 宿主机和Volume故障。
- 误删Key。
核心订单、余额和库存事实仍应以数据库或一致性系统为准,Redis作为缓存或可接受其一致性边界的数据存储。
十二、maxmemory不等于容器RSS上限
Redis内存视图:
Redis进程RSS
├── 数据集和对象
├── allocator碎片
├── 客户端输入输出Buffer
├── 复制backlog和副本Buffer
├── AOF Buffer
├── Lua/Function/Module内存
├── 线程栈与代码
├── 内核页计量
└── fork期间Copy-on-Write页maxmemory主要约束Redis可淘汰/管理的数据内存范围,不保证整个进程RSS一定低于该值。部分客户端、复制和持久化内存可能不按直觉计入淘汰边界。
12.1 错误配置
容器memory=1GiB
maxmemory=1GiB没有为碎片、客户端Buffer、AOF和fork COW留空间,极易OOMKilled。
12.2 学习预算示例
1GiB容器、缓存场景可从:
maxmemory 512mb开始压测,把剩余空间用于非数据集内存和fork峰值。实际比例取决于:
- 写入率。
- 数据量和对象编码。
- RDB/AOF频率。
- 客户端连接和Buffer。
- 主从复制。
- 内存碎片。
- Module。
不能把50%机械应用到所有服务。
十三、淘汰策略必须按业务选择
常见:
| 策略 | 行为 | 场景 |
|---|---|---|
noeviction | 内存满后写命令报错 | 不能自动丢Key的状态数据,但业务必须处理写失败 |
allkeys-lru | 全部Key近似LRU淘汰 | 通用缓存 |
allkeys-lfu | 全部Key近似LFU淘汰 | 访问热点稳定缓存 |
volatile-* | 只在设置TTL的Key中淘汰 | 必须保证所有可淘汰Key都有TTL |
过期删除与淘汰不是一回事。详细原理见 过期删除与内存淘汰。
13.1 为什么缓存适合allkeys而状态数据不一定
缓存Key被淘汰后可回源数据库;队列、分布式锁或唯一状态被淘汰可能破坏业务语义。一个Redis实例混放不同数据类型会让淘汰和权限难治理,商业系统应按用途隔离实例或集群。
十四、宿主机overcommit、THP和文件描述符
Redis常见宿主机警告:
vm.overcommit_memory。- Transparent Huge Pages。
somaxconn。- 文件描述符限制。
14.1 为什么是宿主机问题
容器共享宿主机内核。Redis fork、内存页和网络队列由宿主机内核管理。不能通过给容器 --privileged 随意修改全局sysctl,这会影响同主机其他工作负载。
由运维在宿主机基线中评估:
sysctl vm.overcommit_memory
cat /sys/kernel/mm/transparent_hugepage/enabled
sysctl net.core.somaxconn14.2 文件描述符与maxclients
Redis最大客户端数还受容器/宿主机 nofile 限制。设置很大的 maxclients 但没有足够FD,会连接失败;连接数过大还会增加客户端Buffer内存。
检查:
docker inspect --format '{{json .HostConfig.Ulimits}}' order-redis
docker exec order-redis sh -c 'ulimit -n'极简镜像无Shell时从宿主机进程limits或受控诊断环境检查。
十五、健康检查和认证
ACL启用后,健康检查也需要最小权限用户。示意:
healthcheck:
test:
- CMD-SHELL
- >-
REDISCLI_AUTH="$$(cat /run/secrets/redis-health-password)"
redis-cli --user health ping | grep -q PONG
interval: 10s
timeout: 3s
start_period: 20s
retries: 5这里用 $$ 防止Compose提前插值,等容器内Shell读取Secret。Secret仍可能短暂进入健康检查进程环境,应限制宿主机和Docker API权限。
健康PING只能证明:
- TCP可连接。
- ACL用户可认证。
- Redis事件循环能处理PING。
不能证明:
- 内存未接近上限。
- AOF/RDB正常。
- 主从无延迟。
- Cluster槽完整。
- 业务Key可访问。
十六、停止、SIGTERM与数据落盘
docker stop --time 60 order-redisRedis作为PID 1收到SIGTERM后进入关闭流程,具体是否保存RDB、等待AOF和关闭行为受版本与配置影响。必须在目标版本实验观察日志和持久化文件。
宽限期不足被SIGKILL时:
- AOF可能依靠已刷盘部分恢复。
- RDB只恢复到上次快照。
- everysec可能丢最近窗口。
- 未完成rewrite的临时文件需由启动恢复逻辑处理。
不要用 docker kill 作为日常停止,也不要看到Redis关闭几秒就强杀。
十七、日志和运行状态
第一轮:
docker logs --timestamps --tail 300 order-redis
docker inspect --format '{{json .State}}' order-redis
docker inspect --format '{{json .HostConfig.LogConfig}}' order-redis
docker stats --no-stream order-redisRedis日志应输出stdout/stderr并由Docker日志驱动轮转、集中采集。不要把日志与AOF混淆:
- 应用日志用于诊断。
- AOF用于数据恢复。
- AOF不是日志平台文件,不能随意截断和轮转。
十八、生产观测命令
使用受控ACL用户执行:
redis-cli --user monitor INFO server
redis-cli --user monitor INFO memory
redis-cli --user monitor INFO persistence
redis-cli --user monitor INFO stats
redis-cli --user monitor INFO clients
redis-cli --user monitor INFO replication密码通过 REDISCLI_AUTH 或安全客户端配置提供,不写命令参数。
重点:
| 指标 | 含义 |
|---|---|
used_memory | Redis分配器统计的内存 |
used_memory_rss | OS看到的进程常驻内存 |
maxmemory | 配置的数据内存上限 |
mem_fragmentation_ratio | RSS与Redis内存关系的一个线索,不能单值下结论 |
evicted_keys | 淘汰累计数 |
expired_keys | 过期累计数 |
connected_clients | 客户端连接数 |
blocked_clients | 阻塞客户端 |
rejected_connections | 因限制拒绝连接 |
rdb_bgsave_in_progress | RDB后台保存 |
aof_rewrite_in_progress | AOF重写 |
master_repl_offset | 主复制偏移线索 |
十九、慢命令、大Key和热Key
19.1 SLOWLOG
redis-cli --user monitor SLOWLOG LEN
redis-cli --user monitor SLOWLOG GET 20Slowlog记录命令执行阶段耗时,不一定包含网络排队和客户端等待全耗时。需要结合应用P99、网络、连接池和CPU。
19.2 commandstats与latency
redis-cli --user monitor INFO commandstats
redis-cli --user monitor LATENCY DOCTOR19.3 bigkeys和扫描风险
redis-cli --user monitor --bigkeys会扫描Key空间并产生负载,生产应在低峰、限速、只读副本或采样方案执行。不能使用 KEYS * 扫描生产全库。
大Key会放大:
- 命令阻塞。
- 网络传输。
- fork COW。
- AOF/RDB。
- 主从复制。
- Cluster迁移。
热Key即使在Cluster也只落一个slot和一个主节点,不能自动被所有节点分担。详细见 大Key与热Key治理。
二十、RDB备份与恢复
20.1 生成新RDB
在业务允许和资源足够时:
redis-cli --user admin BGSAVE监控:
redis-cli --user monitor INFO persistence等待:
rdb_bgsave_in_progress:0。rdb_last_bgsave_status:ok。rdb_last_save_time更新。
账号权限应单独设计;普通应用账号不应有BGSAVE管理权限。
20.2 复制完整RDB
RDB完成后文件通过原子替换形成完整快照,可以从受控Volume备份或使用 redis-cli --rdb 等目标版本支持方式。备份流程还需:
- 校验文件。
- 记录Redis版本。
- 加密。
- 独立存储。
- 保留策略。
- 恢复演练。
20.3 隔离恢复
- 创建全新空Volume。
- 停止目标测试Redis。
- 将RDB放入目标配置的dir/dbfilename。
- 修正数字UID/GID和权限。
- 使用兼容Redis版本启动。
- 查看加载日志。
- 验证DBSIZE、抽样Key、TTL、类型和业务查询。
不要直接覆盖运行中生产实例的 /data。
二十一、AOF备份和修复边界
AOF可能是单文件或多部件目录。备份时必须保证 manifest 与base/incremental文件一致。可选择:
- 停止写入并正常关闭后复制。
- 在副本节点协调备份。
- 使用文件系统快照并与Redis持久化状态协调。
- 采用平台提供的受支持备份方式。
Redis提供AOF检查/修复工具,但修复可能截断损坏尾部并丢失命令。必须先复制原文件、在隔离环境评估和验证,不能直接对唯一生产副本执行破坏性修复。
二十二、RPO、RTO与缓存重建
| 模式 | RPO思路 | RTO风险 |
|---|---|---|
| 无持久化纯缓存 | 数据可全部丢,由DB重建 | 回源洪峰和预热时间 |
| RDB | 丢失上次快照后数据 | 文件加载较快,快照可能大 |
| AOF everysec | 通常约秒级窗口 | 重放时间和rewrite布局 |
| 混合持久化 | 快照+增量 | 复杂度与格式兼容 |
| 主从/哨兵 | 减少单节点故障时间 | 异步复制仍可能丢数据 |
| Cluster | 分片扩容与分片HA | 多节点、slot和恢复复杂度 |
纯缓存即使允许全部丢失,也要防止所有实例同时空缓存导致数据库雪崩。需要限流、分批预热、随机TTL和回源保护。
二十三、单容器、主从、哨兵与Cluster边界
23.1 单容器+restart
解决进程退出后的自动重启,不解决:
- 宿主机宕机。
- Volume损坏。
- Redis进程因同一错误反复退出。
- 数据副本。
- 自动故障转移。
23.2 主从
提供副本和读扩展,但复制通常异步,Master故障时未复制命令可能丢失。主从本身不自动完成选主和客户端切换。
23.3 哨兵
监控Master并自动故障转移,但不分片,单Master容量和写入上限仍存在。
在同一Docker宿主机启动一主两从三哨兵,只能做流程实验:宿主机一断全部消失,不是跨故障域高可用。
23.4 Cluster
通过16384个slot分片到多个Master,并为每个Master配置Replica。Docker部署需特别处理:
- 节点向客户端宣告的IP/端口。
- Redis服务端口和Cluster bus端口。
- NAT和端口映射。
- 跨主机网络。
- slot迁移。
- MOVED/ASK。
- 客户端Cluster支持。
不能在随机bridge IP后只发布6379就假定外部客户端能正确访问所有Cluster节点。
完整原理见 Redis主从哨兵Cluster全过程。
二十四、可运行单节点Compose Demo
目录:
redis-stack/
├── compose.yaml
├── redis.conf
└── secrets/
├── users.acl
└── redis-health-password.txtSecret文件只在本地学习时准备并加入 .gitignore,生产由Secret平台生成。users.acl中的health密码必须与 redis-health-password.txt 一致。
24.1 redis.conf
bind 0.0.0.0
protected-mode yes
port 6379
daemonize no
dir /data
dbfilename dump.rdb
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
maxmemory 512mb
maxmemory-policy allkeys-lfu
aclfile /run/secrets/users.acl
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes该配置用于1GiB容器的学习起点。若实例保存不能淘汰的状态,allkeys-lfu不合适,应选noeviction并让业务处理写失败。实际生产必须压测fork内存和持久化IO。
24.2 users.acl
user default off
user order_app on >替换为高强度应用密码 ~order:* +@read +@write +ping -@dangerous
user health on >替换为高强度健康密码 ~* +ping24.3 Compose
name: order-redis-lab
services:
redis:
image: redis:7.2
command:
- redis-server
- /usr/local/etc/redis/redis.conf
volumes:
- redis-data:/data
- type: bind
source: ./redis.conf
target: /usr/local/etc/redis/redis.conf
read_only: true
secrets:
# redis.conf读取/run/secrets/users.acl,因此显式指定带点的目标文件名。
# 如果只写“- users-acl”,Compose默认创建的是
# /run/secrets/users-acl,与aclfile配置不一致,Redis会启动失败。
- source: users-acl
target: users.acl
- source: redis-health-password
target: redis-health-password
ports:
- "127.0.0.1:16379:6379"
mem_limit: 1g
cpus: 1.5
pids_limit: 200
stop_grace_period: 60s
restart: unless-stopped
healthcheck:
test:
- CMD-SHELL
- >-
REDISCLI_AUTH="$$(cat /run/secrets/redis-health-password)"
redis-cli --user health ping | grep -q PONG
interval: 10s
timeout: 3s
start_period: 20s
retries: 5
logging:
driver: json-file
options:
max-size: "20m"
max-file: "5"
secrets:
users-acl:
file: ./secrets/users.acl
redis-health-password:
file: ./secrets/redis-health-password.txt
volumes:
redis-data:这里有两个容易导致“配置看起来正确、容器却一直不健康”的文件对应关系:
| 配置或命令 | 容器内实际文件 | 内容必须满足什么 |
|---|---|---|
aclfile /run/secrets/users.acl | /run/secrets/users.acl | 包含 default、order_app、health 三个用户定义 |
Healthcheck中的 cat | /run/secrets/redis-health-password | 只保存health用户的原始密码,不包含引号、用户名和命令 |
users.acl 中的:
user health on >health-password-123 ~* +ping要求 redis-health-password.txt 的内容恰好是:
health-password-123如果两边不一致,Redis本身可能已经正常运行,但健康检查会得到 WRONGPASS,容器状态变成 unhealthy。如果Secret目标文件名与 aclfile 不一致,Redis则会在启动阶段读取ACL文件失败。排查时要同时检查最终Compose模型、容器Mounts和日志,不能只看源文件:
docker compose config
docker inspect --format '{{json .Mounts}}' order-redis
docker compose logs --timestamps --tail 200 redis24.4 启动前
docker compose config
docker compose pull
docker compose up -d --wait24.5 验证
在当前Shell临时设置密码,避免放在CLI参数:
export REDISCLI_AUTH='替换为本地实验应用密码'
redis-cli -h 127.0.0.1 -p 16379 --user order_app PING
redis-cli -h 127.0.0.1 -p 16379 --user order_app SET order:1001 paid
redis-cli -h 127.0.0.1 -p 16379 --user order_app GET order:1001
unset REDISCLI_AUTH终端环境、Shell历史和进程调试仍是安全边界,生产使用Secret SDK或受控客户端配置。
容器验证:
docker compose ps -a
docker compose logs --timestamps --tail 300 redis
docker compose top redis
docker inspect <redis容器>
docker stats --no-stream <redis容器>24.6 验证持久化
docker compose down
docker volume ls --filter name=order-redis-lab
docker compose up -d --wait再次GET应读到Key,前提是AOF/RDB已按配置落盘并正常加载。不要执行 down -v,它会删除实验卷。
二十五、升级Redis镜像
25.1 升级前
docker exec order-redis redis-server --version
docker inspect --format 'image={{.Image}} mounts={{json .Mounts}}' order-redis还要保存:
INFO server/memory/persistence/replication。CONFIG GET关键项。- ACL配置。
- RDB/AOF一致备份。
- Module版本。
- 客户端兼容性。
- 当前镜像Digest。
25.2 升级流程
flowchart TD
A["阅读Release Notes和格式兼容"] --> B["备份RDB/AOF并隔离恢复"]
B --> C["使用数据副本测试新镜像"]
C --> D["验证ACL、配置、Module和客户端"]
D --> E["从副本或金丝雀节点升级"]
E --> F{"指标和业务是否正常"}
F -- "否" --> G["停止扩散并切回未升级副本/恢复备份"]
F -- "是" --> H["分批完成并观察持久化"]单节点直接原地升级风险高。高可用架构可以先升级Replica并验证,再按官方兼容策略切换。不能只把Tag改回旧版而不确认持久化格式和Module兼容。
二十六、常见故障排查
26.1 NOAUTH或NOPERM
- NOAUTH:未认证或认证失败。
- NOPERM:已认证用户无该命令或Key权限。
检查ACL用户、Key Pattern、命令分类和客户端用户名。不要为了通过而启用default全权限用户。
26.2 连接拒绝或超时
docker compose ps
docker logs --tail 200 order-redis
docker network inspect <网络名>
docker port order-redis检查服务名、共同网络、bind、protected mode、宿主机端口、防火墙和客户端超时。应用容器不使用localhost访问Redis服务。
26.3 OOMKilled
docker inspect --format 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}} memory={{.HostConfig.Memory}}' order-redis
docker stats --no-stream order-redis结合 INFO memory、fork时刻、RDB/AOF状态、客户端Buffer、大Key和内存碎片。不能只降低maxmemory后不查业务写入与持久化峰值。
26.4 内存满但没有淘汰
检查:
maxmemory
maxmemory-policy
evicted_keys
Key是否有TTL
写命令返回OOM错误noeviction会让写失败;volatile-*在没有TTL Key时也可能无法淘汰预期数据。
26.5 重启后数据丢失
检查:
docker inspect --format '{{json .Mounts}}' order-redis
docker logs --timestamps --tail 300 order-redis方向:
- 未开启RDB/AOF。
/data未挂载。- 项目名变化挂新Volume。
down -v删除卷。- 持久化失败。
- Redis从另一个dir/dbfilename加载。
26.6 AOF或RDB失败
查看 INFO persistence 和日志。检查磁盘满、只读、权限、fork失败、overcommit和文件损坏。不要直接删除唯一AOF/RDB让服务“先起来”,应复制现场并在隔离环境修复/恢复。
26.7 延迟突然升高
对齐时间线:
- BGSAVE/BGREWRITEAOF。
- fork耗时。
- 磁盘IO。
- 大Key命令。
- 慢Lua/Function。
- 内存淘汰。
- 主从全量同步。
- CPU throttling。
- 客户端连接池。
使用SLOWLOG、LATENCY、INFO commandstats和容器/宿主机指标,不要直接重启清空证据。
二十七、商业场景一:maxmemory等于容器限制
现象:Redis平时内存稳定,AOF rewrite时容器Exit 137。
配置:
容器memory=2GiB
maxmemory=2GiB根因:没有为allocator碎片、客户端/复制Buffer、AOF和fork COW留空间。Rewrite期间写流量复制大量内存页,RSS超过cgroup限制。
修复:降低maxmemory、控制大Key和写峰值、监控fork/COW、增加合理容器余量并压测。只关闭AOF可能改变RPO,不能作为无评估修复。
二十八、商业场景二:Redis暴露公网被扫描
现象:CPU和连接数异常,日志出现未知来源命令。
发现:
protected-mode no。- 发布
0.0.0.0:6379。 - default用户无密码或弱密码。
- 云安全组全开放。
处理:立即隔离网络、保留审计、轮换凭据、检查数据和宿主机影响,重建受信环境。长期不发布数据库端口、启用ACL/TLS、最小权限和网络白名单。
二十九、商业场景三:三个容器仍然同时不可用
架构:一主两从、三个Sentinel全部部署在同一Docker宿主机。
宿主机磁盘故障后所有容器一起消失,Sentinel无法选主。
根因:进程数量增加了,但故障域没有隔离。高可用需要跨主机/可用区、独立存储和网络,并验证客户端发现与切换。
三十、商业场景四:缓存清空后数据库被打挂
Redis数据被删除后应用全部回源MySQL,连接池和数据库CPU瞬间打满。
即使Redis只是可丢缓存,也需要:
- 分批预热。
- 回源限流。
- 本地缓存。
- 熔断降级。
- 随机TTL。
- 热点保护。
- 数据库容量预案。
“缓存允许丢”不等于“恢复没有业务风险”。
三十一、面试标准回答
Redis容器为什么maxmemory不能等于容器限制
maxmemory主要约束Redis数据内存和淘汰边界,进程RSS还包括allocator碎片、客户端与复制Buffer、AOF Buffer、线程栈、Module以及RDB/AOF rewrite fork期间的Copy-on-Write页。若maxmemory等于cgroup限制,后台持久化或写峰值很容易触发OOMKilled。应留足余量并按fork、RSS和业务压测调整。
Redis容器如何安全暴露
应优先只加入应用内部Docker网络,不发布宿主机6379;本地调试只绑定127.0.0.1。保持protected mode,Redis 6+使用ACL为应用、健康和运维定义不同用户、Key Pattern与命令权限,Secret不写镜像和命令行;外部链路再结合TLS、防火墙和安全组。
RDB/AOF在容器中为什么必须挂Volume
RDB和AOF通常写在
/data,容器可写层会随容器删除,挂Named Volume让持久化文件独立保留。但Volume不是备份,误删、宿主机损坏和down -v仍会丢数据;还需要独立副本、加密、保留、校验和隔离恢复演练。
为什么Redis后台持久化会造成延迟和内存峰值
BGSAVE和AOF rewrite通常fork子进程。fork本身要复制页表并可能产生停顿,父进程继续写时触发Copy-on-Write复制内存页,增加RSS;子进程还会消耗CPU和磁盘IO。数据越大、写入越高、磁盘越慢,影响越明显,需要监控fork耗时、COW、持久化状态和IO。
单容器restart为什么不是高可用
restart只能在同一Docker Engine上重启退出进程,宿主机、Volume或网络故障时无效。主从提供副本但不自动切换,哨兵负责单主故障转移但不分片,Cluster通过slot分片并提供分片级高可用。即使有多个容器,若都在同一宿主机也仍是同一故障域。
Redis容器重启后数据丢失怎么查
先inspect Mounts确认
/data实际挂载,再看项目名是否变化、是否执行down -v、RDB/AOF配置和dir/dbfilename、日志中的加载与持久化错误、Volume权限和磁盘。不能只看当前Compose文件,因为旧容器可能从未挂卷;也不能删除损坏文件直接启动,应先保留现场并隔离恢复。
三十二、学习验收
不看答案完成:
- 使用只读redis.conf、ACL Secret和Named Volume启动Redis,不关闭protected mode。
- 证明应用用户只能访问
order:*且不能执行危险命令。 - 比较RDB、AOF everysec和混合持久化的数据丢失窗口。
- 在1GiB容器中解释512MiB maxmemory之外的内存去向。
- 触发BGSAVE或AOF rewrite,观察fork、RSS、IO和延迟。
- 停止并重建容器,证明Volume中的数据恢复;再解释Volume为何不是备份。
- 在独立空Volume恢复RDB/AOF并验证Key、类型和TTL。
- 分别说明单容器、主从、哨兵和Cluster的故障域、容量和客户端要求。
- 使用INFO、SLOWLOG、LATENCY和commandstats定位一次延迟抖动。
- 写出Redis版本升级、Module兼容、金丝雀和失败回退方案。
