Redis小记-内存解析&内存消耗篇

前置:redis内存指标

注:本文默认读者已初步学会使用redis了。

首先我们通过info命令查看相关指标,其中几个memory的重要指标整理出来如下:

属性 解释
used_memory redis内部存储的所有数据的内存总占用量(自身内存+对象内存+缓冲内存)
used_memory_ress redis进程占用的总物理内存
mem_fragmentation_ratio used_memory_ress与used_memory的比值,即为内存碎片率
mem_allocator 内存分配器,默认为jemalloc
表1

一、碎片率

  1. 内存碎片率 > 1时,说明redis进程占用物理内存的总量大于Redis实际存储数据(表1第一行)的内存占用量,溢出来的部分内存被内存碎片消耗,如果溢出部分过大,则说明内存碎片率严重。
  2. 相反的,如果碎片率 < 1时,则说明Redis存储的数据总量已经超出了redis进程占用内存的总量,造成这种情况是因为操作系统把Redis内存交换至硬盘导致(swap),由于硬盘读取速度远远慢与内存,因此这种情况下redis性能极差,可能出现僵死。

二、redis内存消耗的几个来源

2.1:自身内存

redis启动后自身运行所需内存;

2.2:对象内存

内存占用最大的一部分,这里面存储的就是用户自身的数据(业务数据),数据以key-value类型存储,内存消耗可表示为:key内存+value内存。

2.3:缓冲内存

主要由客户端缓冲区 + 复制积压缓冲区 + AOF缓冲区组成,具体解释如下:

  • 客户端缓冲区指的是所有接入redis服务器的TCP连接的输入和输出缓冲,输入缓冲无法被控制,最大空间为1G,超过立即断开连接,输出缓冲通过client-output-buffer-limit控制。
  • 复制积压缓冲区指的是redis在2.8版本以后提供了一块可以重复利用的固定大小的缓冲区,用来实现部分复制功能,使用repl-backlog-size参数控制,默认1MB(主从结构下,主节点只存在一个该缓冲区,从节点共用,那时可以设置较大的缓冲区空间),该缓冲区可以避免全量复制。
  • AOF缓冲区用于存储在redis重写期间保存最近的写入命令,无法控制,通常取决于AOF重写时间以及写入命令量,一般情况下很小。

2.4:内存碎片

redis默认的内存分配器是jemalloc,可选的还有glibctcmalloc;内存分配器为了更好的管理以及重复利用内存,分配策略一般采用固定范围的内存块进行分配;因此,我们在存储一块5kb的内容时,内存分配器可能会为我们分配8kb的块存储,剩下的3kb不能再次分配给其他对象存储,因而沦为了内存碎片;jemalloc对碎片化问题做了优化,一般来讲碎片化率保持在1.03左右。

可能造成内存碎片率过高的场景:

  • 频繁的更新操作,例如频繁对已存在的键做appendsetrange等操作;
  • 大量过期键删除,键对象过期删除后释放的空间无法得到充分的利用,导致碎片率上升。

解决办法:

  • 数据对齐,尽量采用数字类型或固定长度的字符串(大部分业务场景不满足这种方式);
  • 重启,重启节点可以使内存重整理,利用高可用的结构(节点集群+主从结构),将碎片率过高的节点主节点转换为从节点,然后进行安全重启。

2.5:子进程内存消耗

子进程内存消耗指的是执行AOF/RDB重写时redis创建的子进程内存消耗;redis执行fork操作产生的子进程内存占用量对外表现为与父进程相同,理论上需要一倍的物理内存来完成重写的操作。但是linux具备写时复制技术(copy-on-write),父子进程会共享相同的物理内存页,当父进程处理写请求时会对需要修改的页复制出一份副本来完成写操作,而子进程依然读取fork时整个父进程的内存快照,总结:

  • 子进程并不需要消耗一倍的父进程内存,实际消耗根据期间写入命令量决定,但依然要预留出一些内存防止溢出;
  • 需要设置sysctl vm.overcommit_memory = 1允许内核可以分配所有的物理内存,防止redis进程执行fork时因剩余内存不足导致失败;
  • 排查当前系统是否支持开启THP,如果开启建议关闭,防止copy-on-write期间内存过度消耗。