Skip to content

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从零到生产级掌握。容器化不会自动解决缓存一致性和高可用。

学习目标

学完后,应能:

  1. 解释镜像 Entrypoint、redis-server、配置文件、命令参数和容器PID 1关系。
  2. 使用只读redis.conf、ACL Secret、Named Volume和最小网络暴露运行Redis,不关闭保护模式、不使用privileged。
  3. 解释RDB、AOF、混合持久化的文件、刷盘、fork、rewrite和数据丢失窗口。
  4. 区分Redis maxmemory、used_memory、RSS、内存碎片、复制/客户端Buffer和fork COW峰值。
  5. 为容器cgroup限制、maxmemory和宿主机overcommit/THP设计可验证预算。
  6. 完成RDB/AOF备份、隔离恢复、校验和RPO/RTO说明。
  7. 解释单容器、主从、哨兵、Cluster在Docker网络和故障域中的边界。
  8. 定位NOAUTH、连接失败、OOMKilled、AOF错误、持久化抖动、大Key、热Key和数据重启丢失。
  9. 制定Redis升级、模块兼容、持久化格式验证和回退方案。

一、Redis进入容器后的完整链路

mermaid
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和模块兼容

学习可以使用:

bash
docker pull redis:7.2

7.2仍是可变Tag,生产应固定已验证补丁Tag或Digest:

bash
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

常见镜像入口会处理传入命令,最终执行类似:

text
redis-server

或:

text
redis-server /usr/local/etc/redis/redis.conf

容器启动过程:

mermaid
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["监听端口并接收命令"]

检查最终命令:

bash
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-redis

3.1 为什么daemonize no

容器生命周期由PID 1决定。Redis若自行转为后台守护进程,前台主进程退出后容器可能被认为结束。容器中应让 redis-server 前台运行,日志进入stdout/stderr。

3.2 配置文件与命令参数

配置文件:

bash
redis-server /usr/local/etc/redis/redis.conf

命令参数:

bash
redis-server \
  --appendonly yes \
  --maxmemory 512mb \
  --maxmemory-policy allkeys-lfu

命令行通常可以覆盖同名配置。排查必须看最终容器Args,并通过 CONFIG GETINFO 验证实际值。

四、配置文件路径和只读挂载

官方镜像不保证自动读取你宿主机任意 redis.conf。必须在启动命令明确指定:

yaml
command:
  - redis-server
  - /usr/local/etc/redis/redis.conf

只读挂载:

yaml
volumes:
  - type: bind
    source: ./redis.conf
    target: /usr/local/etc/redis/redis.conf
    read_only: true

检查:

bash
docker inspect --format '{{json .Mounts}}' order-redis
docker inspect --format 'path={{json .Path}} args={{json .Args}}' order-redis

挂载成功但启动命令仍是裸 redis-server 时,Redis可能继续使用默认配置。

五、安全网络:不要关闭protected mode后暴露公网

旧教程常见组合:

text
protected-mode no
bind 0.0.0.0
-p 6379:6379
无ACL或弱密码

这可能让Redis暴露给局域网或公网扫描。正确原则:

  1. 只让应用和Redis加入同一个受控Docker网络。
  2. 不需要宿主机访问时不配置 ports
  3. 本地调试只绑定 127.0.0.1:16379:6379
  4. 保持 protected mode,并配置ACL。
  5. 宿主机防火墙和云安全组继续限制来源。
  6. 敏感网络使用TLS或受控网络隧道。

Compose内部访问:

text
redis:6379

不是:

text
localhost:6379

因为应用容器中的localhost只表示应用容器自己。

六、Redis 6+ ACL与Secret

传统 requirepass 只有单一密码思路,Redis 6+ ACL可以定义用户、Key Pattern和命令权限。

users.acl示例:

text
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 CATACL DRYRUN 等按版本验证,不能只凭示例假定所有命令都覆盖。

Redis配置:

ini
aclfile /run/secrets/users.acl

6.1 ACL文件作为只读Secret的边界

只读挂载适合由部署平台管理ACL。容器内执行 ACL SETUSER 后若要 ACL SAVE,只读Secret通常无法写回。生产应选择:

  • 由配置/Secret系统生成ACL文件并滚动重启。
  • 使用受控可写ACL文件并严格权限。
  • 企业Secret集成。

不要在命令行直接使用:

text
redis-cli -a 明文密码

密码可能出现在Shell历史、进程参数和日志。可使用 REDISCLI_AUTH 的短生命周期环境变量、受控配置文件或Secret注入,仍要保护进程环境和诊断信息。

七、为什么不使用--privileged

Redis写 /data Permission denied 通常是Volume UID/GID、SELinux或只读挂载问题,与特权容器无关。

检查:

bash
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与持久化文件

官方镜像常把工作目录/持久化目录设为:

text
/data

使用命名卷:

yaml
volumes:
  - redis-data:/data

目录中可能有:

  • dump.rdb
  • AOF文件或Redis 7多部件AOF目录与manifest。
  • 临时重写文件。

文件名和布局受Redis版本与配置影响。升级、备份和恢复必须按目标版本验证,不能只找一个旧教程中的 appendonly.aof

九、RDB原理与容器内存峰值

RDB生成某个时间点的内存快照。

mermaid
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。

mermaid
flowchart TD
    A["AOF文件持续增长"] --> B["触发BGREWRITEAOF"]
    B --> C["fork重写子进程"]
    C --> D["生成新的基础AOF内容"]
    B --> E["主进程继续接收写入"]
    E --> F["记录重写期间增量"]
    D --> G["合并并切换新AOF"]
    F --> G

Rewrite也有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内存视图:

text
Redis进程RSS
├── 数据集和对象
├── allocator碎片
├── 客户端输入输出Buffer
├── 复制backlog和副本Buffer
├── AOF Buffer
├── Lua/Function/Module内存
├── 线程栈与代码
├── 内核页计量
└── fork期间Copy-on-Write页

maxmemory主要约束Redis可淘汰/管理的数据内存范围,不保证整个进程RSS一定低于该值。部分客户端、复制和持久化内存可能不按直觉计入淘汰边界。

12.1 错误配置

text
容器memory=1GiB
maxmemory=1GiB

没有为碎片、客户端Buffer、AOF和fork COW留空间,极易OOMKilled。

12.2 学习预算示例

1GiB容器、缓存场景可从:

text
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,这会影响同主机其他工作负载。

由运维在宿主机基线中评估:

bash
sysctl vm.overcommit_memory
cat /sys/kernel/mm/transparent_hugepage/enabled
sysctl net.core.somaxconn

14.2 文件描述符与maxclients

Redis最大客户端数还受容器/宿主机 nofile 限制。设置很大的 maxclients 但没有足够FD,会连接失败;连接数过大还会增加客户端Buffer内存。

检查:

bash
docker inspect --format '{{json .HostConfig.Ulimits}}' order-redis
docker exec order-redis sh -c 'ulimit -n'

极简镜像无Shell时从宿主机进程limits或受控诊断环境检查。

十五、健康检查和认证

ACL启用后,健康检查也需要最小权限用户。示意:

yaml
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与数据落盘

bash
docker stop --time 60 order-redis

Redis作为PID 1收到SIGTERM后进入关闭流程,具体是否保存RDB、等待AOF和关闭行为受版本与配置影响。必须在目标版本实验观察日志和持久化文件。

宽限期不足被SIGKILL时:

  • AOF可能依靠已刷盘部分恢复。
  • RDB只恢复到上次快照。
  • everysec可能丢最近窗口。
  • 未完成rewrite的临时文件需由启动恢复逻辑处理。

不要用 docker kill 作为日常停止,也不要看到Redis关闭几秒就强杀。

十七、日志和运行状态

第一轮:

bash
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-redis

Redis日志应输出stdout/stderr并由Docker日志驱动轮转、集中采集。不要把日志与AOF混淆:

  • 应用日志用于诊断。
  • AOF用于数据恢复。
  • AOF不是日志平台文件,不能随意截断和轮转。

十八、生产观测命令

使用受控ACL用户执行:

bash
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_memoryRedis分配器统计的内存
used_memory_rssOS看到的进程常驻内存
maxmemory配置的数据内存上限
mem_fragmentation_ratioRSS与Redis内存关系的一个线索,不能单值下结论
evicted_keys淘汰累计数
expired_keys过期累计数
connected_clients客户端连接数
blocked_clients阻塞客户端
rejected_connections因限制拒绝连接
rdb_bgsave_in_progressRDB后台保存
aof_rewrite_in_progressAOF重写
master_repl_offset主复制偏移线索

十九、慢命令、大Key和热Key

19.1 SLOWLOG

bash
redis-cli --user monitor SLOWLOG LEN
redis-cli --user monitor SLOWLOG GET 20

Slowlog记录命令执行阶段耗时,不一定包含网络排队和客户端等待全耗时。需要结合应用P99、网络、连接池和CPU。

19.2 commandstats与latency

bash
redis-cli --user monitor INFO commandstats
redis-cli --user monitor LATENCY DOCTOR

19.3 bigkeys和扫描风险

bash
redis-cli --user monitor --bigkeys

会扫描Key空间并产生负载,生产应在低峰、限速、只读副本或采样方案执行。不能使用 KEYS * 扫描生产全库。

大Key会放大:

  • 命令阻塞。
  • 网络传输。
  • fork COW。
  • AOF/RDB。
  • 主从复制。
  • Cluster迁移。

热Key即使在Cluster也只落一个slot和一个主节点,不能自动被所有节点分担。详细见 大Key与热Key治理

二十、RDB备份与恢复

20.1 生成新RDB

在业务允许和资源足够时:

bash
redis-cli --user admin BGSAVE

监控:

bash
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 隔离恢复

  1. 创建全新空Volume。
  2. 停止目标测试Redis。
  3. 将RDB放入目标配置的dir/dbfilename。
  4. 修正数字UID/GID和权限。
  5. 使用兼容Redis版本启动。
  6. 查看加载日志。
  7. 验证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

目录:

text
redis-stack/
├── compose.yaml
├── redis.conf
└── secrets/
    ├── users.acl
    └── redis-health-password.txt

Secret文件只在本地学习时准备并加入 .gitignore,生产由Secret平台生成。users.acl中的health密码必须与 redis-health-password.txt 一致。

24.1 redis.conf

ini
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

text
user default off
user order_app on >替换为高强度应用密码 ~order:* +@read +@write +ping -@dangerous
user health on >替换为高强度健康密码 ~* +ping

24.3 Compose

yaml
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包含 defaultorder_apphealth 三个用户定义
Healthcheck中的 cat/run/secrets/redis-health-password只保存health用户的原始密码,不包含引号、用户名和命令

users.acl 中的:

text
user health on >health-password-123 ~* +ping

要求 redis-health-password.txt 的内容恰好是:

text
health-password-123

如果两边不一致,Redis本身可能已经正常运行,但健康检查会得到 WRONGPASS,容器状态变成 unhealthy。如果Secret目标文件名与 aclfile 不一致,Redis则会在启动阶段读取ACL文件失败。排查时要同时检查最终Compose模型、容器Mounts和日志,不能只看源文件:

bash
docker compose config
docker inspect --format '{{json .Mounts}}' order-redis
docker compose logs --timestamps --tail 200 redis

24.4 启动前

bash
docker compose config
docker compose pull
docker compose up -d --wait

24.5 验证

在当前Shell临时设置密码,避免放在CLI参数:

bash
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或受控客户端配置。

容器验证:

bash
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 验证持久化

bash
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 升级前

bash
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 升级流程

mermaid
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 连接拒绝或超时

bash
docker compose ps
docker logs --tail 200 order-redis
docker network inspect <网络>
docker port order-redis

检查服务名、共同网络、bind、protected mode、宿主机端口、防火墙和客户端超时。应用容器不使用localhost访问Redis服务。

26.3 OOMKilled

bash
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 内存满但没有淘汰

检查:

text
maxmemory
maxmemory-policy
evicted_keys
Key是否有TTL
写命令返回OOM错误

noeviction会让写失败;volatile-*在没有TTL Key时也可能无法淘汰预期数据。

26.5 重启后数据丢失

检查:

bash
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。

配置:

text
容器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文件,因为旧容器可能从未挂卷;也不能删除损坏文件直接启动,应先保留现场并隔离恢复。

三十二、学习验收

不看答案完成:

  1. 使用只读redis.conf、ACL Secret和Named Volume启动Redis,不关闭protected mode。
  2. 证明应用用户只能访问 order:* 且不能执行危险命令。
  3. 比较RDB、AOF everysec和混合持久化的数据丢失窗口。
  4. 在1GiB容器中解释512MiB maxmemory之外的内存去向。
  5. 触发BGSAVE或AOF rewrite,观察fork、RSS、IO和延迟。
  6. 停止并重建容器,证明Volume中的数据恢复;再解释Volume为何不是备份。
  7. 在独立空Volume恢复RDB/AOF并验证Key、类型和TTL。
  8. 分别说明单容器、主从、哨兵和Cluster的故障域、容量和客户端要求。
  9. 使用INFO、SLOWLOG、LATENCY和commandstats定位一次延迟抖动。
  10. 写出Redis版本升级、Module兼容、金丝雀和失败回退方案。

关联知识点