Redis那些复杂又细节的东西,咱们这次再来一次更深层次的探讨和分析
- 问答
- 2026-01-16 09:49:41
- 1
AOF重写背后的“数据陷阱”
我们都知道Redis有RDB和AOF两种持久化方式,AOF是通过记录每一个写命令来保证数据安全,但文件会越来越大,所以Redis提供了AOF重写机制,也就是把当前数据库的状态,用最小化的命令序列重新写到一个新的AOF文件中,然后替换掉旧的。
听起来很美,但问题就出在这个“重写”过程中,根据Redis官方文档的描述,AOF重写是由父进程fork出一个子进程来完成的,子进程会拥有父进程内存数据的一个快照,子进程开始遍历内存,将数据写入新的AOF文件。
关键细节来了: 在子进程进行重写的这段时间里(如果数据量很大,这个过程可能会很长),主进程(父进程)依然在接受客户端的写请求,这些新的写命令除了会写入原来的AOF缓冲区,还会被写入一个叫做“AOF重写缓冲区”的地方,当子进程完成重写后,父进程会把这个重写缓冲区里的命令追加到新的AOF文件中,然后再原子性地切换文件。
这会导致什么潜在问题?

- 最后追加阶段的主进程阻塞: 在将重写缓冲区的内容写入新AOF文件时,主进程是会被阻塞的,如果这个缓冲区的内容非常多,那么阻塞的时间就会变长,可能导致服务短暂不可用。
- 内存压力倍增: fork子进程的那一刻,虽然子进程不会完全复制父进程的内存(采用写时复制机制),但如果父进程有大量数据被修改,内核就需要为子进程复制这些内存页,导致内存消耗瞬间增加,如果你的机器内存刚好比较紧张,这个操作可能触发OOM(内存溢出),直接被系统杀掉,灾难就发生了。
- 数据不一致的极端情况: 这是一个更隐蔽的问题,假设你在重写过程中,使用了一个过期键,这个键在子进程的快照里是存在的,但就在重写完成前,这个键因为过期被主进程删除了,新生成的AOF文件里会包含一个本应过期的键,虽然这个键在Redis重新加载AOF后,会被惰性删除或定期删除机制清理掉,但在某个极短的时间窗口内,数据状态是“不干净”的。
主从复制与脑裂:网络抖动引发的“双主”灾难
主从复制是Redis高可用的基础,但它的机制里也埋着大雷,这就是“脑裂”问题。
想象一个场景:主节点(Master)和从节点(Slave)之间的网络突然出现严重延迟甚至短暂中断,但主节点和客户端之间的网络是好的,哨兵(Sentinel)集群检测到主节点失联,经过投票,它会将一个从节点提升为新的主节点。
但旧的主节点并没有宕机! 它只是因为网络分区,成了一个“孤岛”,它依然认为自己是主节点,并且还能接受客户端的写请求,系统中同时存在了两个都可以写入的“主节点”。

当网络恢复后,哨兵会让旧主节点降级为从节点,并去同步新主节点的数据。最要命的一步来了: 这个旧主节点会清空自己的全部数据,然后从新主节点做全量同步,这意味着,在网络分区期间,所有写入旧主节点的数据,将全部丢失,且不可恢复。
如何缓解? Redis提供了一些配置参数,min-slaves-to-write 和 min-slaves-max-lag,你可以设置主节点至少需要有多少个从节点连接,并且延迟不能超过多少秒,否则主节点就停止接受写请求,这样,一旦发生网络分区,旧主节点发现健康的从节点数量不足,就会自动变成“只读”状态,从而避免了数据丢失,但这本质上是以牺牲可用性来换取一致性。
内存管理:当删除一个超大Hash键时,服务为什么会卡住?
Redis是单线程处理命令的,这意味着所有命令都是排队执行的,当你使用 DEL 命令删除一个普通的键时,速度很快。

但如果你要删除的是一个包含几百万个字段的Hash键,或者一个非常长的List键,问题就来了,释放大量内存是一个比较耗时的操作,这个操作会一直占用着Redis的主线程,在这几百毫秒甚至几秒的时间里,Redis无法处理任何其他命令,所有请求都会被阻塞,看起来就是服务卡住了。
解决方案是使用异步删除。 Redis从4.0版本开始提供了 UNLINK 命令,它和 DEL 的区别在于,UNLINK 会在后台异步地回收内存,对于大键,一定要优先使用 UNLINK,同样,对于清空数据库,使用 FLUSHDB ASYNC 和 FLUSHALL ASYNC 也能避免服务卡顿。
CPU瓶颈:看似内存数据库,实则可能卡在CPU上
很多人认为Redis的瓶颈只在内存大小和网络带宽,但在一些极端场景下,CPU也会成为瓶颈,单线程的Redis无法利用多核优势,所以一个Redis实例的CPU使用率最高就是100%(一颗核心)。
什么操作会疯狂消耗CPU?
- 复杂度过高的命令:
KEYS *(绝对禁止在生产环境使用),或者对一个大集合进行SINTER/SUNION等求交集、并集的操作。 - 持久化的fork操作: 前面提到,fork本身虽然不阻塞主线程,但fork这个系统调用本身在数据量大时是比较耗CPU的。
- 大量的连接和频繁的短命令: 虽然每个命令简单,但每秒几十万的QPS意味着线程需要不停地进行网络IO、解析命令、查找数据,上下文切换非常频繁,CPU负载也会很高。
应对方法就是做好监控,避免使用慢查询命令,以及通过集群模式将压力分摊到多个Redis实例上。
Redis的深度在于,它的简单模型背后,隐藏着与操作系统(fork、内存)、网络(脑裂)、以及自身实现(单线程、持久化)紧密相关的复杂行为,不理解这些,就很难驾驭一个在生产环境中承担重要职责的Redis。
本文由寇乐童于2026-01-16发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://www.haoid.cn/wenda/81722.html
