3

随着我不断向 redis 写入数据,copy-on-write 使用的内存不断增加。即使我将程序写成足够长的睡眠时间,以便 redis 能够完成所有后台保存(最后的内存消息是写时复制使用的 0 MB 内存),下一次后台保存将回到高数字。

例子,

牛使用的1300MB内存

牛使用的1400MB内存

牛使用0MB内存

牛使用1500MB内存

所有这些究竟意味着什么?据我所知,如果写时复制内存不断增加,那么内存就没有足够的空间。此外,每次使用高内存的后台保存时,redis 似乎都无法正常工作。Jedis 总是遇到套接字超时异常。

4

2 回答 2

6

在这里我将解释一些事情:什么是写时复制(CoW)以及它如何消耗内存,为什么设置'vm.overcommit_memory = 1'不会帮助解决内存使用和性能问题,以及支持的最佳实践起来 Redis 数据。

Copy-on-Write 及其内存使用情况

Redis 的快照备份利用现代操作系统提供的 CoW 语义,解决了在 fork 进程时,父进程的内存被复制到子进程从而使内存占用加倍的问题。在 CoW 中,分叉的子进程将共享父进程的原始内存空间。它仅在任一进程修改该内存页面时复制该内存页面。下面是数据修改前和数据修改后的内存空间示意图:

当子进程 Q 被分叉时,它与父进程 P 共享确切的内存空间 当父进程 P 修改内存第 3 页时,它复制了它,而不是修改原始页 当 Redis 的 RDB 备份正在进行时,在父进程中会发生数据变化,它正在接受来自客户端的新请求并在内存中处理它。如果 QPS 很高,父进程将在子进程的备份时间内复制大量内存页面以用于新的更改。因此父进程将消耗额外的内存。在极端情况下,如果所有的内存页面都被修改了,那么 Redis 实例的内存占用会翻倍。是的,内存翻倍是有可能的,这个事实就解释了为什么Redis提供了“overcommit_memory=1”选项,能解决什么问题,不能解决什么问题(减少内存使用)。

“vm.overcommit_memory = 1”是什么,它解决了什么问题

在 RDB 备份期间,您可能会看到这样的日志错误:

10202:M 13 Sep 11:34:16.535 # Can't save in background: fork: Cannot allocate memory

它表示没有足够的内存来分叉子进程进行备份。如果 Redis 进程现在消耗了 2GB 内存,在 fork 子进程时,操作系统会假设你还有 ANOTHER 2GB 内存,这样在 CoW 的极端情况下,就有足够的内存来复制所有脏内存页。即使在 fork 子进程时还没有使用额外的内存,它也会检查空闲内存以避免以后的内存不足错误。在 Redis 日志中,它提供了解决方案:

10202:M 13 Sep 11:33:09.943 # WARNING overcommit_memory is set to 0! Background
save may fail under low memory condition. To fix this issue add
'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the
command 'sysctl vm.overcommit_memory=1' for this to take effect.

因此,设置 'vm.overcommit_memory = 1' 将允许您在空闲内存不足时分叉子进程。如果你知道备份过程中的脏内存页不会太多,就不会有任何实际问题,因为每次发生新的 CoW 操作时都会成功分配内存。

并且,'vm.overcommit_memory = 1'只保证你可以fork子进程来备份Redis数据,但如果父进程一直在进行写操作,它并不能减少内存使用。

Redis备份实践

Redis内存数据的持久化方式有三种:RDB(snapshotting),AOF,以及两者的混合。无论您如何配置设置,任何方法都会在一定程度上影响服务器响应时间。为了尽量减少持久化进程的影响,我们通常在从属实例中运行备份,而不是在主实例上运行。但是,如果我们在奴隶上这样做,就会有新的风险。当有网络分区时发生这种情况时,slave 可能无法保持最新​​状态,因此在 slave 上备份可能会丢失一些数据。一种解决方案是拥有多个从属服务器,因此降低了所有从属服务器与主实例不同步的可能性。另一个预防措施是建立强大的监控系统,以便我们可以更快地发现网络问题并减少网络分区的时间跨度。

于 2020-10-27T04:18:53.773 回答
2

从 Redis 常见问题解答:

Redis 后台保存模式依赖于现代操作系统中 fork 的写时复制语义:Redis fork(创建子进程)是父进程的精确副本。子进程将数据库转储到磁盘上并最终退出。理论上,子进程应该使用与作为副本的父进程一样多的内存,但实际上由于大多数现代操作系统实现的写时复制语义,父进程和子进程将共享公共内存页面。仅当页面在子级或父级中发生更改时,才会复制页面。因为理论上,所有页面都可能在子进程保存时发生变化。

由于写入时复制 (COW) 机制,保存过程中增加的内存使用量取决于转储过程中执行的写入次数。

相反,您可以做的是,配置一个 Redis 从站并将持久性任务委托给它。

于 2018-02-11T12:05:52.577 回答