什么是写时复制内存

twb*_*twb 6 copy-on-write redis jedis

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

例子,

牛使用了1300MB内存

牛使用1400MB内存

牛使用了 0MB 内存

牛使用了1500MB内存

这些到底意味着什么?据我所知,如果写时复制内存不断增加,就不可能有足够的内存。此外,每次后台保存都使用大量内存时,redis 似乎不起作用。Jedis总是遇到socket超时异常。

Dar*_*ger 11

在这里我将解释一些事情:什么是Copy-on-Write (CoW) 以及它如何消耗内存,为什么设置 'vm.overcommit_memory = 1' 对内存使用和性能问题没有帮助,以及备份的最佳实践上Redis数据。

Copy-on-Write 及其内存使用

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

当子进程Q被fork时,它与父进程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
Run Code Online (Sandbox Code Playgroud)

它表明没有足够的内存来派生子进程来进行备份。如果Redis进程现在消耗2GB内存,当fork子进程时,操作系统会假设你还有另外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.
Run Code Online (Sandbox Code Playgroud)

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

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

Redis备份实践

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


小智 2

来自 Redis 常见问题解答:

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

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

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