Redis高可用、持久化与性能管理:生产环境实战避坑指南 Redis 这套东西刚上手的时候觉得就是个内存缓存set/get 一把梭能有多难可真到了生产环境尤其是流量一上来、机器一重启、主从一切换各种幺蛾子全冒出来了。我见过太多团队Redis 用着用着就变成了单点定时炸弹——没做高可用主节点一挂整个业务雪崩没配持久化重启之后缓存全空数据库瞬间被击穿性能不管理慢查询堆着堆着就把内存打爆了。这篇就围绕Redis 高可用、持久化及性能管理这三块把我这些年踩过的坑、调过的参数、验证过的方案从头到尾捋一遍。不管你是刚接触 Redis 的新手还是已经管着几十个实例的老手应该都能从里面找到能直接抄作业的东西。1. 先把整体思路理清楚为什么这三件事必须一起做1.1 高可用、持久化、性能管理不是三个独立话题很多人学 Redis 是割裂着学的今天看主从复制明天看 RDB 和 AOF后天调 maxmemory。结果真到线上出问题发现这三者是互相咬合的。举个最典型的场景主节点宕机哨兵把从节点提升为主——这个高可用动作能不能顺利完成取决于从节点的数据是不是足够新而数据新不新直接跟持久化和复制积压缓冲区挂钩。再比如你为了性能把 RDB 的 save 频率调得很低结果宕机时丢了一大段数据高可用切换过去之后数据是残缺的业务照样出问题。所以我的建议是把这三块当成一个整体来设计。高可用解决的是服务别断持久化解决的是数据别丢性能管理解决的是别把自己撑死。三者优先级怎么排取决于你的业务如果是纯缓存场景丢了能回源重建那持久化可以弱化重点保高可用和性能如果是拿 Redis 当存储用比如做排行榜、计数器、分布式锁那持久化就是命根子必须重点保障。我一般会先问自己三个问题这份数据丢了能不能从别处恢复服务中断几分钟业务能不能忍内存和 QPS 的天花板在哪把这三个问题回答清楚方案基本就定型了。1.2 三种部署形态的取舍逻辑Redis 的部署形态从简单到复杂大致是单机、主从、哨兵、集群。选哪个不是拍脑袋得看数据量和可用性要求。单机就不用说了只适合开发测试生产环境上单机基本等于给自己埋雷。主从复制解决了读扩展和数据备份的问题但主挂了需要人工介入切换做不到自动故障转移。哨兵在主观下线、客观下线、领导者选举这一套机制下能实现自动切换是中小规模场景下性价比最高的高可用方案。集群则是为了解决单机内存上限和写扩展问题把数据分片到多个主节点上每个分片再配从节点。这里有个常见的误区以为集群就一定比哨兵高级。其实不是。集群的运维复杂度、客户端兼容性要求、跨槽操作的限制都是实打实的成本。如果你的数据量没超过单机内存上限比如 32G 以内QPS 也没到单节点扛不住的程度哨兵方案往往更省心。我见过有团队数据才几个 G硬上集群结果为了处理跨槽的 mget 问题折腾了好几周纯属自找麻烦。1.3 一个我常用的决策参考表场景特征推荐形态持久化策略关键理由开发测试、数据可丢单机可关闭省资源快速迭代读多写少、数据量小主从 哨兵RDB 为主自动切换运维简单数据重要、不能丢主从 哨兵AOF everysec兼顾安全与性能数据量大、写并发高集群RDB AOF分片扩展双重保障纯缓存、可回源主从 哨兵可弱化优先保性能和可用这张表不是死的实际选型还要结合团队运维能力。一个只有两三个人的小团队硬上集群出了问题没人能快速定位反而更危险。2. 高可用方案落地从主从复制到哨兵自动切换2.1 主从复制的完整握手过程主从复制是 Redis 高可用的地基搞不清楚它的握手流程后面排查问题就是抓瞎。整个复制过程分几个阶段第一阶段是建立连接、协商同步。从节点执行replicaof命令后会跟主节点建立 socket 连接然后发送 PING 确认对方活着接着进行身份验证如果配了 requirepass再发送自己的监听端口信息。这一步做完从节点会发送PSYNC命令带上自己记录的 runid 和复制偏移量。第二阶段是全量同步或部分同步。主节点收到 PSYNC 后判断如果从节点带的 runid 跟自己不一致或者偏移量已经不在复制积压缓冲区里了就触发全量同步full resync否则走部分同步continue只补发缺失的那部分数据。全量同步的过程是主节点 fork 一个子进程生成 RDB 快照同时把 fork 之后的新写命令攒在缓冲区里RDB 生成完发给从节点从节点先清空自己的数据再加载 RDB最后主节点把缓冲区里的增量命令补发过去。第三阶段是命令传播。全量同步完成后主节点每执行一条写命令就异步发给所有从节点从节点执行同样的命令保持数据一致。同时从节点会周期性发送REPLCONF ACK上报自己的偏移量主节点据此判断从节点是否掉队。提示repl-backlog-size这个参数非常关键它决定了部分同步能不能成功。默认 1MB在高写入场景下很容易被写满导致从节点重连时被迫全量同步。我一般会按平均写入速率 × 断连容忍秒数来估算比如每秒写 2MB、容忍断 60 秒那就设成 128MB 比较稳妥。2.2 哨兵机制的核心三个定时任务和两次下线判断哨兵Sentinel本质是一个独立的进程它通过三个定时任务来监控整个主从体系每 10 秒一次向所有主从节点发送 INFO 命令获取拓扑结构发现新的从节点或哨兵。每 2 秒一次向所有节点的__sentinel__:hello频道发送自己的信息和判断同时订阅这个频道实现哨兵之间互相发现和状态交换。每 1 秒一次向所有主从节点和其他哨兵发送 PING做心跳检测。故障判断分两步。主观下线SDOWN是单个哨兵自己的判断如果 PING 超过down-after-milliseconds没收到有效回复就认为这个节点挂了。但单个哨兵可能因为网络抖动误判所以需要客观下线ODOWN当认为主节点下线的哨兵数量超过 quorum法定票数时才真正判定主节点客观下线触发故障转移。故障转移的流程是哨兵之间选举出一个领导者基于 Raft 算法由领导者从从节点里挑一个最合适的提升为主节点。挑选规则依次是先过滤掉不健康的从节点然后比较replica-priority越小优先级越高相同则比较复制偏移量越大数据越新再相同就比较 runid越小越优先。选出来之后执行slaveof no one提升为主再让其他从节点指向新主最后通过发布订阅通知客户端。2.3 哨兵配置实操与关键参数下面是一份我常用的哨兵配置模板可以直接改改就用port 26379 daemonize yes logfile /var/log/redis/sentinel.log dir /var/lib/redis/sentinel # 监控的主节点quorum 设为 2 sentinel monitor mymaster 192.168.1.10 6379 2 # 主节点多久无响应判定主观下线单位毫秒 sentinel down-after-milliseconds mymaster 30000 # 故障转移时最多同时有几个从节点同步新主 sentinel parallel-syncs mymaster 1 # 故障转移超时时间 sentinel failover-timeout mymaster 180000 # 主节点密码 sentinel auth-pass mymaster yourpassword几个参数的选择逻辑我解释一下。down-after-milliseconds设 30 秒是经验值设太短容易误判比如一次 GC 停顿就触发切换设太长故障恢复慢。parallel-syncs设 1 是为了避免所有从节点同时向新主发起全量同步把新主 IO 打满宁可慢一点也要稳。failover-timeout设 180 秒给故障转移留足时间尤其是数据量大的时候全量同步很耗时。注意哨兵节点至少部署 3 个且分布在不同物理机上。如果只部署 2 个一个挂了另一个无法形成多数派故障转移就失效了。这是很多人踩过的坑。2.4 客户端如何正确接入哨兵哨兵方案里客户端不能直连主节点 IP因为主节点会变。正确的做法是客户端连接哨兵集群通过哨兵获取当前主节点地址。以常见的客户端为例连接时传入的是哨兵节点列表和 master 名称客户端内部会订阅哨兵的切换事件主节点变了自动更新连接。这里有个容易忽略的点故障转移期间客户端会短暂报错。因为切换需要时间通常几秒到几十秒这期间写请求会失败。业务代码必须做好重试和降级不能假设 Redis 永远可用。我一般会在客户端配置里加上重试次数和重试间隔同时业务层对写失败做本地缓存或消息队列缓冲等 Redis 恢复后再补。3. 持久化机制RDB 与 AOF 的取舍和组合3.1 RDB 的触发时机与 fork 代价RDB 是把某一时刻的内存数据快照写成二进制文件。触发方式有三种手动执行SAVE阻塞主线程生产禁用和BGSAVEfork 子进程不阻塞主线程、根据配置自动触发、主从全量同步时自动触发。自动触发的配置长这样save 900 1 # 900 秒内至少 1 个 key 变化 save 300 10 # 300 秒内至少 10 个 key 变化 save 60 10000 # 60 秒内至少 10000 个 key 变化这三个条件是或的关系满足任意一个就触发 BGSAVE。RDB 的优点是文件紧凑、恢复速度快适合做备份和灾难恢复。缺点是两次快照之间的数据会丢而且 fork 子进程时如果内存很大fork 本身会阻塞主线程。fork 的代价跟内存页表大小成正比。在 Linux 上fork 采用写时复制COW理论上很快但如果内存有几十 G页表映射本身就要花几百毫秒甚至更久。我实测过一个 32G 的实例fork 耗时大概在 300-500ms 之间这期间主线程是卡住的。所以单实例内存不建议超过 32G这也是集群分片的一个现实理由。3.2 AOF 的三种刷盘策略对比AOF 是把每条写命令追加到日志文件里恢复时重放这些命令。刷盘策略由appendfsync控制有三种策略行为数据安全性性能always每条命令都 fsync最高几乎不丢最差QPS 大幅下降everysec每秒 fsync 一次最多丢 1 秒较好推荐no交给操作系统决定不可控可能丢很多最好生产环境我基本都用everysec它在安全性和性能之间取得了很好的平衡。always虽然安全但每次写都要落盘QPS 会掉到几千甚至更低除非是金融级场景否则没必要。no则太不可控操作系统可能几十秒才刷一次宕机丢数据量太大。AOF 文件会随着时间不断膨胀所以需要重写rewrite来压缩。重写也是 fork 子进程完成的子进程根据当前内存数据生成一份最精简的命令集比如一个 key 被 set 了 100 次重写后只保留最后一次。重写期间的新命令会同时写入旧 AOF 缓冲和新 AOF 缓冲重写完成后合并。auto-aof-rewrite-percentage 100 # 文件比上次重写后增长 100% 触发 auto-aof-rewrite-min-size 64mb # 文件至少 64MB 才触发3.3 混合持久化兼顾恢复速度和数据安全Redis 4.0 之后引入了混合持久化配置aof-use-rdb-preamble yes开启。它的原理是AOF 重写时先把当前内存数据以 RDB 格式写入 AOF 文件的开头之后的增量命令再以 AOF 格式追加。这样恢复时先加载 RDB 部分快再重放 AOF 部分补全兼顾了 RDB 的恢复速度和 AOF 的数据安全。我现在的默认配置就是混合持久化 everysec实测下来恢复一个 10G 的实例纯 AOF 要几分钟混合持久化只要几十秒差距非常明显。3.4 持久化选型的实操建议到底怎么选我给几个具体场景的建议纯缓存场景可以只开 RDB甚至关闭持久化。因为数据能从数据库回源丢了无所谓重点是别让持久化拖累性能。数据不能丢但能容忍秒级损失AOF everysec 混合持久化这是最通用的方案。数据绝对不能丢AOF always但要接受性能下降同时做好主从和哨兵。需要快速恢复的大实例RDB 为主 AOF 兜底或者混合持久化。提示无论选哪种都要定期验证备份文件的可用性。我见过太多团队配了持久化结果真出事时发现 RDB 文件损坏或者 AOF 文件不完整白配了。建议定期做恢复演练把备份文件拉到测试环境实际恢复一次。4. 性能管理内存、慢查询与连接数调优4.1 内存管理maxmemory 与淘汰策略Redis 是内存数据库内存管理是性能的生命线。核心参数是maxmemory它限制 Redis 能用的最大内存。如果不设Redis 会一直吃内存直到把机器撑爆触发 OOM Killer进程被杀这比主动淘汰还糟糕。maxmemory 8gb maxmemory-policy allkeys-lru淘汰策略有八种常用的几种noeviction不淘汰写入报错。适合当存储用、数据不能丢的场景。allkeys-lru所有 key 里淘汰最近最少使用的。适合纯缓存。volatile-lru只淘汰设了过期时间的 key。适合缓存和持久数据混存的场景。allkeys-lfu / volatile-lfu按访问频率淘汰比 LRU 更精准适合热点数据明显的场景。选策略的逻辑如果所有数据都是缓存用 allkeys-lru如果一部分是缓存一部分是持久数据用 volatile-lru并给缓存 key 都设过期时间如果热点集中用 lfu 系列。这里有个坑淘汰是采样近似算法不是精确的。maxmemory-samples控制采样数量默认 5调大更精确但更耗 CPU。一般 5-10 就够了不用调太高。4.2 慢查询日志定位性能瓶颈的利器Redis 的慢查询日志记录执行时间超过阈值的命令。配置两个参数slowlog-log-slower-than 10000 # 超过 10000 微秒10ms记录 slowlog-max-len 128 # 最多保留 128 条slowlog-log-slower-than设 10ms 是个合理的起点。注意单位是微秒别设错了。设 0 会记录所有命令性能影响大设负数则关闭。查看慢查询用SLOWLOG GET会返回命令、执行时间、时间戳等信息。我排查性能问题时第一步就是看慢查询日志往往能直接定位到问题命令。常见的慢命令有keys *全量扫描生产禁用、hgetall大 hash、smembers大 set、sort大列表、zrange大范围等。注意慢查询日志只记录命令执行时间不包括网络传输和排队时间。所以如果慢查询日志里没有慢命令但客户端还是感觉慢那问题可能出在网络、连接数或者阻塞命令上。4.3 连接数与客户端缓冲区管理连接数方面maxclients控制最大客户端连接数默认 10000。如果连接数打满新连接会被拒绝。排查连接泄漏要看CLIENT LIST找出空闲连接和异常连接。客户端缓冲区是另一个容易被忽视的点。Redis 为每个客户端维护输入缓冲和输出缓冲如果客户端消费慢输出缓冲会不断增长吃光内存。相关参数client-output-buffer-limit normal 0 0 0 client-output-buffer-limit replica 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60格式是硬限制 软限制 软限制持续秒数。超过硬限制立即断开超过软限制且持续超过指定秒数则断开。对于从节点和发布订阅客户端一定要设限制否则一个慢从节点就能把主节点内存拖垮。4.4 大 key 和热 key 的治理大 key 是性能杀手。一个几 MB 的 hash 或 list操作一次就可能阻塞主线程几百毫秒。治理方法一是拆分把大 key 拆成多个小 key比如按 hash 字段分片二是用SCAN系列命令渐进式遍历替代keys和hgetall三是定期用redis-cli --bigkeys扫描发现大 key。热 key 则是访问过于集中单个 key 的 QPS 过高可能打满单个分片。治理方法一是本地缓存在客户端缓存热 key二是读写分离把读请求分散到多个从节点三是 key 拆分给热 key 加随机后缀分散到多个 key 上。5. 常见问题排查与避坑经验实录5.1 故障转移后数据丢失怎么办这是哨兵方案最常被问到的问题。原因通常是主节点宕机时有一部分写命令还没同步到从节点切换后这部分数据就丢了。Redis 的复制是异步的这个窗口无法完全消除只能缩小。缓解手段一是调小repl-backlog-size相关的断连容忍时间让从节点尽量跟上二是用min-replicas-to-write和min-replicas-max-lag参数强制主节点在有足够健康从节点时才接受写入牺牲可用性换一致性。min-replicas-to-write 1 min-replicas-max-lag 10意思是至少 1 个从节点延迟不超过 10 秒主节点才接受写。这样能大幅减少切换时的数据丢失但代价是如果从节点都挂了主节点会拒绝写入。5.2 内存突然暴涨的排查思路内存暴涨一般有几个原因大 key 写入、客户端输出缓冲堆积、复制积压缓冲过大、碎片率过高。排查步骤INFO memory看 used_memory、used_memory_rss、mem_fragmentation_ratio。如果碎片率超过 1.5考虑开启 activedefrag 或重启实例。CLIENT LIST看有没有输出缓冲异常的客户端。redis-cli --bigkeys扫描大 key。检查是否有大量 key 没设过期时间导致只增不减。5.3 常见问题速查表现象可能原因排查命令解决方向主从切换后数据缺失异步复制窗口INFO replication配置 min-replicas内存持续增长大 key / 无过期INFO memory, --bigkeys拆分 key设过期响应变慢慢命令 / 阻塞SLOWLOG GET优化命令禁用 keys连接被拒连接数打满CLIENT LIST调大 maxclients查泄漏从节点频繁全量同步backlog 太小INFO stats调大 repl-backlog-sizefork 卡顿内存过大LATENCY DOCTOR控制单实例内存5.4 我踩过的几个真实坑第一个坑是持久化文件把磁盘写满。AOF 文件增长很快如果磁盘没监控写满之后 Redis 会拒绝写入。后来我加了磁盘告警并且把 AOF 重写阈值调低让它更频繁地压缩。第二个坑是哨兵和主从配置不一致。有一次主节点设了密码哨兵配置里忘了加sentinel auth-pass导致哨兵无法监控故障转移失效。配置变更一定要同步到所有相关组件。第三个坑是客户端没处理重连。故障转移期间连接断开客户端没做重试业务直接报错。后来在客户端加了连接池和重试逻辑才稳定下来。第四个坑是大 key 导致的阻塞。有个业务往一个 list 里塞了几十万条数据一次lrange就卡住主线程。后来改成用lrange分批取并且限制 list 长度问题才解决。5.5 监控指标清单最后给一份我常用的监控指标清单这些指标建议都接入告警可用性主从状态、哨兵数量、故障转移次数。性能QPS、命中率、慢查询数量、平均响应时间。内存used_memory、碎片率、淘汰 key 数量。持久化RDB 最后保存时间、AOF 文件大小、重写次数。连接当前连接数、拒绝连接数、输出缓冲大小。复制主从偏移量差、全量同步次数。命中率低于 90% 就要警惕了可能是缓存穿透或者 key 设计有问题。淘汰 key 数量持续增长说明内存不够用要么扩容要么优化数据结构。这套东西说起来多但真正落地之后Redis 的稳定性会有质的提升。我个人的体会是高可用、持久化、性能管理这三块任何一块偷懒最后都会以线上故障的形式还回来。与其出事之后救火不如一开始就把配置做扎实把监控做全把演练做够。