☰
Redis高可用、持久化与性能管理实战:从单机到集群的避坑指南
2026/10/22 21:54:22 网站建设 项目流程

Redis 这玩意儿,刚上手的时候觉得就是个内存缓存,set/get 一把梭,能有多复杂?但真到了生产环境,尤其是流量稍微上来一点,你就会发现事情远没那么简单。节点挂了怎么办?重启之后数据还在不在?内存满了是疯狂淘汰还是直接写不进去?这些问题不解决,线上随时可能给你来个“惊喜”。这篇内容就是围绕 Redis 的高可用、持久化以及性能管理这三个核心命题展开,把我自己踩过的坑、调过的参数、验证过的方案,尽可能完整地梳理一遍。不管你是刚接触 Redis 的新手,还是已经在线上跑了一段时间但总觉得心里没底的老手,应该都能从中找到一些可以直接拿去用的东西。

1. 先把高可用的账算清楚:你到底需要哪种级别的“不挂”

很多人一上来就说“我要高可用”,但高可用这三个字在不同业务场景下的含义差别巨大。是允许秒级抖动但不能丢数据?还是可以丢几秒数据但绝对不能停服务?又或者是读写分离之后读节点挂了无所谓?这些问题不想清楚,后面选型就是瞎选。

1.1 单机模式的真实边界在哪里

单机 Redis 其实比很多人想象的要能扛。在一台配置还不错的机器上,纯内存操作,QPS 跑到几万甚至十万级别并不罕见。但它的边界也非常明确:进程挂了,服务就没了;机器宕机了,数据就没了(除非你配了持久化,但恢复也需要时间);内存不够了,要么淘汰要么报错。

我见过不少团队在业务早期用单机 Redis 扛了很久,这本身没问题。问题在于,他们往往没有为“单机扛不住的那一天”做任何准备,等到流量真的上来了,才手忙脚乱地去搞集群,结果因为前期代码里埋了太多对单机模式的假设(比如用了 keys 命令、用了多数据库、用了事务里跨 slot 的操作),迁移成本高得吓人。

所以我的建议是:你可以用单机,但要从第一天起就假设未来会走向分布式。具体来说,避免使用那些在集群模式下会失效的命令和设计,比如 KEYS、MIGRATE、涉及多个 key 的事务操作等。这样等到需要扩容的时候,迁移会平滑很多。

1.2 主从复制:读扩展与故障兜底的基础

主从复制是 Redis 高可用的基石。它的原理不复杂:从节点连上主节点之后,主节点会 fork 一个子进程生成 RDB 快照发给从节点,从节点加载完之后,主节点后续的写命令会持续同步给从节点。

这里有几个关键点容易被忽略。第一,主从复制是异步的,主节点写完内存就返回给客户端了,不会等从节点确认。这意味着如果主节点在同步完成之前挂了,那部分数据就丢了。第二,从节点默认是只读的,但可以通过配置改成可写,不过强烈不建议这么做,因为从节点写入的数据不会同步回主节点,会造成数据不一致。第三,主从之间的复制缓冲区大小需要关注,如果从节点断连时间较长,主节点的复制积压缓冲区(repl-backlog)可能已经被覆盖,导致从节点只能做全量同步,这会带来明显的性能抖动。

在实际部署中,我通常会把主从节点放在不同的物理机上,甚至不同的机架上,避免单点硬件故障导致主从同时挂掉。从节点的数量一般控制在 2 到 3 个,太多了主节点的复制压力会比较大,而且故障切换时的选举也会更慢。

1.3 哨兵模式:自动故障切换的利与弊

哨兵(Sentinel)解决的核心问题是:主节点挂了之后,能不能自动把从节点提升为新的主节点,让服务继续可用。它的工作方式是:一组哨兵进程持续监控主从节点的状态,当多数哨兵都认为主节点不可达时,就会触发故障切换流程,选出一个从节点提升为主节点,并通知其他从节点和客户端。

哨兵模式看起来很美,但实际用起来有几个坑。首先是哨兵本身也需要奇数个节点,至少三个,否则无法形成多数派。其次,故障切换期间服务会有短暂的不可用,通常是几秒到十几秒,具体取决于配置的超时时间和网络状况。第三,哨兵的配置在切换后需要正确传播,如果客户端没有正确实现哨兵协议,切换后可能连到旧的主节点上,导致写入失败。

我个人的经验是,哨兵模式适合那些对自动切换有需求、但能容忍秒级抖动的场景。如果你的业务对抖动极其敏感,可能需要考虑在客户端层面做更精细的容错,比如双写、快速失败重试等策略。

1.4 集群模式:分片与高可用的结合

Redis Cluster 把数据分成 16384 个槽位,每个节点负责一部分槽位,同时每个主节点可以带若干从节点。当某个主节点挂掉时,它的从节点会被提升,接管对应的槽位,从而实现高可用。同时,由于数据分布在多个节点上,集群模式也天然支持水平扩展。

但集群模式的复杂度也最高。首先,客户端必须支持集群协议,能够根据 key 计算出对应的槽位并路由到正确的节点。其次,涉及多个 key 的操作必须保证这些 key 在同一个槽位上,否则会报 CROSSSLOT 错误。第三,集群的扩缩容涉及槽位迁移,这个过程虽然可以在线进行,但对性能有一定影响,需要选择合适的时机。

我在实际使用中总结了一个原则:如果你的数据量在单机内存能扛住的范围内,且业务对延迟极其敏感,优先考虑主从加哨兵;如果数据量确实很大,或者写入量单机扛不住,再考虑集群。不要为了“看起来更高级”而盲目上集群,运维复杂度会成倍增加。

2. 持久化不是“配了就完事”:RDB 与 AOF 的取舍逻辑

持久化这个话题,网上很多文章讲得比较表面,无非是 RDB 快、AOF 全、混合最好。但实际生产环境中,持久化的配置直接关系到数据安全性和性能表现,需要根据业务特点仔细权衡。

2.1 RDB 的触发时机与 fork 代价

RDB 是把当前内存中的数据生成一个快照文件。它的触发方式有三种:手动执行 SAVE 或 BGSAVE、配置定时触发、以及主从复制时自动触发。

SAVE 命令会阻塞主线程,生产环境绝对不要用。BGSAVE 会 fork 一个子进程来做快照,主线程继续处理请求。但 fork 这个操作本身是有代价的,尤其是在内存较大的实例上。fork 的时间与内存页表的大小成正比,一个几十 GB 的实例,fork 可能耗时几百毫秒甚至更长,这期间主线程是阻塞的。

另外,fork 之后,主线程和子进程共享内存页,但一旦主线程有写操作,就会触发写时复制(Copy-On-Write),被修改的内存页会被复制一份。如果写入非常频繁,COW 会导致内存占用迅速膨胀,极端情况下可能触发 OOM。

所以 RDB 的定时触发频率需要根据业务写入量来调整。写入量大的实例,不建议设置过于频繁的 RDB,比如每 5 分钟一次就太激进了。我通常会用类似save 900 1、save 300 10、save 60 10000这样的组合,意思是 900 秒内至少 1 个 key 变化、300 秒内至少 10 个、60 秒内至少 10000 个,满足任一条件就触发。这样在写入量小的时候不会频繁 fork,写入量大的时候也能保证一定的持久化频率。

2.2 AOF 的刷盘策略:性能与安全的平衡点

AOF 是把每个写命令追加到文件末尾。它的核心配置是appendfsync,有三个选项:

  • always:每个写命令都 fsync 到磁盘。最安全,但性能最差,通常只能跑到几百 TPS。
  • everysec:每秒 fsync 一次。这是默认值,也是大多数场景下的推荐值。最多丢一秒的数据,性能影响可控。
  • no:由操作系统决定什么时候 fsync。性能最好,但丢数据的风险最大,可能丢几十秒甚至更多。

我见过一些团队为了追求极致性能把 appendfsync 设成 no,结果机器一断电,AOF 文件里最后一大段数据全没了。除非你的业务真的能容忍大量数据丢失,否则不建议用 no。

另外,AOF 文件会随着时间推移不断增大,因为很多命令其实是重复的或者可以被合并的。Redis 提供了 AOF 重写机制,会 fork 一个子进程,根据当前内存中的数据生成一份新的 AOF 文件,然后替换旧的。重写过程中,新的写命令会同时写入旧 AOF 缓冲和新 AOF 缓冲,保证数据不丢。

AOF 重写的触发条件可以通过auto-aof-rewrite-percentage和auto-aof-rewrite-min-size来控制。前者表示 AOF 文件比上次重写后增长了百分之多少就触发,后者表示最小文件大小。我一般会设置成增长 100% 且文件大于 64MB 时触发,避免小文件频繁重写。

2.3 混合持久化:兼顾恢复速度与数据完整性

Redis 4.0 之后引入了混合持久化,也就是 RDB 和 AOF 结合。具体来说,AOF 重写的时候,不再单纯写命令,而是先写一份 RDB 格式的全量数据,然后再追加增量命令。这样既保留了 RDB 恢复快的优势,又保留了 AOF 数据相对完整的优势。

开启方式很简单,把aof-use-rdb-preamble设为 yes 即可。我实测下来,混合持久化在恢复速度上比纯 AOF 快很多,尤其是数据量大的时候,差距非常明显。同时,由于增量命令还在,数据安全性也没有明显下降。

不过需要注意的是,混合持久化生成的 AOF 文件,前半部分是二进制格式,后半部分是文本格式,用普通的文本工具打开会看到乱码。这是正常的,不要以为是文件损坏了。

2.4 持久化配置的实战检查清单

在实际部署中,我通常会按照下面这个清单来检查持久化配置:

检查项推荐配置说明
RDB 触发策略save 900 1 / save 300 10 / save 60 10000根据写入量调整
AOF 开关appendonly yes对数据安全有要求的场景必开
AOF 刷盘策略appendfsync everysec性能与安全的平衡点
AOF 重写触发auto-aof-rewrite-percentage 100增长一倍时触发
AOF 重写最小大小auto-aof-rewrite-min-size 64mb避免小文件频繁重写
混合持久化aof-use-rdb-preamble yes兼顾恢复速度与完整性
持久化目录dir /data/redis独立磁盘,避免与系统盘竞争 IO

还有一点容易被忽略:持久化文件所在的磁盘 IO 性能直接影响 fork 和 fsync 的耗时。如果条件允许,尽量把持久化文件放在独立的 SSD 上,不要和系统盘或者日志盘混用。

3. 内存管理:别等到 OOM 了才想起来看配置

Redis 是内存数据库,内存管理的重要性怎么强调都不为过。但很多团队在部署的时候,只是简单地设了一个 maxmemory,然后选了一个淘汰策略,就觉得万事大吉了。实际上,内存管理涉及监控、淘汰、碎片整理等多个方面,任何一个环节出问题都可能导致服务异常。

3.1 maxmemory 到底该设多少

maxmemory 的设置不是拍脑袋决定的。设得太小,数据还没存多少就开始淘汰,缓存命中率下降;设得太大,可能触发系统 OOM Killer,把 Redis 进程直接杀掉,这比 Redis 自己淘汰数据要严重得多。

我的经验是,maxmemory 应该设置为物理内存的 70% 到 80%,留出足够的空间给操作系统、持久化 fork 时的 COW、以及客户端连接缓冲区等。比如一台 32GB 内存的机器,maxmemory 设在 24GB 左右比较稳妥。

另外,如果同时开启了 RDB 和 AOF 重写,fork 时的 COW 可能导致内存占用瞬间翻倍。这种情况下,maxmemory 的比例还要再降低一些,或者考虑把持久化操作分散到不同的时间窗口。

3.2 八种淘汰策略的适用场景

Redis 提供了八种淘汰策略,很多人只知道 allkeys-lru 和 noeviction,其实其他几种在特定场景下也很有用。

  • noeviction:不淘汰任何数据,内存满了之后写入报错。适合那些绝对不能丢数据的场景,但需要应用层做好错误处理。
  • allkeys-lru:从所有 key 中淘汰最近最少使用的。这是最常用的策略,适合纯缓存场景。
  • allkeys-lfu:从所有 key 中淘汰使用频率最低的。适合热点数据明显的场景,LFU 比 LRU 更能抵抗偶发访问的干扰。
  • allkeys-random:随机淘汰。适合访问分布比较均匀、没有明显热点的场景。
  • volatile-lru:从设置了过期时间的 key 中淘汰最近最少使用的。适合混合存储场景,持久数据不设过期时间,缓存数据设过期时间。
  • volatile-lfu:类似 volatile-lru,但用 LFU 算法。
  • volatile-random:从设置了过期时间的 key 中随机淘汰。
  • volatile-ttl:从设置了过期时间的 key 中淘汰剩余生存时间最短的。

选择哪种策略,核心看你的数据访问模式。如果数据访问有明显的热点,LRU 或 LFU 比较合适;如果访问比较均匀,random 也可以;如果只有部分数据是缓存,其他是持久数据,那就用 volatile 系列。

3.3 内存碎片:那个容易被忽视的性能杀手

Redis 默认使用 jemalloc 作为内存分配器,它在分配内存时会按照一定的尺寸类别来分配,这可能导致实际分配的内存比需要的内存多,从而产生内存碎片。碎片率可以通过mem_fragmentation_ratio这个指标来观察,它等于 Redis 报告的内存使用量除以操作系统报告的内存使用量。

一般来说,碎片率在 1 到 1.5 之间是正常的,超过 1.5 就说明碎片比较严重了。碎片率过高会导致内存浪费,甚至可能因为物理内存不足而触发 OOM。

Redis 4.0 之后提供了主动碎片整理功能,通过activedefrag yes开启。但碎片整理本身是有代价的,会消耗 CPU 和内存带宽,所以需要配合active-defrag-ignore-bytes和active-defrag-threshold-lower等参数来控制整理的触发条件,避免在业务高峰期进行整理。

我通常会在业务低峰期手动触发碎片整理,或者配置成只在碎片率超过一定阈值且 CPU 使用率较低时才自动整理。

3.4 大 key 和热 key 的发现与处理

大 key 和热 key 是 Redis 运维中的两大难题。大 key 会导致操作阻塞、网络拥塞、持久化变慢;热 key 会导致单个节点压力过大,即使集群模式下也无法通过增加节点来分担。

发现大 key 的方法有几种:用redis-cli --bigkeys扫描,或者用memory usage命令查看单个 key 的内存占用,或者通过分析 RDB 文件来统计。我一般会定期跑一次 bigkeys 扫描,对超过一定大小的 key 进行告警。

处理大 key 的思路是拆分。比如一个包含百万元素的 Hash,可以按照某种规则拆成多个小 Hash,每个 Hash 只存一部分元素。或者把大 key 拆成多个 String,用应用层逻辑来组合。

热 key 的发现相对困难一些,因为需要统计每个 key 的访问频率。可以通过 Redis 的monitor命令(但性能影响大,只适合临时排查),或者在客户端层面做统计。处理热 key 的思路是在客户端做本地缓存,或者把热 key 复制到多个节点上,让读请求分散开。

4. 性能调优:从参数到架构的逐层优化

性能调优是个系统工程,不是改一两个参数就能立竿见影的。我通常会把优化分成几个层次:操作系统层、Redis 配置层、客户端使用层、以及架构层。每一层都有各自的优化空间。

4.1 操作系统层面的关键调整

Redis 在 Linux 上运行,操作系统的很多默认配置并不适合高并发场景。下面这几个参数是我每次部署都会检查的:

  • vm.overcommit_memory:设为 1。这个参数控制内存分配策略,设为 1 表示允许超额分配。Redis 在 fork 的时候需要这个设置,否则可能因为内存不足而 fork 失败。
  • transparent_hugepage:设为 never。透明大页会导致 fork 和内存访问的延迟增加,对 Redis 这种延迟敏感的应用不友好。
  • somaxconn:调大,比如 1024 或更大。这个参数控制 TCP 连接队列的长度,在高并发场景下,默认的 128 可能不够用。
  • tcp_max_syn_backlog:同样调大,配合 somaxconn 使用。
  • 文件描述符限制:通过ulimit -n调整,建议至少 65535。Redis 需要为每个客户端连接维护一个文件描述符,连接数多了之后默认的 1024 肯定不够。

这些参数看起来琐碎,但每一个都可能成为性能瓶颈。我遇到过因为 somaxconn 太小导致连接被丢弃的情况,排查了很久才定位到。

4.2 Redis 配置中那些值得关注的参数

除了前面提到的持久化和内存相关配置,还有几个参数对性能影响比较大:

  • tcp-backlog:对应操作系统的 somaxconn,建议设成一样的值。
  • timeout:客户端空闲连接的超时时间,设为 0 表示不超时。生产环境建议设一个合理的值,比如 300 秒,避免大量空闲连接占用资源。
  • tcp-keepalive:TCP 保活时间,建议设为 300 秒,及时发现断连的客户端。
  • hz:Redis 后台任务的执行频率,默认是 10。调大可以让过期 key 的清理更及时,但会增加 CPU 消耗。一般不建议超过 100。
  • dynamic-hz:开启后 Redis 会根据连接数动态调整 hz,比较智能,建议开启。
  • lazyfree-lazy-eviction、lazyfree-lazy-expire、lazyfree-lazy-server-del:这几个参数控制是否用异步方式释放内存。开启后可以避免大 key 删除时的阻塞,但会增加内存管理的复杂度。我一般会开启 expire 和 eviction 的 lazyfree,server-del 看情况。

4.3 客户端使用中的常见性能陷阱

很多时候性能问题不是 Redis 本身的问题,而是客户端使用方式的问题。下面这几个坑我几乎在每个项目里都见过:

第一,大量使用 keys 命令。keys 命令会遍历所有 key,在 key 数量多的时候会严重阻塞 Redis。应该用 scan 命令代替,虽然 scan 不是一次性返回所有结果,但它是渐进式的,不会长时间阻塞。

第二,频繁使用 flushall 或 flushdb。这两个命令会清空数据,如果数据量大,释放内存的过程会阻塞很久。应该用flushall async或flushdb async来异步执行。

第三,pipeline 使用不当。pipeline 可以减少网络往返次数,但一次 pipeline 里塞太多命令会导致客户端和 Redis 的内存占用飙升。我一般建议单次 pipeline 的命令数控制在几百到一千以内。

第四,没有设置合理的连接池。连接池太小会导致请求排队,太大又会占用过多资源。需要根据实际并发量来调整,同时设置好最大空闲连接数和最小空闲连接数。

第五,在循环里逐条操作 Redis。比如要获取 100 个 key 的值,不要循环 100 次 get,而是用 mget 一次性获取。虽然集群模式下 mget 有限制,但至少可以用 pipeline 来批量执行。

4.4 慢查询日志与延迟监控

Redis 提供了慢查询日志功能,通过slowlog-log-slower-than设置阈值(单位是微秒),超过这个阈值的命令会被记录到慢查询日志中。我一般会把这个阈值设成 10000 微秒,也就是 10 毫秒,然后定期检查慢查询日志,看看有没有需要优化的命令。

另外,latency-monitor-threshold可以开启延迟监控,记录那些执行时间超过阈值的操作。这个功能对排查偶发的延迟抖动很有帮助。

还有一个很实用的命令是redis-cli --latency,它会持续采样 Redis 的响应延迟,输出最小、最大、平均延迟等统计信息。在排查性能问题时,我通常会先跑一下这个命令,看看延迟是否稳定。

4.5 架构层面的优化思路

如果单机优化做到头了,性能还是不够,那就需要考虑架构层面的优化了。常见的思路有:

读写分离:主节点负责写,从节点负责读。这样可以显著提升读吞吐量,但需要注意主从延迟问题。如果业务对读的一致性要求很高,可能需要强制走主节点读,或者用 wait 命令等待同步。

分片:把数据分散到多个 Redis 实例上,每个实例只负责一部分数据。分片可以在客户端做,也可以用 Redis Cluster。客户端分片的好处是简单可控,缺点是需要自己处理扩缩容和数据迁移。

多级缓存:在 Redis 前面再加一层本地缓存,比如用 Caffeine 或 Guava Cache。本地缓存的访问速度比 Redis 快几个数量级,可以扛住绝大部分热点请求。但本地缓存的一致性维护比较麻烦,需要根据业务特点来设计失效策略。

代理层:在客户端和 Redis 之间加一个代理层,比如 Twemproxy 或 Codis。代理层可以屏蔽后端的复杂性,提供统一的访问入口,但也会引入额外的网络跳数和单点风险。

选择哪种架构,取决于你的业务特点、团队的技术栈和运维能力。没有银弹,只有权衡。

5. 监控与告警:让问题在爆发前被发现

再好的架构和配置,如果没有配套的监控和告警,出了问题也只能被动救火。Redis 的监控指标很多,但核心的其实就那么几个。

5.1 必须关注的几个核心指标

  • used_memory和used_memory_rss:前者是 Redis 报告的内存使用量,后者是操作系统报告的内存使用量。两者的比值就是碎片率。
  • mem_fragmentation_ratio:碎片率,超过 1.5 需要关注。
  • connected_clients:当前连接数,突然飙升可能意味着客户端连接泄漏。
  • blocked_clients:被阻塞的客户端数量,比如在执行 BLPOP 等阻塞命令。
  • instantaneous_ops_per_sec:实时 QPS,用来观察流量变化。
  • keyspace_hits和keyspace_misses:缓存命中率,命中率下降可能意味着缓存策略需要调整。
  • evicted_keys:被淘汰的 key 数量,如果持续增长,说明内存不够用了。
  • expired_keys:过期 key 数量,可以反映过期策略的效果。
  • latest_fork_usec:最近一次 fork 的耗时,如果超过 100 毫秒需要关注。
  • master_link_status:主从复制状态,从节点上这个指标应该是 up。

这些指标可以通过 Redis 的info命令获取,也可以接入 Prometheus 等监控系统。我一般会设置告警规则,比如内存使用率超过 80%、碎片率超过 1.5、主从断连超过 10 秒等。

5.2 慢查询与延迟的持续跟踪

慢查询日志和延迟监控不能只看一次,要持续跟踪。我通常会把慢查询日志定期导出,分析哪些命令出现频率高、耗时长的原因是什么。是数据量太大了?还是命令本身复杂度高?还是网络问题?

延迟监控也是类似,如果发现延迟抖动频繁,需要结合操作系统层面的指标(比如 CPU、内存、磁盘 IO、网络)来综合判断。有时候问题不在 Redis 本身,而在宿主机上其他进程抢占了资源。

5.3 告警策略的制定原则

告警不是越多越好,太多告警会导致“告警疲劳”,真正重要的问题反而被忽略。我的原则是:只对影响服务可用性和数据安全性的问题设置告警,其他指标可以通过仪表盘观察,但不一定要告警。

比如,内存使用率超过 80% 需要告警,因为可能触发淘汰或 OOM;主从断连需要告警,因为影响高可用;fork 耗时超过 500 毫秒需要告警,因为可能导致明显的请求延迟。而像 QPS 波动、连接数小幅变化这些,看看仪表盘就行了,不需要告警。

告警的阈值也需要根据业务特点来调整。比如一个写入量很大的业务,evicted_keys 有一定数量是正常的;但一个写入量很小的业务,如果出现 evicted_keys,那就说明内存配置有问题了。

6. 几个真实踩过的坑与应对方式

理论说了这么多,最后分享几个我在实际运维中踩过的坑,以及当时的处理方式。这些经验在官方文档里是找不到的,但每一个都让我印象深刻。

6.1 主从切换后客户端连不上新主节点

有一次线上做故障演练,手动触发了一次哨兵切换。切换本身很顺利,从节点成功提升为主节点,但客户端却一直报连接错误。排查后发现,客户端的哨兵配置里只写了哨兵的地址,但没有正确处理哨兵返回的新主节点地址。客户端仍然在尝试连接旧的主节点,而旧主节点已经变成从节点了,只读不写。

这个问题的根源在于客户端没有正确实现哨兵协议。后来我们换了一个支持哨兵协议的客户端库,并且在代码里增加了连接失败后的重试逻辑,确保切换后能自动连到新的主节点。

6.2 AOF 文件损坏导致启动失败

有一次一台 Redis 实例意外宕机,重启的时候发现 AOF 文件损坏了,Redis 无法启动。原因是宕机的时候正好在写 AOF,文件没有完整刷盘。Redis 提供了redis-check-aof工具来修复 AOF 文件,但修复过程会丢弃损坏部分之后的所有命令,也就是说会丢一部分数据。

为了避免这种情况,我们后来做了两件事:一是把appendfsync从 always 改成了 everysec,减少频繁 fsync 带来的性能开销和文件损坏风险;二是增加了 AOF 文件的备份机制,定期把 AOF 文件复制到其他存储上,万一损坏可以从备份恢复。

6.3 大 key 删除导致的请求阻塞

有一次业务方反馈 Redis 突然变慢,大量请求超时。排查后发现,有一个业务在删除一个包含几十万元素的 Hash key,这个删除操作是同步的,导致 Redis 主线程阻塞了好几秒。

后来我们做了两件事:一是开启lazyfree-lazy-server-del,让删除操作异步执行;二是推动业务方对大 key 进行拆分,从源头上避免大 key 的产生。同时,我们也在监控里增加了大 key 的检测,定期扫描并告警。

6.4 内存碎片率飙升导致的 OOM

有一次一台 Redis 实例的内存碎片率突然从 1.2 飙升到 2.0 以上,随后触发了系统 OOM,Redis 进程被杀死。排查后发现,那段时间业务在频繁地写入和删除大量不同大小的 key,导致 jemalloc 产生了大量碎片。

处理方式是开启activedefrag,并配置在业务低峰期自动整理碎片。同时,我们也调整了 maxmemory 的比例,从 80% 降到了 70%,给碎片留出更多缓冲空间。后来碎片率一直稳定在 1.3 左右,没有再出现 OOM。

6.5 集群模式下跨 slot 操作报错

有一次在集群模式下执行一个涉及多个 key 的 Lua 脚本,结果报 CROSSSLOT 错误。原因是这些 key 被分配到了不同的槽位上,而 Redis Cluster 要求单个命令或脚本涉及的所有 key 必须在同一个槽位上。

解决方式有两种:一是用 hash tag 强制让相关 key 落在同一个槽位上,比如把 key 命名成user:{123}:name和user:{123}:age,这样花括号里的内容相同,Redis 就会把它们分配到同一个槽位;二是把跨 key 的操作拆分成多个单 key 操作,在应用层组合结果。

7. 一些零散但实用的经验补充

除了上面这些系统性的内容,还有一些零散的经验,虽然不成体系,但关键时刻能派上用场。

关于备份:RDB 文件是很好的备份介质,因为它紧凑且恢复快。我通常会定期把 RDB 文件备份到对象存储上,保留最近几天的版本。AOF 文件因为体积大且恢复慢,一般不作为主要备份手段。

关于版本升级:Redis 的版本升级通常比较平滑,但跨大版本升级还是需要谨慎。我一般会先在测试环境验证,确认没有兼容性问题后再上生产。升级方式优先选择主从切换,即先升级从节点,再手动切换主从,最后升级旧主节点。

关于安全:Redis 默认没有密码,且监听所有网络接口。生产环境一定要设置密码(requirepass),并且通过bind限制监听的 IP,避免暴露在公网上。另外,可以重命名或禁用一些危险命令,比如FLUSHALL、CONFIG、KEYS等,通过rename-command配置来实现。

关于连接池:不同的客户端库对连接池的实现不一样,需要仔细阅读文档。比如 Jedis 的连接池是阻塞式的,连接不够时会等待;Lettuce 是基于 Netty 的,支持异步和响应式编程。选择哪个取决于你的技术栈和业务需求。

关于序列化:存到 Redis 里的数据需要序列化,序列化方式的选择会影响性能和兼容性。JSON 可读性好但体积大,Protobuf 体积小但需要定义 schema,JDK 序列化兼容性好但有安全风险。我一般推荐用 JSON 或 Protobuf,具体看场景。

关于过期时间:设置过期时间时,尽量避免大量 key 在同一时刻过期,否则可能导致缓存雪崩。可以在基础过期时间上加上一个随机值,让过期时间分散开。另外,对于热点数据,可以考虑设置永不过期,通过后台任务定期更新。

关于主从延迟:主从复制是异步的,从节点上的数据可能比主节点旧。如果业务对一致性要求高,可以在写入后执行WAIT命令,等待指定数量的从节点确认。但 WAIT 会增加写入延迟,需要权衡。

关于集群扩缩容:集群扩容时,新节点加入后需要迁移槽位。迁移过程中,槽位对应的 key 会被逐个迁移,这个过程中如果有大量写入,可能会影响性能。建议在业务低峰期进行,并且监控迁移进度和延迟。

关于监控工具:除了 Prometheus,还有一些专门针对 Redis 的监控工具,比如 RedisInsight、Redis Live 等。这些工具可以提供更直观的界面和更丰富的分析功能,适合日常运维使用。

关于压测:在上线前做压测是必要的,但压测的方式很重要。不要只测 get/set 这种简单命令,要模拟真实业务的命令组合和数据分布。另外,压测时要监控 Redis 的各项指标,观察在压力下的表现,而不仅仅是看 QPS 数字。

关于故障演练:定期做故障演练可以帮助发现潜在问题。比如手动 kill 掉主节点,观察哨兵是否能正常切换;模拟网络分区,观察集群的行为;模拟磁盘满,观察持久化是否正常。这些演练虽然麻烦,但能在真正出问题的时候让你心里有底。

关于文档:Redis 的配置项非常多,而且不同版本之间可能有差异。建议把生产环境的配置整理成文档,记录每个配置项的值和设置理由。这样在排查问题或者做变更的时候,能快速定位到相关配置。

关于团队协作:Redis 的运维不是一个人的事,需要开发、运维、DBA 等多方协作。建议建立清晰的职责边界和沟通机制,比如开发负责客户端使用优化,运维负责实例部署和监控,DBA 负责数据安全和备份恢复。定期开会对齐,避免出现问题时互相推诿。

关于学习路径:Redis 的官方文档是最权威的学习资料,但比较零散。建议先通读一遍《Redis 设计与实现》这类书籍,理解底层原理,然后再结合实际运维经验来加深理解。遇到问题时,多查官方文档和社区讨论,少依赖二手资料。

关于心态:Redis 运维是个需要耐心和细心的活,很多问题不是一下子就能定位到的。遇到问题时不要慌,按照“先恢复服务,再排查原因”的原则来处理。恢复服务的手段包括切换主从、重启实例、扩容等,排查原因则需要结合日志、监控、慢查询等多方面信息。

关于持续优化:Redis 的优化没有终点,随着业务的发展和数据量的增长,之前合适的配置可能变得不合适了。建议定期回顾 Redis 的使用情况,看看有没有可以优化的地方,比如调整淘汰策略、优化 key 的设计、增加从节点等。保持对新技术和新版本的关注,适时引入一些新的特性和工具。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询