Redis】数据被删去,内存占用还这么大?

操作体系分配给 Redis 的内存有 6GB,经过目标 used_memory_human 发现存储数据只运用了 4GB,为何会这样?为何无法保存数据?

经过 CONFIG SET maxmemory 100mb或许在 redis.conf 配置文件设置 maxmemory 100mb Redis 内存占用限制。当到达内存最大值,会触发内存淘汰战略删去数据。

除此之外,当 key 到达过期时刻,Redis 会有以下两种删去过期数据的战略:

  • 后台定时使命选取部分数据删去;
  • 惰性删去。

假设 Redis 实例保存了 5GB 的数据,现在删去了 2GB 数据,Redis 进程占用的内存一定会下降么?(也叫做 RSS,进程耗费内存页数)。

答案是:或许仍然占用了大约 5GB 的内存,即便 Redis 的数据只占用了 3GB 左右。

我们一定要设置maxmemory,否则 Redis 会继续为新写入的数据分配内存,无法分配就会导致应用程序报错,当然不会导致宕机。

开释的内存去哪了

分明删去了数据,运用 top 指令检查,为何还是占用了那么多内存?

内存都去哪了?运用 info memory 指令获取 Redis 内存相关目标,我列举了几个重要的数据:

127.0.0.1:6379> info memory
# Memory
used_memory:1132832  // Redis 存储数据占用的内存量
used_memory_human:1.08M  // 人类可读方法回来内存总量
used_memory_rss:2977792  // 操作体系角度,进程占用的物理总内存
used_memory_rss_human:2.84M // used_memory_rss 可读性模式展现
used_memory_peak:1183808 // 内存运用的最大值,表明 used_memory 的峰值used_memory_peak_human:1.13M  // 以可读的格局回来 used_memory_peak的值
used_memory_lua:37888  // Lua 引擎所耗费的内存巨细。
used_memory_lua_human:37.00K
maxmemory:2147483648   // 能运用的最大内存值,字节为单位。
maxmemory_human:2.00G  // 可读方法
maxmemory_policy:noeviction // 内存淘汰战略
​
// used_memory_rss / used_memory 的比值,代表内存碎片mem_fragmentation_ratio:2.79

Redis 进程内存耗费主要由以下部分组成:

  • Redis 本身启动所占用的内存;
  • 存储目标数据内存;
  • 缓冲区内存:主要由 client-output-buffer-limit 客户端输出缓冲区、仿制积压缓冲区、AOF 缓冲区。
  • 内存碎片。

Redis 本身空进程占用的内存很小能够忽略不计,目标内存是占比最大的一块,里边存储着所有的数据。

缓冲区内存在大流量场景容易失控,构成 Redis 内存不稳定,需求要点重视。

内存碎片过大会导致分明有空间可用,可是却无法存储数据。

碎片 = used_memory_rss 实践运用的物理内存(RSS 值)除以 used_memory 实践存储数据内存。

什么是内存碎片

内存碎片会构成分明有内存空间闲暇,可是却无法存储数据。举个比方,你跟美丽小姐姐去电影院看电影,肯定想连在一块。

假设现在有 8 个座位,现已卖出了 4 张票,还有 4 张能够买。可是好巧不巧,买票的人很奇葩,分别间隔一个座位买票。

即便还有 4 个座位闲暇,可是你却买不到两个座位连在一块的票,厚礼蟹!

内存碎片构成原因

内存碎片是什么原因导致呢?

主要有两个原因:

  • 内存分配器的分配战略。
  • 键值对的巨细不一样和修改操作:Redis 频频做更新操作、很多过期数据删去,开释的空间(不行接连)无法得到复用,导致碎片率上升。

接下来我分别讨论实践产生的原因……

内存分配器的分配战略

Redis 默认的内存分配器选用 jemalloc,可选的分配器还有:glibc、tcmalloc。

内存分配器并不能做到按需分配,而是选用固定范围的内存块进行分配。

例如 8 字节、16 字节…..,2 KB,4KB,当恳求内存最近接某个固定值的时分,jemalloc 会给它分配最接近固定值巨细的空间。

这样就会呈现内存碎片,比方程序只需求 1.5 KB,内存分配器会分配 2KB 空间,那么这 0.5KB 便是碎片。

这么做的目的是削减内存分配次数,比方恳求 22 字节的空间保存数据,jemalloc 就会分配 32 字节,假如后边还要写入 10 字节,就不需求再向操作体系恳求空间了,能够运用之前恳求的 32 字节。

删去 key 的时分,Redis 并不会立马把内存归还给操作体系,呈现这个情况是由于底层内存分配器管理导致,比方大多数现已删去的 key 仍然与其他有效的 key 分配在同一个内存页中。

别的,分配器为了复用闲暇的内存块,原有 5GB 的数据中删去了 2 GB 后,当再次增加数据到实例中,Redis 的 RSS 会保持稳定,不会增长太多。

由于内存分配器基本上复用了之前删去开释出来的 2GB 内存。

键值对巨细不一样和修改操作

由于内存分配器是按照固定巨细分配内存,所以一般分配的内存空间比实践数据占用的巨细多一些,会构成碎片,下降内存的存储功率。

别的,键值对的频频修改和删去,导致内存空间的扩容和开释,比方本来占用 32 字节的字符串,现在修改为占用 20 字节的字符串,那么开释出的 12 字节便是闲暇空间。

假如下一个数据存储恳求需求恳求 13 字节的字符串,那么刚刚开释的 12 字节空间无法运用,导致碎片。

碎片最大的问题:空间总量足够大,可是这些内存不是接连的,或许大致无法存储数据。

内存碎片解决之道

那该怎么解决呢?

首先要确认是否产生了内存碎片,要点重视前面 INFO memory 指令提示的 mem_fragmentation_ratio 目标,表明内存碎片率:

mem_fragmentation_ratio = used_memory_rss/ used_memory

假如 1 < 碎片率 < 1.5,能够认为是合理的,而大于 1.5 阐明碎片现已超越 50%,需求采取一些手段解决碎片率过大的问题。

重启大法

最简单粗暴的方法便是重启,假如没有敞开耐久化,数据会丢掉。

敞开耐久化的话,需求运用 RDB 或许 AOF 康复数据,假如只有一个实例,数据大的话会导致康复阶段长时刻无法供给服务,高可用大打折扣。

主动整理内存碎片

既然你都叫我靓仔了,就倾囊相助告诉你终极杀招:Redis 4.0 版本后,本身供给了一种内存碎片整理机制。

怎样整理呢?

很简单,还是上面的比方,想要买两张连在一块的电影票。与与他人沟通互换下方位,就实现了。

对于 Redis 来说,当一块接连的内存空间被划分为好几块不接连的空间的时分,操作体系先把数据以依次挪动拼接在一块,并开释本来数据占据的空间,构成一块接连闲暇内存空间。。

如下图所示:

主动整理内存碎片的代价

主动整理虽好,可不要肆意妄为,操作体系把数据移动到新方位,再把原有空间开释是需求耗费资源的。

Redis 操作数据的指令是单线程,所以在数据仿制移动的时分,只能等待整理碎片完成才干处理恳求,构成功能损耗。

怎么防止整理碎片对功能的影响又能实现主动整理呢?

好问题,经过以下两个参数来控制内存碎片整理和完毕机遇,防止占用 CPU 过多,削减整理碎片对 Redis 处理恳求的功能影响。

敞开主动内存碎片整理

CONFIG SET activedefrag yes

这仅仅敞开主动整理,何时整理要同时满意以下两个条件才会触发整理操作。

整理的条件

active-defrag-ignore-bytes 200mb:内存碎片占用的内存到达 200MB,开端整理;

active-defrag-threshold-lower 20:内存碎片的空间占比超越体系分配给 Redis 空间的 20% ,开端整理。

防止对功能构成影响

整理时刻有了,还需求控制整理对功能的影响。由一项两个设置先分配整理碎片占用的 CPU 资源,确保既能正常整理碎片,又能防止对 Redis 处理恳求的功能影响。

active-defrag-cycle-min 20:主动整理进程中,占用 CPU 时刻的份额不低于 20%,从而确保能正常打开整理使命。

active-defrag-cycle-max 50:主动整理进程占用的 CPU 时刻份额不能高于 50%,超越的话就马上中止整理,防止对 Redis 的堵塞,构成高延迟。

总结

假如你发现分明 Redis 存储数据的内存占用远小于操作体系分配给 Redis 的内存,而又无法保存数据,那或许呈现很多内存碎片了。

经过 info memory 指令,看下内存碎片mem_fragmentation_ratio 目标是否正常。

那么就敞开主动整理并合理设置整理机遇和 CPU 资源占用,该机制涉及到内存拷贝,会对 Redis 功能构成潜在风险。

假如遇到 Redis 功能变慢,排查下是否由于整理碎片导致,假如是,那就调小 active-defrag-cycle-max 的值。